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