File Coverage

blib/lib/No/OCRData.pm
Criterion Covered Total %
statement 6 113 5.3
branch 0 38 0.0
condition 0 6 0.0
subroutine 2 5 40.0
pod 3 3 100.0
total 11 165 6.6


line stmt bran cond sub pod time code
1             package No::OCRData;
2              
3             # This package deals with files that can be retrieved from Norwegian
4             # banks, if you have proper agreements. In the good old days, these
5             # files were generated mostly by scanning and OCRing paper, and for
6             # that reason the service is still called "OCRGiro". The data
7             # I've operated on has hardly been OCRed, but that is what they
8             # to date emphasize...
9              
10             # For copyright and author information, see inline POD.
11              
12             require Exporter;
13              
14             @ISA=qw(Exporter);
15             @EXPORT_OK = qw(parse reduce kid_hash);
16              
17 1     1   16536 use strict;
  1         3  
  1         46  
18 1     1   6 use vars qw($VERSION);
  1         2  
  1         1380  
19              
20             $VERSION = sprintf("%d.%02d", q$Revision: 0.93 $ =~ /(\d+)\.(\d+)/);
21              
22              
23             =head1 NAME
24              
25             No::OCRData - Operate on OCRed data from Norwegian banks
26              
27             =head1 SYNOPSIS
28              
29             use No::OCRData qw(parse reduce kid_hash);
30             @data = reduce(parse(@d));
31             print $data[21]{'SUM_BELOP'};
32             %mykids = kid_hash(reduce(parse(@d)));
33             print $mykids{'900969'}{'OPPGJORSDATO'}
34              
35             =head1 DESCRIPTION
36              
37             B, for others, suffice to say that it does not really have much to do with Optical Character Recognition.
38              
39             Denne modulen brukes til å parse og få ut noe fornuftig ut av filene som kommer fra Bankenes Betalingssentrals OCRGiro-tjeneste.
40              
41             Denne dokumentasjonen, eller modulen for den saks skyld, vil ikke gi deg mye uten at du leser BBS sin spesifikasjon, som finnes på
42             L
43              
44             Rutinene lager en hash av hashrefs eller array av hashrefs, der navnene på nøklene er de samme som i spesifikasjonen, med det unntak av mellomrom blir til '_' og ø blir til o. De er alle i store bokstaver.
45              
46             Ingen av rutinene eksporteres implisitt. Du må be om dem. Rutinene er som følger:
47              
48              
49             =over
50              
51             =item C
52              
53             Funksjonen C tar en array som inneholder innholdet i fila (som må leses inn som en array på vanlig måte hvis man faktisk leser fra fil). Den gjør grovarbeidet med å parse fila. Den returnerer en array med hashrefs, der nøklene er navnene fra spesifikasjonen som beskrevet over. 0-er som brukes til padding taes ikke med, men forøvrig gjør ikke C noe forsøk på å gjøre noe å gjøre noe med dataene. Se C.
54              
55             =cut
56              
57             sub parse
58             {
59 0     0 1   my @data;
60 0           foreach my $str (@_) {
61 0           my %record;
62 0           $record{'FORMATKODE'} = substr($str,0,2);
63 0           $record{'TJENESTEKODE'} = substr($str,2,2);
64 0 0         if ($record{'TJENESTEKODE'} eq '00') {
    0          
65             # Vi har et start/slutt-record for forsendelse
66 0           $record{'FORSENDELSESTYPE'} = substr($str,4,2);
67 0           $record{'RECORDTYPE'} = substr($str,6,2);
68 0 0         if ($record{'RECORDTYPE'} eq '10') {
    0          
69             # startrecord for forsendelse
70 0           $record{'DATAAVSENDER'} = substr($str,8,8);
71 0           $record{'FORSENDELSESNUMMER'} = substr($str,16,7);
72 0           $record{'DATAMOTTAKER'} = substr($str,23,8);
73             }
74             elsif ($record{'RECORDTYPE'} eq '89') {
75             # sluttrecord for forsendelse
76 0           $record{'ANTALL_TRANSAKSJONER'} = substr($str,8,8);
77 0           $record{'ANTALL_RECORDS'} = substr($str,16,8);
78 0           $record{'SUM_BELOP'} = substr($str,24,17);
79 0           $record{'OPPGJORSDATO'} = substr($str,41,6);
80             } else {
81 0           die "Ukjent RECORDTYPE $record{'RECORDTYPE'} $!";
82             }
83             }
84             elsif ($record{'TJENESTEKODE'} eq '09') {
85 0           $record{'TRANSAKSJONSTYPE'} = substr($str,4,2);
86 0           $record{'RECORDTYPE'} = substr($str,6,2);
87 0 0         if($record{'TRANSAKSJONSTYPE'} eq '00') {
88             # Start eller sluttrecord for oppdrag
89 0           $record{'OPPDRAGSTYPE'} = $record{'TRANSAKSJONSTYPE'};
90 0           delete $record{'TRANSAKSJONSTYPE'}; # Finnes ikke naa
91 0 0         if ($record{'RECORDTYPE'} eq '20') {
    0          
92             # startrecord for forsendelse
93 0           $record{'AVTALE-ID'} = substr($str,8,9);
94 0           $record{'OPPDRAGSNUMMER'} = substr($str,17,7);
95 0           $record{'OPPDRAGSKONTO'} = substr($str,24,11);
96             }
97             elsif ($record{'RECORDTYPE'} eq '88') {
98             # sluttrecord for oppdrag
99 0           $record{'ANTALL_TRANSAKSJONER'} = substr($str,8,17);
100 0           $record{'ANTALL_RECORDS'} = substr($str,16,8);
101 0           $record{'SUM_BELOP'} = substr($str,24,17);
102 0           $record{'OPPGJORSDATO'} = substr($str,41,6);
103 0           $record{'FORSTE_OPPGJORSDATO'} = substr($str,47,6);
104 0           $record{'SISTE_OPPGJORSDATO'} = substr($str,53,6);
105             } else {
106 0           die "Ukjent RECORDTYPE $record{'RECORDTYPE'} $!";
107             }
108             } else {
109             # Transaksjonsrecord
110 0           $record{'TRANSAKSJONSNUMMER'} = substr($str,8,7);
111 0 0         if ($record{'RECORDTYPE'} eq '30') {
    0          
112             # Belopspost 1
113 0           $record{'OPPGJORSDATO'} = substr($str,15,6);
114 0           $record{'SENTRAL-ID'} = substr($str,21,2);
115 0           $record{'DAGKODE'} = substr($str,23,2);
116 0           $record{'DELAVREGNINGSNUMMER'} = substr($str,25,1);
117 0           $record{'LOPENUMMER'} = substr($str,26,5);
118 0           $record{'BELOP'} = substr($str,32,17);
119 0           $record{'KID'} = substr($str,49,25);
120             }
121             elsif ($record{'RECORDTYPE'} eq '31') {
122             # Belopspost 2
123 0           $record{'BLANKETTNUMMER'} = substr($str,15,10);
124 0           $record{'AVTALE-ID'} = substr($str,25,9);
125 0           $record{'POSTGIROKONTO'} = substr($str,34,7);
126 0           $record{'OPPDRAGSDATO'} = substr($str,41,6);
127 0           $record{'DEBET_KONTO'} = substr($str,47,11);
128             } else {
129 0           die "Ukjent RECORDTYPE $record{'RECORDTYPE'} $!";
130             }
131             }
132             } else {
133 0           die "Ukjent TJENESTEKODE $record{'TJENESTEKODE'} $!";
134             }
135 0           push(@data,\%record);
136             }
137 0           return @data;
138             }
139              
140             =item C
141              
142             C tar en array som har kommet fra C som input og prøver å gjøre en del nødvendige ting med den (astronomer har det med å kalle det denne rutinen gjør for "redusering av data"). Det anbefales at C brukes umiddelbart etter C, men det kan jo tenkes at andre vil gjøre det på en annen måte. Den returner så en modifisert versjon av samme array. Disse forandringene gjøres av C:
143              
144             =over
145              
146             =item *
147              
148             Sletter det overflødige FORMATKODE-feltet.
149              
150             =item *
151              
152             Fjerner whitespace fra starten av KID-feltet.
153              
154             =item *
155              
156             Alle datoer blir transformert fra formen DDMMYY (bare de to siste tallene i årstallet brukes) til en streng på formen YYYY-MM-DD (ISO8601). Det antas her at årstallet begynner med 20.
157              
158             =item *
159              
160             Ledende nuller fra felter som ikke er en KONTO, en KODE eller en TYPE fjernes.
161              
162             =item *
163              
164             Alle beløper er i øre i fila, og deles på hundre for å gjøres om til kroner.
165              
166             =item *
167              
168             Tomme felter fjernes (dette inkluderer felter som kun hadde nuller).
169              
170             =item *
171              
172             DEBET_KONTO og POSTGIROKONTO fjernes hvis den kun inneholdt nuller.
173              
174             =back
175              
176             =cut
177              
178             sub reduce {
179 0     0 1   my @data;
180 0           foreach my $record (@_) {
181 0           delete ${$record}{'FORMATKODE'}; # FORMATKODE-feltet er overfloedig
  0            
182 0 0         if (${$record}{'KID'}) {
  0            
183 0           ${$record}{'KID'} =~ s/^\s*//; # Fjern blanke fra starten av KID
  0            
184             }
185 0           foreach my $key (keys(%{$record})) {
  0            
186 0 0         if ($key =~ m/DATO/)
187             {
188             # Omform datoer til ISO8601-form
189             # NB: Problemer med forrige og neste aarhundre...
190 0           ${$record}{$key} = '20' . substr(${$record}{$key},4,2)
  0            
  0            
191 0           . '-' . substr(${$record}{$key},2,2)
192 0           . '-' . substr(${$record}{$key},0,2);
193             }
194 0 0 0       unless (($key =~ m/KODE/) || ($key =~ m/TYPE/) || ($key =~ m/KONTO/))
      0        
195             {
196             # Fjern 0 fra starten av felt som ikke er en KODE, TYPE eller KONTO
197 0           ${$record}{$key} =~ s/^0*//;
  0            
198             }
199 0 0         unless (${$record}{'DEBET_KONTO'} =~ m/[1-9]/)
  0            
200             {
201 0           delete ${$record}{'DEBET_KONTO'};
  0            
202             }
203 0 0         unless (${$record}{'POSTGIROKONTO'} =~ m/[1-9]/)
  0            
204             {
205 0           delete ${$record}{'POSTGIROKONTO'};
  0            
206             }
207 0 0         if ($key =~ m/BELOP/) {
208             # Alle beloeper er i oere og deles derfor paa hundre
209 0           ${$record}{$key} /= 100;
  0            
210             }
211             # Felter som naa er tomme kan fjernes
212 0 0         if (length(${$record}{$key}) == 0) {
  0            
213 0           delete ${$record}{$key};
  0            
214             }
215             }
216 0           push(@data, $record);
217             }
218 0           return @data;
219             }
220              
221             =item C
222              
223             C tar en array som har blitt redusert med C og returnerer en hash der KID-nummerne brukes som nøkler for transaksjonene som fila inneholder. Transaksjonene selv representeres ved hashrefs, der nøkkelordene fra spesifikasjonen brukes (se over).
224              
225             Data fra fila som ikke er tilknyttet enkelte transaksjoner (f.eks. sum beløp) kastes vekk av denne rutinen. Feltet RECORDTYPE fjernes også fordi data fra RECORDTYPEne 30 og 31 begge inkluderes. 
226              
227             Hvordan dette gjøres i detalj er en smule innviklet, og det er potensiale for en bug i dette. Se under "L" hvis detaljene er interessante.
228              
229              
230             =cut
231              
232              
233             sub kid_hash
234             {
235 0     0 1   my @indata = @_;
236 0           my %data;
237             my $kid;
238 0           foreach my $record (@indata) {
239 0 0         if (${$record}{'RECORDTYPE'} eq '30') {
  0            
240 0           $kid = ${$record}{'KID'};
  0            
241 0           $data{$kid} = $record;
242             }
243 0 0         if (${$record}{'RECORDTYPE'} eq '31') {
  0            
244 0           my $transnr = ${$record}{'TRANSAKSJONSNUMMER'};
  0            
245 0 0         if ($data{$kid}{'TRANSAKSJONSNUMMER'} eq $transnr) {
246 0           foreach my $key (keys(%{$record})) {
  0            
247 0           $data{$kid}{$key} = ${$record}{$key};
  0            
248             }
249 0           delete $data{$kid}{'RECORDTYPE'};
250             } else {
251 0           die "Du har sannsynligvis funnet en bug i No::OCRData::kid_hash, se BUGS i POD.";
252             }
253             }
254             }
255 0           return %data;
256             }
257              
258             1;
259              
260             =back
261              
262             =head1 BUGS/TODO
263              
264             =head2 Y2K
265              
266             C antar at årstallet begynner med 20. Dette vil selvfølgelig ikke fungere hvis man jobber på data fra 1900-tallet (eller 2100-tallet...). Dette er ikke min feil, fordi BBS sine filer representerer årstall med kun to siffer. De har således en Y2K-feil.
267              
268             =head2 Ett oppdrag
269              
270             Jeg har kun operert på filer der det har vært et enkelt oppdrag per fil. Flere oppdrag per fil er utestet og C vil sannsynligvis ikke fungere på slike filer, så se på det som en TODO. Det bør ikke influere de andre rutinene. Jeg antar at hvis man kun har en OCRGiro-avtale vil man kunne bruke C som den er.
271              
272             =head2 Sammenstilling av transaksjoner
273              
274             Å sette sammen transaksjoner fra forskjellige RECORDTYPEr slik det gjøres av C er som nevnt litt innviklet. Det gjøres ved å sammenligne feltene TRANSAKSJONSNUMMER fra forskjellige records. I seg selv greit, og siden alle filer jeg har sett har hatt records med samme transaksjonsnummer umiddelbart etter hverandre, er det antatt at dette gjelder generelt. Spesifikasjonen er ikke klar på dette punktet. C inneholder en enkel sjekk på om dette er tilfelle for hver enkel record, og vil dø med en feilmelding hvis antagelsen ikke holder.
275              
276             =head2 Operere på filehandles
277              
278             Det hadde vært mer elegant om C faktisk kunne operere på filehandles, av mer generisk art.
279              
280              
281             =head1 AUTHOR
282              
283             Kjetil Kjernsmo
284              
285             =head1 COPYRIGHT AND LICENSE
286              
287             Copyright 2003, Kjetil Kjernsmo. Some rights reserved.
288              
289             This program is free software; you may redistribute it and/or modify it
290             under the same terms as Perl itself.
291              
292             =cut
293