File Coverage

blib/lib/Business/ID/VehiclePlate.pm
Criterion Covered Total %
statement 40 78 51.2
branch 11 66 16.6
condition n/a
subroutine 6 6 100.0
pod 1 1 100.0
total 58 151 38.4


line stmt bran cond sub pod time code
1             package Business::ID::VehiclePlate;
2              
3 2     2   480683 use 5.010001;
  2         6  
4 2     2   9 use warnings;
  2         3  
  2         131  
5 2     2   11 use strict;
  2         11  
  2         47  
6              
7 2     2   1926 use DateTime;
  2         1308668  
  2         110  
8 2     2   19 use Exporter 'import';
  2         4  
  2         2795  
9              
10             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
11             our $DATE = '2024-08-05'; # DATE
12             our $DIST = 'Business-ID-VehiclePlate'; # DIST
13             our $VERSION = '0.002'; # VERSION
14              
15             our @EXPORT_OK = qw(parse_idn_vehicle_plate_number);
16              
17             # source data: devdata/prefixes.csv
18             our %prefixes = (
19             A => {
20             iso_prov_codes => "ID-BT",
21             region => "Banten",
22             summary => "Banten, Cilegon, Serang, Pandeglang, Lebak, Tangerang",
23             },
24             AA => {
25             iso_prov_codes => "ID-JT",
26             region => "Jawa Tengah",
27             summary => "Magelang, Purworejo, Temanggung, Kebumen, Wonosobo",
28             },
29             AB => {
30             iso_prov_codes => "ID-YO",
31             region => "DIY",
32             summary => "Yogyakarta, Bantul, Gunung Kidul, Sleman, Kulon Progo",
33             },
34             AD => {
35             iso_prov_codes => "ID-JT",
36             region => "Jawa Tengah",
37             summary => "Surakarta, Sukoharjo, Boyolali, Klaten, Karanganyar, Sragen, Wonogiri",
38             },
39             AE => {
40             iso_prov_codes => "ID-JI",
41             region => "Jawa Timur",
42             summary => "Madiun, Ngawi, Ponorogo, Magetan, Pacitan",
43             },
44             AG => {
45             iso_prov_codes => "ID-JI",
46             region => "Jawa Timur",
47             summary => "Kediri, Blitar, Nganjuk, Tulungagung, Trenggalek",
48             },
49             B => {
50             iso_prov_codes => "ID-JK,ID-JB",
51             region => "DKI",
52             summary => "Jakarta, Depok, Bekasi",
53             },
54             BA => {
55             iso_prov_codes => "ID-SB",
56             region => "Sumatera",
57             summary => "Sumatera Barat",
58             },
59             BB => {
60             iso_prov_codes => "ID-SU",
61             region => "Sumatera",
62             summary => "Sumatera Utara bagian barat",
63             },
64             BD => { iso_prov_codes => "ID-BE", region => "Sumatera", summary => "Bengkulu" },
65             BE => { iso_prov_codes => "ID-LA", region => "Sumatera", summary => "Lampung" },
66             BG => {
67             iso_prov_codes => "ID-SS",
68             region => "Sumatera",
69             summary => "Sumatera Selatan",
70             },
71             BH => { iso_prov_codes => "ID-JA", region => "Sumatera", summary => "Jambi" },
72             BK => {
73             iso_prov_codes => "ID-SU",
74             region => "Sumatera",
75             summary => "Sumatera Utara bagian timur",
76             },
77             BL => { iso_prov_codes => "ID-AC", region => "Sumatera", summary => "Aceh" },
78             BM => { iso_prov_codes => "ID-RI", region => "Sumatera", summary => "Riau" },
79             BN => {
80             iso_prov_codes => "ID-BB",
81             region => "Sumatera",
82             summary => "Bangka-Belitung",
83             },
84             BP => {
85             iso_prov_codes => "ID-KR",
86             region => "Sumatera",
87             summary => "Kepulauan Riau",
88             },
89             D => { iso_prov_codes => "ID-JB", region => "Jawa Barat", summary => "Bandung" },
90             DA => {
91             iso_prov_codes => "ID-KS",
92             region => "Kalimantan",
93             summary => "Banjarmasin",
94             },
95             DB => {
96             iso_prov_codes => "ID-SA",
97             region => "Sulawesi",
98             summary => "Manado, Bolaang Mongondow, Minahasa, Bitung",
99             },
100             DC => {
101             iso_prov_codes => "ID-SR",
102             region => "Sulawesi",
103             summary => "Majumu, Polewari Mandar, Majene",
104             },
105             DD => {
106             iso_prov_codes => "ID-SN",
107             region => "Sulawesi",
108             summary => "Makassar, Takalar, Giwa, Bantaeng",
109             },
110             DE => {
111             iso_prov_codes => "ID-MA",
112             region => "Maluku & Papua",
113             summary => "Maluku, Serang, Ambon, Tual",
114             },
115             DG => {
116             iso_prov_codes => "ID-MU",
117             region => "Maluku & Papua",
118             summary => "Ternate, Halmahera, Tidore, Murotai",
119             },
120             DH => {
121             iso_prov_codes => "ID-NT",
122             region => "Bali & NT",
123             summary => "Pulau Timor, Kupang",
124             },
125             DK => { iso_prov_codes => "ID-BA", region => "Bali & NT", summary => "Bali" },
126             DL => {
127             iso_prov_codes => "ID-SA",
128             region => "Sulawesi",
129             summary => "Sahinge, Sitaro, Talaud",
130             },
131             DM => {
132             iso_prov_codes => "ID-GO",
133             region => "Sulawesi",
134             summary => "Gorontalo, Bone Bolango",
135             },
136             DN => {
137             iso_prov_codes => "ID-ST",
138             region => "Sulawesi",
139             summary => "Donggala, Palu, Poso",
140             },
141             DR => {
142             iso_prov_codes => "ID-NB",
143             region => "Bali & NT",
144             summary => "Pulau Lombok, Mataram",
145             },
146             DT => {
147             iso_prov_codes => "ID-SG",
148             region => "Sulawesi",
149             summary => "Kolaka, Konawe, Wakatobi, Buton, Kendari",
150             },
151             E => {
152             iso_prov_codes => "ID-JB",
153             region => "Jawa Barat",
154             summary => "Cirebon, Majalengka, Indramayu, Kuningan",
155             },
156             EA => {
157             iso_prov_codes => "ID-NB",
158             region => "Bali & NT",
159             summary => "Pulau Sumbawa",
160             },
161             EB => {
162             iso_prov_codes => "ID-NT",
163             region => "Bali & NT",
164             summary => "Pulau Flores",
165             },
166             ED => {
167             iso_prov_codes => "ID-NT",
168             region => "Bali & NT",
169             summary => "Pulau Sumba",
170             },
171             F => {
172             iso_prov_codes => "ID-JB",
173             region => "Jawa Barat",
174             summary => "Bogor, Cianjur,\nSukabumi",
175             },
176             G => {
177             iso_prov_codes => "ID-JT",
178             region => "Jawa Tengah",
179             summary => "Pekalongan, Pemalang, Batang, Tegal, Brebes",
180             },
181             H => {
182             iso_prov_codes => "ID-JT",
183             region => "Jawa Tengah",
184             summary => "Semarang, Kendal, Salatiga, Demak",
185             },
186             K => {
187             iso_prov_codes => "ID-JT",
188             region => "Jawa Tengah",
189             summary => "Pati, Jepara, Kudus, Blora, Rembang, Grombogan",
190             },
191             KB => {
192             iso_prov_codes => "ID-KB",
193             region => "Kalimantan",
194             summary => "Singkawang, Pontianak",
195             },
196             KH => {
197             iso_prov_codes => "ID-KT",
198             region => "Kalimantan",
199             summary => "Palangkaraya, Kotawaringin, Barito",
200             },
201             KT => {
202             iso_prov_codes => "ID-KI",
203             region => "Kalimantan",
204             summary => "Balikpapan, Kutai Kartanegara, Samarinda, Bontang, Kutai",
205             },
206             KU => {
207             iso_prov_codes => "ID-KU",
208             region => "Kalimantan",
209             summary => "Kalimantan Utara",
210             },
211             L => {
212             iso_prov_codes => "ID-JI",
213             region => "Jawa Timur",
214             summary => "Surabaya",
215             },
216             M => { iso_prov_codes => "ID-JI", region => "Jawa Timur", summary => "Madura" },
217             N => {
218             iso_prov_codes => "ID-JI",
219             region => "Jawa Timur",
220             summary => "Malang, Pasuruan, Probolinggo, Lumajang",
221             },
222             P => {
223             iso_prov_codes => "ID-JI",
224             region => "Jawa Timur",
225             summary => "Bondowoso, Jember, Situbondo, Banyuwangi",
226             },
227             PA => {
228             iso_prov_codes => "ID-PA",
229             region => "Maluku & Papua",
230             summary => "Jayapura, Merauke, Mimika, Paniai",
231             },
232             PB => {
233             iso_prov_codes => "ID-PB",
234             region => "Maluku & Papua",
235             summary => "Papua Barat",
236             },
237             R => {
238             iso_prov_codes => "ID-JT",
239             region => "Jawa Tengah",
240             summary => "Banyumas, Purbalingga, Cilacap, Banjarnegara",
241             },
242             S => {
243             iso_prov_codes => "ID-JI",
244             region => "Jawa Timur",
245             summary => "Bojonegoro, Tuban, Mojokerto, Lamongan, Jombang",
246             },
247             T => {
248             iso_prov_codes => "ID-JB",
249             region => "Jawa Barat",
250             summary => "Purwakarta, Karawang, Subang",
251             },
252             W => {
253             iso_prov_codes => "ID-JI",
254             region => "Jawa Timur",
255             summary => "Gresik, Sidoarjo",
256             },
257             Z => {
258             iso_prov_codes => "ID-JB",
259             region => "Jawa Barat",
260             summary => "Garut, Sumedang, Tasikmalaya, Pangandaran, Ciamis, Banjar",
261             },
262             );
263              
264             our %SPEC;
265              
266             $SPEC{parse_idn_vehicle_plate_number} = {
267             v => 1.1,
268             summary => 'Parse Indonesian vehicle plate number',
269             args => {
270             number => {
271             summary => 'Input to be parsed',
272             schema => 'str*',
273             pos => 0,
274             req => 1,
275             },
276             },
277             };
278             sub parse_idn_vehicle_plate_number {
279 2     2 1 481641 my %args = @_;
280              
281             defined(my $num = $args{number})
282 2 100       17 or return [400, "Please specify number"];
283 1         4 $num = uc $num;
284 1         3 my $res = {};
285              
286 1         11 $num =~ s/\s+//g;
287              
288 1 50       6 return [400, "Missing area prefix (1-2 letters)"] unless $num =~ s/\A([A-Z]{1,2})//;
289 1         5 my $prefix = $1;
290 1         3 $res->{prefix} = $prefix;
291 1 50       7 if (my $area = $prefixes{ $prefix }) {
292 1         6 $res->{ind_prefix_area} = $area->{summary};
293 1         4 $res->{prefix_iso_prov_codes} = $area->{iso_prov_codes};
294             } else {
295 0         0 return [400, "Unknown area prefix: $prefix"];
296             }
297              
298 1 50       6 return [400, "Missing main number (1-4 digits after prefix)"] unless $num =~ s/\A(\d{1,4})//;
299 1         4 my $main = $1;
300 1         3 $res->{main} = $main;
301 1 50       7 if ($main < 1) {
    50          
    0          
    0          
302 0         0 return [400, "Main number cannot be 0"];
303             } elsif ($main < 2000) {
304 1         3 $res->{ind_main_vehicle_type} = 'Kendaraan penumpang (1-1999)';
305             } elsif ($main < 7000) {
306 0         0 $res->{ind_main_vehicle_type} = 'Sepeda motor (2000-6999)';
307             } elsif ($main < 8000) {
308 0         0 $res->{ind_main_vehicle_type} = 'Bus (7000-7999)';
309             } else {
310 0         0 $res->{ind_main_vehicle_type} = 'Kendaraan beban atau pengangkut (8000-9999)';
311             }
312              
313             # XXX check whether main number is a pretty number
314              
315 1         3 my $suffix = "";
316             GET_SUFFIX: {
317 1 50       3 last unless $num =~ s/\A([A-Z]{1,3})//;
  1         6  
318 1         4 $suffix = $1;
319             }
320 1         6 $res->{suffix} = $suffix;
321              
322             CHECK_RF_SUFFIX: {
323 1 50       17 last unless $suffix =~ /\ARF(.)\z/;
  1         6  
324 0         0 my $s = $1;
325 0         0 $res->{ind_suffix_vehicle_type} = 'Staf pemerintahan (RF)';
326 0 0       0 if ($s eq 'S') {
    0          
    0          
    0          
    0          
    0          
327 0         0 $res->{ind_suffix_rf_type} = 'Sekretariat Negara (S)';
328             } elsif ($s =~ /\A[OHQ]\z/) {
329 0         0 $res->{ind_suffix_rf_type} = 'Pejabat eselon II (O/H/Q)';
330 0 0       0 $res->{ind_suffix_rf_type} .= ' (Kemenhan)' if $s eq 'H';
331             } elsif ($s eq 'P') {
332 0         0 $res->{ind_suffix_rf_type} = 'Polri (P)';
333             } elsif ($s eq 'D') {
334 0         0 $res->{ind_suffix_rf_type} = 'TNI AD (D)';
335             } elsif ($s eq 'L') {
336 0         0 $res->{ind_suffix_rf_type} = 'TNI AL (L)';
337             } elsif ($s eq 'U') {
338 0         0 $res->{ind_suffix_rf_type} = 'TNI AU (U)';
339             } else {
340 0         0 $res->{ind_suffix_rf_type} = "Tidak dikenal ($s)"
341             }
342             }
343              
344 1 50       6 if ($prefix eq 'B') {
345             CHECK_JAKARTA_SUFFIX1: {
346 0         0 my $s = substr($suffix, 0, 1);
  0         0  
347 0 0       0 if ($s eq 'B') {
    0          
    0          
    0          
    0          
348 0         0 $res->{suffix1_city} = "Jakarta Barat (B)";
349             } elsif ($s eq 'P') {
350 0         0 $res->{suffix1_city} = "Jakarta Pusat (P)";
351             } elsif ($s eq 'S') {
352 0         0 $res->{suffix1_city} = "Jakarta Selatan (S)";
353             } elsif ($s eq 'T') {
354 0         0 $res->{suffix1_city} = "Jakarta Timue (T)";
355             } elsif ($s eq 'U') {
356 0         0 $res->{suffix1_city} = "Jakarta Utara & Kepulauan Seribu (U)";
357             } else {
358 0         0 $res->{suffix1_city} = "Unknown ($s)";
359             }
360             }
361             CHECK_JAKARTA_SUFFIX2: {
362 0 0       0 last unless length($suffix) >= 2;
  0         0  
363 0         0 my $s = substr($suffix, 1, 1);
364 0 0       0 if ($s eq 'A') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
365 0         0 $res->{ind_suffix1_vehicle_type} = "Sedan/pickup (A)";
366             } elsif ($s eq 'D') {
367 0         0 $res->{ind_suffix1_vehicle_type} = "Truk (D)";
368             } elsif ($s eq 'F') {
369 0         0 $res->{ind_suffix1_vehicle_type} = "Minibus/hatchback/city (F)";
370             } elsif ($s eq 'J') {
371 0         0 $res->{ind_suffix1_vehicle_type} = "Jip/SUV (J)";
372             } elsif ($s eq 'Q') {
373 0         0 $res->{ind_suffix1_vehicle_type} = "Kendaraan staf pemerintah (Q)";
374             } elsif ($s eq 'T') {
375 0         0 $res->{ind_suffix1_vehicle_type} = "Taksi (T)";
376             } elsif ($s eq 'U') {
377 0         0 $res->{ind_suffix1_vehicle_type} = "Kendaraan staf pemerintah (U)";
378             } elsif ($s eq 'V') {
379 0         0 $res->{ind_suffix1_vehicle_type} = "Minibus(V)";
380             } else {
381 0         0 $res->{ind_suffix1_vehicle_type} = "Tidak dikenal ($s)";
382             }
383             }
384             }
385              
386 1 50       4 return [400, "Extraneous bits after suffix: $num"] if length $num;
387              
388 1         6 [200, "OK", $res];
389             }
390              
391             1;
392             # ABSTRACT: Parse Indonesian vehicle plate number
393              
394             __END__
395              
396             =pod
397              
398             =encoding UTF-8
399              
400             =head1 NAME
401              
402             Business::ID::VehiclePlate - Parse Indonesian vehicle plate number
403              
404             =head1 VERSION
405              
406             This document describes version 0.002 of Business::ID::VehiclePlate (from Perl distribution Business-ID-VehiclePlate), released on 2024-08-05.
407              
408             =head1 SYNOPSIS
409              
410             use Business::ID::VehiclePlate qw(parse_idn_vehicle_plate_number);
411              
412             my $res = parse_idn_vehicle_plate_number(number => "B 1234 SJW");
413              
414             =head1 DESCRIPTION
415              
416             Keywords: vehicle plate number, registered plate number
417              
418             =head1 FUNCTIONS
419              
420              
421             =head2 parse_idn_vehicle_plate_number
422              
423             Usage:
424              
425             parse_idn_vehicle_plate_number(%args) -> [$status_code, $reason, $payload, \%result_meta]
426              
427             Parse Indonesian vehicle plate number.
428              
429             This function is not exported by default, but exportable.
430              
431             Arguments ('*' denotes required arguments):
432              
433             =over 4
434              
435             =item * B<number>* => I<str>
436              
437             Input to be parsed.
438              
439              
440             =back
441              
442             Returns an enveloped result (an array).
443              
444             First element ($status_code) is an integer containing HTTP-like status code
445             (200 means OK, 4xx caller error, 5xx function error). Second element
446             ($reason) is a string containing error message, or something like "OK" if status is
447             200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
448             element (%result_meta) is called result metadata and is optional, a hash
449             that contains extra information, much like how HTTP response headers provide additional metadata.
450              
451             Return value: (any)
452              
453             =head1 HOMEPAGE
454              
455             Please visit the project's homepage at L<https://metacpan.org/release/Business-ID-VehiclePlate>.
456              
457             =head1 SOURCE
458              
459             Source repository is at L<https://github.com/perlancar/perl-Business-ID-VehiclePlate>.
460              
461             =head1 AUTHOR
462              
463             perlancar <perlancar@cpan.org>
464              
465             =head1 CONTRIBUTING
466              
467              
468             To contribute, you can send patches by email/via RT, or send pull requests on
469             GitHub.
470              
471             Most of the time, you don't need to build the distribution yourself. You can
472             simply modify the code, then test via:
473              
474             % prove -l
475              
476             If you want to build the distribution (e.g. to try to install it locally on your
477             system), you can install L<Dist::Zilla>,
478             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
479             L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
480             Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
481             that are considered a bug and can be reported to me.
482              
483             =head1 COPYRIGHT AND LICENSE
484              
485             This software is copyright (c) 2024 by perlancar <perlancar@cpan.org>.
486              
487             This is free software; you can redistribute it and/or modify it under
488             the same terms as the Perl 5 programming language system itself.
489              
490             =head1 BUGS
491              
492             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Business-ID-VehiclePlate>
493              
494             When submitting a bug or request, please include a test-file or a
495             patch to an existing test-file that illustrates the bug or desired
496             feature.
497              
498             =cut