Package Gnumed :: Package business :: Module gmKVK
[frames] | no frames]

Source Code for Module Gnumed.business.gmKVK

  1  # -*- coding: latin-1 -*- 
  2  """GNUmed German KVK/eGK objects. 
  3   
  4  These objects handle German patient cards (KVK and eGK). 
  5   
  6  KVK: http://www.kbv.de/ita/register_G.html 
  7  eGK: http://www.gematik.de/upload/gematik_Qop_eGK_Spezifikation_Teil1_V1_1_0_Kommentare_4_1652.pdf 
  8   
  9  license: GPL v2 or later 
 10  """ 
 11  #============================================================ 
 12  # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/business/gmKVK.py,v $ 
 13  # $Id: gmKVK.py,v 1.22 2010-01-08 13:49:43 ncq Exp $ 
 14  __version__ = "$Revision: 1.22 $" 
 15  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
 16   
 17  # access our modules 
 18  import sys, os, os.path, fileinput, codecs, time, datetime as pyDT, glob, re as regex, logging 
 19   
 20   
 21  # our modules 
 22  if __name__ == '__main__': 
 23          sys.path.insert(0, '../../') 
 24  from Gnumed.business import gmPerson 
 25  from Gnumed.pycommon import gmExceptions, gmDateTime, gmTools, gmPG2 
 26   
 27   
 28  _log = logging.getLogger('gm.kvk') 
 29  _log.info(__version__) 
 30   
 31  true_egk_fields = [ 
 32          'insurance_company', 
 33          'insurance_number', 
 34          'insuree_number', 
 35          'insuree_status', 
 36          'insuree_status_detail', 
 37          'insuree_status_comment', 
 38          'title', 
 39          'firstnames', 
 40          'lastnames', 
 41          'dob', 
 42          'street', 
 43          'zip', 
 44          'urb', 
 45          'valid_since', 
 46  ] 
 47   
 48   
 49  true_kvk_fields = [ 
 50          'insurance_company', 
 51          'insurance_number', 
 52          'insurance_number_vknr', 
 53          'insuree_number', 
 54          'insuree_status', 
 55          'insuree_status_detail', 
 56          'insuree_status_comment', 
 57          'title', 
 58          'firstnames', 
 59          'name_affix', 
 60          'lastnames', 
 61          'dob', 
 62          'street', 
 63          'urb_region_code', 
 64          'zip', 
 65          'urb', 
 66          'valid_until' 
 67  ] 
 68   
 69   
 70  map_kvkd_tags2dto = { 
 71          'Version': 'libchipcard_version', 
 72          'Datum': 'last_read_date', 
 73          'Zeit': 'last_read_time', 
 74          'Lesertyp': 'reader_type', 
 75          'Kartentyp': 'card_type', 
 76          'KK-Name': 'insurance_company', 
 77          'KK-Nummer': 'insurance_number', 
 78          'KVK-Nummer': 'insurance_number_vknr', 
 79          'VKNR': 'insurance_number_vknr', 
 80          'V-Nummer': 'insuree_number', 
 81          'V-Status': 'insuree_status', 
 82          'V-Statusergaenzung': 'insuree_status_detail', 
 83          'V-Status-Erlaeuterung': 'insuree_status_comment', 
 84          'Titel': 'title', 
 85          'Vorname': 'firstnames', 
 86          'Namenszusatz': 'name_affix', 
 87          'Familienname': 'lastnames', 
 88          'Geburtsdatum': 'dob', 
 89          'Strasse': 'street', 
 90          'Laendercode': 'urb_region_code', 
 91          'PLZ': 'zip', 
 92          'Ort': 'urb', 
 93          'gueltig-seit': 'valid_since', 
 94          'gueltig-bis': 'valid_until', 
 95          'Pruefsumme-gueltig': 'crc_valid', 
 96          'Kommentar': 'comment' 
 97  } 
 98   
 99  issuer_template = u'%s (%s)' 
100  insurance_number_external_id_type = u'Versichertennummer' 
101  insurance_number_external_id_type_egk = u'Versichertennummer (eGK)' 
102   
103  #============================================================ 
104 -class cDTO_eGK(gmPerson.cDTO_person):
105 106 kvkd_card_id_string = u'Elektronische Gesundheitskarte' 107
108 - def __init__(self, filename=None, strict=True):
109 self.dto_type = 'eGK' 110 self.dob_format = '%d%m%Y' 111 self.valid_since_format = '%d%m%Y' 112 self.last_read_time_format = '%H:%M:%S' 113 self.last_read_date_format = '%d.%m.%Y' 114 self.filename = filename 115 116 self.__parse_egk_file(strict = strict)
117 118 # if we need to interpret KBV requirements by the 119 # letter we have to delete the file right here 120 #self.delete_from_source() 121 #-------------------------------------------------------- 122 # external API 123 #--------------------------------------------------------
124 - def get_candidate_identities(self, can_create = False):
125 old_idents = gmPerson.cDTO_person.get_candidate_identities(self, can_create = can_create) 126 127 cmd = u""" 128 select pk_identity from dem.v_external_ids4identity where 129 value = %(val)s and 130 name = %(name)s and 131 issuer = %(kk)s 132 """ 133 args = { 134 'val': self.insuree_number, 135 'name': insurance_number_external_id_type, 136 'kk': issuer_template % (self.insurance_company, self.insurance_number) 137 } 138 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 139 140 # weed out duplicates 141 new_idents = [] 142 for r in rows: 143 for oid in old_idents: 144 if r[0] == oid.ID: 145 break 146 new_idents.append(gmPerson.cIdentity(aPK_obj = r['pk_identity'])) 147 148 old_idents.extend(new_idents) 149 150 return old_idents
151 #--------------------------------------------------------
152 - def import_extra_data(self, identity=None, *args, **kwargs):
153 # FIXME: rather use remember_external_id() 154 155 # Versicherungsnummer 156 identity.add_external_id ( 157 type_name = insurance_number_external_id_type_egk, 158 value = self.insuree_number, 159 issuer = issuer_template % (self.insurance_company, self.insurance_number), 160 comment = u'Nummer (eGK) des Versicherten bei der Krankenkasse' 161 ) 162 # address 163 street = self.street 164 number = regex.findall(' \d+.*', street) 165 if len(number) == 0: 166 number = None 167 else: 168 street = street.replace(number[0], '') 169 number = number[0].strip() 170 identity.link_address ( 171 number = number, 172 street = street, 173 postcode = self.zip, 174 urb = self.urb, 175 state = u'??', 176 country = u'DE' # actually: map urb_region_code 177 )
178 # FIXME: eGK itself 179 #--------------------------------------------------------
180 - def delete_from_source(self):
181 try: 182 os.remove(self.filename) 183 self.filename = None 184 except: 185 _log.exception('cannot delete kvkd file [%s]' % self.filename, verbose = False)
186 #-------------------------------------------------------- 187 # internal helpers 188 #--------------------------------------------------------
189 - def __parse_egk_file(self, strict=True):
190 191 _log.debug('parsing eGK data in [%s]', self.filename) 192 193 egk_file = codecs.open(filename = self.filename, mode = 'rU', encoding = 'utf8') 194 195 card_type_seen = False 196 for line in egk_file: 197 line = line.replace('\n', '').replace('\r', '') 198 tag, content = line.split(':', 1) 199 content = content.strip() 200 201 if tag == 'Kartentyp': 202 card_type_seen = True 203 if content != cDTO_eGK.kvkd_card_id_string: 204 _log.error('parsing wrong card type') 205 _log.error('found : %s', content) 206 _log.error('expected: %s', cDTO_KVK.kvkd_card_id_string) 207 if strict: 208 raise ValueError('wrong card type: %s, expected %s', content, cDTO_KVK.kvkd_card_id_string) 209 else: 210 _log.debug('trying to parse anyway') 211 212 if tag == 'Geburtsdatum': 213 tmp = time.strptime(content, self.dob_format) 214 content = pyDT.datetime(tmp.tm_year, tmp.tm_mon, tmp.tm_mday, tzinfo = gmDateTime.gmCurrentLocalTimezone) 215 216 try: 217 setattr(self, map_kvkd_tags2dto[tag], content) 218 except KeyError: 219 _log.exception('unknown KVKd eGK file key [%s]' % tag) 220 221 # valid_since -> valid_since_timestamp 222 ts = time.strptime ( 223 '%s20%s' % (self.valid_since[:4], self.valid_since[4:]), 224 self.valid_since_format 225 ) 226 227 # last_read_date and last_read_time -> last_read_timestamp 228 ts = time.strptime ( 229 '%s %s' % (self.last_read_date, self.last_read_time), 230 '%s %s' % (self.last_read_date_format, self.last_read_time_format) 231 ) 232 self.last_read_timestamp = pyDT.datetime(ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, tzinfo = gmDateTime.gmCurrentLocalTimezone) 233 234 # guess gender from firstname 235 self.gender = gmTools.coalesce(gmPerson.map_firstnames2gender(firstnames=self.firstnames), 'f') 236 237 if not card_type_seen: 238 _log.warning('no line with card type found, unable to verify')
239 #============================================================
240 -class cDTO_KVK(gmPerson.cDTO_person):
241 242 kvkd_card_id_string = u'Krankenversichertenkarte' 243
244 - def __init__(self, filename=None, strict=True):
245 self.dto_type = 'KVK' 246 self.dob_format = '%d%m%Y' 247 self.valid_until_format = '%d%m%Y' 248 self.last_read_time_format = '%H:%M:%S' 249 self.last_read_date_format = '%d.%m.%Y' 250 self.filename = filename 251 252 self.__parse_kvk_file(strict = strict)
253 254 # if we need to interpret KBV requirements by the 255 # letter we have to delete the file right here 256 #self.delete_from_source() 257 #-------------------------------------------------------- 258 # external API 259 #--------------------------------------------------------
260 - def get_candidate_identities(self, can_create = False):
261 old_idents = gmPerson.cDTO_person.get_candidate_identities(self, can_create = can_create) 262 263 cmd = u""" 264 select pk_identity from dem.v_external_ids4identity where 265 value = %(val)s and 266 name = %(name)s and 267 issuer = %(kk)s 268 """ 269 args = { 270 'val': self.insuree_number, 271 'name': insurance_number_external_id_type, 272 'kk': issuer_template % (self.insurance_company, self.insurance_number) 273 } 274 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 275 276 # weed out duplicates 277 new_idents = [] 278 for r in rows: 279 for oid in old_idents: 280 if r[0] == oid.ID: 281 break 282 new_idents.append(gmPerson.cIdentity(aPK_obj = r['pk_identity'])) 283 284 old_idents.extend(new_idents) 285 286 return old_idents
287 #--------------------------------------------------------
288 - def import_extra_data(self, identity=None, *args, **kwargs):
289 # Versicherungsnummer 290 identity.add_external_id ( 291 type_name = insurance_number_external_id_type, 292 value = self.insuree_number, 293 issuer = issuer_template % (self.insurance_company, self.insurance_number), 294 comment = u'Nummer des Versicherten bei der Krankenkasse' 295 ) 296 # address 297 street = self.street 298 number = regex.findall(' \d+.*', street) 299 if len(number) == 0: 300 number = None 301 else: 302 street = street.replace(number[0], '') 303 number = number[0].strip() 304 identity.link_address ( 305 number = number, 306 street = street, 307 postcode = self.zip, 308 urb = self.urb, 309 state = u'??', 310 country = u'DE' # actually: map urb_region_code 311 )
312 # FIXME: kvk itself 313 #--------------------------------------------------------
314 - def delete_from_source(self):
315 try: 316 os.remove(self.filename) 317 self.filename = None 318 except: 319 _log.exception('cannot delete kvkd file [%s]' % self.filename, verbose = False)
320 #-------------------------------------------------------- 321 # internal helpers 322 #--------------------------------------------------------
323 - def __parse_kvk_file(self, strict=True):
324 325 _log.debug('parsing KVK data in [%s]', self.filename) 326 327 kvk_file = codecs.open(filename = self.filename, mode = 'rU', encoding = 'utf8') 328 329 card_type_seen = False 330 for line in kvk_file: 331 line = line.replace('\n', '').replace('\r', '') 332 tag, content = line.split(':', 1) 333 content = content.strip() 334 335 if tag == 'Kartentyp': 336 card_type_seen = True 337 if content != cDTO_KVK.kvkd_card_id_string: 338 _log.error('parsing wrong card type') 339 _log.error('found : %s', content) 340 _log.error('expected: %s', cDTO_KVK.kvkd_card_id_string) 341 if strict: 342 raise ValueError('wrong card type: %s, expected %s', content, cDTO_KVK.kvkd_card_id_string) 343 else: 344 _log.debug('trying to parse anyway') 345 346 if tag == 'Geburtsdatum': 347 tmp = time.strptime(content, self.dob_format) 348 content = pyDT.datetime(tmp.tm_year, tmp.tm_mon, tmp.tm_mday, tzinfo = gmDateTime.gmCurrentLocalTimezone) 349 350 try: 351 setattr(self, map_kvkd_tags2dto[tag], content) 352 except KeyError: 353 _log.exception('unknown KVKd kvk file key [%s]' % tag) 354 355 # valid_until -> valid_until_timestamp 356 ts = time.strptime ( 357 '28%s20%s' % (self.valid_until[:2], self.valid_until[2:]), 358 self.valid_until_format 359 ) 360 361 # last_read_date and last_read_time -> last_read_timestamp 362 ts = time.strptime ( 363 '%s %s' % (self.last_read_date, self.last_read_time), 364 '%s %s' % (self.last_read_date_format, self.last_read_time_format) 365 ) 366 self.last_read_timestamp = pyDT.datetime(ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, tzinfo = gmDateTime.gmCurrentLocalTimezone) 367 368 # guess gender from firstname 369 self.gender = gmTools.coalesce(gmPerson.map_firstnames2gender(firstnames=self.firstnames), 'f') 370 371 if not card_type_seen: 372 _log.warning('no line with card type found, unable to verify')
373 #============================================================
374 -def detect_card_type(card_file=None):
375 376 data_file = codecs.open(filename = card_file, mode = 'rU', encoding = 'utf8') 377 378 for line in kvk_file: 379 line = line.replace('\n', '').replace('\r', '') 380 tag, content = line.split(':', 1) 381 content = content.strip() 382 383 if tag == 'Kartentyp': 384 pass
385 #============================================================
386 -def get_available_kvks_as_dtos(spool_dir = None):
387 388 kvk_files = glob.glob(os.path.join(spool_dir, 'KVK-*.dat')) 389 dtos = [] 390 for kvk_file in kvk_files: 391 try: 392 dto = cDTO_KVK(filename = kvk_file) 393 except: 394 _log.exception('probably not a KVKd KVK file: [%s]' % kvk_file) 395 continue 396 dtos.append(dto) 397 398 return dtos
399 #------------------------------------------------------------
400 -def get_available_egks_as_dtos(spool_dir = None):
401 402 egk_files = glob.glob(os.path.join(spool_dir, 'eGK-*.dat')) 403 dtos = [] 404 for egk_file in egk_files: 405 try: 406 dto = cDTO_eGK(filename = egk_file) 407 except: 408 _log.exception('probably not a KVKd eGK file: [%s]' % egk_file) 409 continue 410 dtos.append(dto) 411 412 return dtos
413 #------------------------------------------------------------
414 -def get_available_cards_as_dtos(spool_dir = None):
415 416 dtos = [] 417 dtos.extend(get_available_kvks_as_dtos(spool_dir = spool_dir)) 418 dtos.extend(get_available_egks_as_dtos(spool_dir = spool_dir)) 419 420 return dtos
421 #============================================================ 422 # main 423 #------------------------------------------------------------ 424 if __name__ == "__main__": 425 426 from Gnumed.pycommon import gmI18N 427 428 gmI18N.activate_locale() 429 gmDateTime.init() 430
431 - def test_egk_dto():
432 # test cKVKd_file object 433 kvkd_file = sys.argv[2] 434 print "reading eGK data from KVKd file", kvkd_file 435 dto = cDTO_eGK(filename = kvkd_file, strict = False) 436 print dto 437 for attr in true_egk_fields: 438 print getattr(dto, attr)
439
440 - def test_kvk_dto():
441 # test cKVKd_file object 442 kvkd_file = sys.argv[2] 443 print "reading KVK data from KVKd file", kvkd_file 444 dto = cDTO_KVK(filename = kvkd_file, strict = False) 445 print dto 446 for attr in true_kvk_fields: 447 print getattr(dto, attr)
448
449 - def test_get_available_kvks_as_dto():
450 dtos = get_available_kvks_as_dtos(spool_dir = sys.argv[2]) 451 for dto in dtos: 452 print dto
453 454 if (len(sys.argv)) > 1 and (sys.argv[1] == 'test'): 455 if len(sys.argv) < 3: 456 print "give name of KVKd file as first argument" 457 sys.exit(-1) 458 test_egk_dto() 459 #test_kvk_dto() 460 #test_get_available_kvks_as_dto() 461 462 #============================================================ 463 # docs 464 #------------------------------------------------------------ 465 # name | mandat | type | length | format 466 # -------------------------------------------- 467 # Name Kasse | x | str | 2-28 468 # Nr. Kasse | x | int | 7 469 # VKNR | | int | 5 # MUST be derived from Stammdaten-file, not from KVK 470 # Nr. Pat | x | int | 6-12 471 # Status Pat | x | str | 1 or 4 472 # Statuserg. | | str | 1-3 473 # Titel Pat | | str | 3-15 474 # Vorname | | str | 2-28 475 # Adelspraed.| | str | 1-15 476 # Nachname | x | str | 2-28 477 # geboren | x | int | 8 | DDMMYYYY 478 # Straße | | str | 1-28 479 # Ländercode | | str | 1-3 480 # PLZ | x | int | 4-7 481 # Ort | x | str | 2-23 482 # gültig bis | | int | 4 | MMYY 483 484 #============================================================ 485 # $Log: gmKVK.py,v $ 486 # Revision 1.22 2010-01-08 13:49:43 ncq 487 # - adjust to add-external-id() changes 488 # 489 # Revision 1.21 2009/04/03 09:31:37 ncq 490 # - improved docs 491 # 492 # Revision 1.20 2008/08/28 18:30:28 ncq 493 # - region_code -> urb_region_code 494 # - support eGK now that libchipcard can read it :-) 495 # - improved testing 496 # 497 # Revision 1.19 2008/02/25 17:31:41 ncq 498 # - logging cleanup 499 # 500 # Revision 1.18 2008/01/30 13:34:50 ncq 501 # - switch to std lib logging 502 # 503 # Revision 1.17 2007/12/26 12:35:30 ncq 504 # - import_extra_data(..., *args, **kwargs) 505 # 506 # Revision 1.16 2007/11/12 22:54:26 ncq 507 # - fix longstanding semantic bug ! KVK-Nummmer really is VKNR 508 # - delete KVKd file after importing it 509 # - improve get_candidate_identities() 510 # - improve import_extra_data() 511 # - implement delete_from_source() 512 # - cleanup, improve docs 513 # 514 # Revision 1.15 2007/11/02 10:55:37 ncq 515 # - syntax error fix 516 # 517 # Revision 1.14 2007/10/31 22:06:17 ncq 518 # - teach about more fields in file 519 # - start find_me_sql property 520 # 521 # Revision 1.13 2007/10/31 11:27:02 ncq 522 # - fix it again 523 # - test suite 524 # 525 # Revision 1.12 2007/05/11 14:10:19 ncq 526 # - latin1 -> utf8 527 # 528 # Revision 1.11 2007/02/17 13:55:26 ncq 529 # - consolidate, remove bitrot 530 # 531 # Revision 1.10 2007/02/15 14:54:47 ncq 532 # - fix test suite 533 # - true_kvk_fields list 534 # - map_kvkd_tags2dto 535 # - cDTO_KVK() 536 # - get_available_kvks_as_dtos() 537 # 538 # Revision 1.9 2006/01/01 20:37:22 ncq 539 # - cleanup 540 # 541 # Revision 1.8 2005/11/01 08:49:49 ncq 542 # - naming fix 543 # 544 # Revision 1.7 2005/03/06 14:48:23 ncq 545 # - patient pick list now works with 'field name' not 'data idx' 546 # 547 # Revision 1.6 2004/03/04 19:46:53 ncq 548 # - switch to package based import: from Gnumed.foo import bar 549 # 550 # Revision 1.5 2004/03/02 10:21:10 ihaywood 551 # gmDemographics now supports comm channels, occupation, 552 # country of birth and martial status 553 # 554 # Revision 1.4 2004/02/25 09:46:20 ncq 555 # - import from pycommon now, not python-common 556 # 557 # Revision 1.3 2003/11/17 10:56:34 sjtan 558 # 559 # synced and commiting. 560 # 561 # Revision 1.1 2003/10/23 06:02:38 sjtan 562 # 563 # manual edit areas modelled after r.terry's specs. 564 # 565 # Revision 1.2 2003/04/19 22:53:46 ncq 566 # - missing parameter for %s 567 # 568 # Revision 1.1 2003/04/09 16:15:24 ncq 569 # - KVK classes and helpers 570 # 571