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