diff --git a/lib/al/dune b/lib/al/dune new file mode 100644 index 0000000..90f5d36 --- /dev/null +++ b/lib/al/dune @@ -0,0 +1,4 @@ +(library + (name al) + (modules nipt) + (libraries tools)) diff --git a/lib/al/nipt.ml b/lib/al/nipt.ml new file mode 100644 index 0000000..51480dd --- /dev/null +++ b/lib/al/nipt.ml @@ -0,0 +1,28 @@ +open Tools + +exception Invalid_length +exception Invalid_format + +let nipt_re = Str.regexp "^[A-M][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][A-Z]$" + +let compact number = + let number = + Utils.clean number " " |> String.uppercase_ascii |> String.trim + in + if String.starts_with number ~prefix:"AL" then + String.sub number 2 (String.length number - 2) + else if String.starts_with number ~prefix:"(AL)" then + String.sub number 4 (String.length number - 4) + else number + +let validate number = + let number = compact number in + print_string number; + if not (Str.string_match nipt_re number 0) then raise Invalid_format + else number + +let is_valid number = + try + ignore (validate number); + true + with _ -> false diff --git a/lib/al/nipt.mli b/lib/al/nipt.mli new file mode 100644 index 0000000..6e21a80 --- /dev/null +++ b/lib/al/nipt.mli @@ -0,0 +1,32 @@ +(* +NIPT, NUIS (Numri i Identifikimit për Personin e Tatueshëm, Albanian tax number). + +The Albanian NIPT is a 10-digit number with the first and last character +being letters. The number is assigned to individuals and organisations for +tax purposes. + +The first letter indicates the decade the number was assigned or date birth +date for individuals, followed by a digit for the year. The next two digits +contain the month (and gender for individuals and region for organisations) +followed by two digits for the day of the month. The remainder is a serial +followed by a check letter (check digit algorithm unknown). +*) + +exception Invalid_length +(** Exception raised when the NIPT number has an invalid length. *) + +exception Invalid_format +(** Exception raised when the NIPT number has an invalid format. *) + +val nipt_re : Str.regexp + +val compact : string -> string +(** [compact number] removes spaces and converts [number] to uppercase. *) + +val validate : string -> string +(** [validate number] checks if [number] is a valid NIPT number. + It raises [Invalid_length] or [Invalid_format] if the number is not valid. *) + +val is_valid : string -> bool +(** [is_valid number] checks if [number] is a valid NIPT number. + It returns [true] if the number is valid, [false] otherwise. *) diff --git a/test/al/dune b/test/al/dune new file mode 100644 index 0000000..77bd9c4 --- /dev/null +++ b/test/al/dune @@ -0,0 +1,4 @@ +(test + (name test_nipt) + (libraries alcotest al) + (modules test_nipt)) diff --git a/test/al/test_nipt.ml b/test/al/test_nipt.ml new file mode 100644 index 0000000..af8d29d --- /dev/null +++ b/test/al/test_nipt.ml @@ -0,0 +1,134 @@ +open Alcotest + +let test_is_valid () = + let numbers = + [ + "J 98624806 P" + ; "J61827501H" + ; "J61922018S" + ; "J61923008Q" + ; "J62903770O" + ; "J66702410U" + ; "J67902218L" + ; "J67902618M" + ; "J73721043Q" + ; "J74517201G" + ; "J76418907K" + ; "J76705047U" + ; "J77411245Q" + ; "J78716317H" + ; "J82916489E" + ; "J86526614T" + ; "J91425005N" + ; "J93910409N" + ; "K 01725001F" + ; "K 11723003 M" + ; "K 37507987 N" + ; "K 41424801 U" + ; "K 47905861 R" + ; "K 63005203 O" + ; "K 67204202 P" + ; "K01730502W" + ; "K11515001T" + ; "K12113002H" + ; "K13001013H" + ; "K14019001H" + ; "K21622001M" + ; "K22218003V" + ; "K31518077S" + ; "K31525146H" + ; "K31526056N" + ; "K32203501H" + ; "K32801430W" + ; "K33714725W" + ; "K36308746I" + ; "K36520204A" + ; "K41315003J" + ; "K46621201I" + ; "K51518058O" + ; "K56417201G" + ; "K59418208E" + ; "K61617040L" + ; "K71822006R" + ; "K72113010E" + ; "K81428502L" + ; "K81618039O" + ; "K82418002C" + ; "K82612003J" + ; "K91725009J" + ; "K92402023O" + ; "L 22614402 H" + ; "L 62119008 A" + ; "L01622006F" + ; "L01717030C" + ; "L02023501H" + ; "L02226012N" + ; "L03321203G" + ; "L03929803I" + ; "L06426702Q" + ; "L07305201K" + ; "L08711201I" + ; "L11325024K" + ; "L11810502T" + ; "L12213005M" + ; "L14118803B" + ; "L21310054D" + ; "L21408015A" + ; "L21429502L" + ; "L21906001L" + ; "L21923507N" + ; "L22804207O" + ; "L24006002V" + ; "L29616001A" + ; "L31518001O" + ; "L32210507A" + ; "L32319014A" + ; "L32622601G" + ; "L41410025S" + ; "L41512005R" + ; "L42008005H" + ; "L42115015G" + ; "L42307007E" + ; "L44119601E" + ; "L47014204F" + ; "L48117101S" + ; "L52305009L" + ; "L58428303T" + ; "L62119008A" + ; "L72031013B" + ; "L81506043D" + ; "L81618040T" + ; "L82306024Q" + ; "L98602504L" + ; "M 02129023 S" + ; "M 11807013 N" + ; "M02129023 S" + ] + in + List.iter + (fun number -> check bool number true (Al.Nipt.is_valid number)) + numbers + +let test_is_not_valid () = + let numbers = + [ + "A 123 V" + ; "ABCDEF" + ; "1234567890" + ; "A B C D E F" + ; "J 986206 P" + ; "X 98624806 M" + ; "Z67902218L" + ] + in + List.iter + (fun number -> check bool number false (Al.Nipt.is_valid number)) + numbers + +let suite = + [ + ("test_validate", `Quick, test_is_valid) + ; ("test_is_not_valid", `Quick, test_is_not_valid) + ] + +let () = Alcotest.run "Us.Atin" [ ("suite", suite) ]