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

# Source Code for Module Gnumed.business.gmKVK

      1  # -*- coding: utf-8 -*-
      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  #============================================================
     10  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
     11  __license__ = "GPL v2 or later"
     12
     13  import sys
     14  import os
     15  import os.path
     16  import fileinput
     17  import io
     18  import time
     19  import glob
     20  import datetime as pyDT
     21  import re as regex
     22  #import simplejson as json
     23  import logging
     24
     25
     26  # our modules
     27  if __name__ == '__main__':
     28          sys.path.insert(0, '../../')
     29  from Gnumed.business import gmPerson
     30  from Gnumed.pycommon import gmExceptions, gmDateTime, gmTools, gmPG2
     31
     32
     33  _log = logging.getLogger('gm.kvk')
     34
     35  true_egk_fields = [
     36          'insurance_company',
     37          'insurance_number',
     38          'insuree_number',
     39          'insuree_status',
     40          'insuree_status_detail',
     41          'insuree_status_comment',
     42          'title',
     43          'firstnames',
     44          'lastnames',
     45          'dob',
     46          'street',
     47          'zip',
     48          'urb',
     49          'valid_since',
     50  ]
     51
     52
     53  true_kvk_fields = [
     54          'insurance_company',
     55          'insurance_number',
     56          'insurance_number_vknr',
     57          'insuree_number',
     58          'insuree_status',
     59          'insuree_status_detail',
     60          'insuree_status_comment',
     61          'title',
     62          'firstnames',
     63          'name_affix',
     64          'lastnames',
     65          'dob',
     66          'street',
     67          'urb_region_code',
     68          'zip',
     69          'urb',
     70          'valid_until'
     71  ]
     72
     73
     74  map_kvkd_tags2dto = {
     75          'Version': 'libchipcard_version',
     76          'Datum': 'last_read_date',
     77          'Zeit': 'last_read_time',
     78          'Lesertyp': 'reader_type',
     79          'Kartentyp': 'card_type',
     80          'KK-Name': 'insurance_company',
     81          'KK-Nummer': 'insurance_number',
     82          'KVK-Nummer': 'insurance_number_vknr',
     83          'VKNR': 'insurance_number_vknr',
     84          'V-Nummer': 'insuree_number',
     85          'V-Status': 'insuree_status',
     86          'V-Statusergaenzung': 'insuree_status_detail',
     87          'V-Status-Erlaeuterung': 'insuree_status_comment',
     88          'Titel': 'title',
     89          'Vorname': 'firstnames',
     90          'Namenszusatz': 'name_affix',
     91          'Familienname': 'lastnames',
     92          'Geburtsdatum': 'dob',
     93          'Strasse': 'street',
     94          'Laendercode': 'urb_region_code',
     95          'PLZ': 'zip',
     96          'Ort': 'urb',
     97          'gueltig-seit': 'valid_since',
     98          'gueltig-bis': 'valid_until',
     99          'Pruefsumme-gueltig': 'crc_valid',
    100          'Kommentar': 'comment'
    101  }
    102
    103
    104  map_CCRdr_gender2gm = {
    105          'M': 'm',
    106          'W': 'f',
    107          'U': None,
    108          'D': 'h'
    109  }
    110
    111
    112  map_CCRdr_region_code2country = {
    113          'D': 'DE'
    114  }
    115
    116
    117  EXTERNAL_ID_ISSUER_TEMPLATE = '%s (%s)'
    118  EXTERNAL_ID_TYPE_VK_INSUREE_NUMBER = 'Versichertennummer'
    119  EXTERNAL_ID_TYPE_VK_INSUREE_NUMBER_EGK = 'Versichertennummer (eGK)'
    120
    121  #============================================================
    122 -class cDTO_CCRdr(gmPerson.cDTO_person):
    123
    124 -        def __init__(self, filename=None, strict=True):
    125
    126                  gmPerson.cDTO_person.__init__(self)
    127
    128                  self.filename = filename
    129                  self.date_format = '%Y%m%d'
    130                  self.valid_since = None
    131                  self.valid_until = None
    132                  self.card_is_rejected = False
    133                  self.card_is_expired = False
    134
    135                  self.__load_vk_file()
    136
    137                  # if we need to interpret KBV requirements by the
    138                  # letter we have to delete the file right here
    139                  #self.delete_from_source()
    140
    141          #--------------------------------------------------------
    142          # external API
    143          #--------------------------------------------------------
    144 -        def get_candidate_identities(self, can_create = False):
    145                  old_idents = gmPerson.cDTO_person.get_candidate_identities(self, can_create = can_create)
    146
    147                  # look for candidates based on their Insuree Number
    148                  if not self.card_is_rejected:
    149                          cmd = """
    150                                  SELECT pk_identity FROM dem.v_external_ids4identity WHERE
    151                                          value = %(val)s AND
    152                                          name = %(name)s AND
    153                                          issuer = %(kk)s
    154                                  """
    155                          args = {
    156                                  'val': self.insuree_number,
    157                                  'name': '%s (%s)' % (
    158                                          EXTERNAL_ID_TYPE_VK_INSUREE_NUMBER,
    159                                          self.raw_data['Karte']
    160                                  ),
    161                                  'kk': EXTERNAL_ID_ISSUER_TEMPLATE % (self.raw_data['KostentraegerName'], self.raw_data['Kostentraegerkennung'])
    162                          }
    163                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = None)
    164
    165                          # weed out duplicates
    166                          name_candidate_ids = [ o.ID for o in old_idents ]
    167                          for r in rows:
    168                                  if r[0] not in name_candidate_ids:
    169                                          old_idents.append(gmPerson.cPerson(aPK_obj = r[0]))
    170
    171                  return old_idents
    172
    173          #--------------------------------------------------------
    174 -        def delete_from_source(self):
    175  #               try:
    176  #                       os.remove(self.filename)
    177  #                       self.filename = None
    178  #               except:
    179  #                       _log.exception('cannot delete CCReader file [%s]' % self.filename, verbose = False)
    180                  pass    # for now
    181
    182          #--------------------------------------------------------
    183 -        def import_extra_data(self, identity=None, *args, **kwargs):
    184                  if not self.card_is_rejected:
    185                          args = {
    186                                  'pat': identity.ID,
    187                                  'dob': self.preformatted_dob,
    188                                  'valid_until': self.valid_until,
    189                                  'data': self.raw_data
    190                          }
    191                          cmd = """
    192                                  INSERT INTO de_de.insurance_card (
    193                                          fk_identity,
    194                                          formatted_dob,
    195                                          valid_until,
    196                                          raw_data
    197                                  ) VALUES (
    198                                          %(pat)s,
    199                                          %(dob)s,
    200                                          %(valid_until)s,
    201                                          %(data)s
    202                                  )"""
    203                          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    204
    205          #--------------------------------------------------------
    206          # internal helpers
    207          #--------------------------------------------------------
    208 -        def __load_vk_file(self):
    209
    210                  _log.debug('loading eGK/KVK/PKVK data from [%s]', self.filename)
    211                  vk_file = io.open(self.filename, mode = 'rt', encoding = 'utf8')
    212                  self.raw_data = json.load(vk_file)
    213                  vk_file.close()
    214
    215                  if self.raw_data['Fehlerbericht']['FehlerNr'] != '0x9000':
    216                          _log.error('error [%s] reading VK: %s', self.raw_data['Fehlerbericht']['FehlerNr'], self.raw_data['Fehlerbericht']['Fehlermeldung'])
    217                          raise ValueError('error [%s] reading VK: %s' % (self.raw_data['Fehlerbericht']['FehlerNr'], self.raw_data['Fehlerbericht']['Fehlermeldung']))
    218
    219                  # rejection
    220                  if self.raw_data['Ablehnen'] == 'ja':
    221                          self.card_is_rejected = True
    222                          _log.info('eGK may contain insurance information but KBV says it must be rejected because it is of generation 0')
    223
    224                  # validity
    225                  # - since
    226                  tmp = time.strptime(self.raw_data['VersicherungsschutzBeginn'], self.date_format)
    227                  self.valid_since = pyDT.date(tmp.tm_year, tmp.tm_mon, tmp.tm_mday)
    228                  # - until
    229                  tmp = time.strptime(self.raw_data['VersicherungsschutzEnde'], self.date_format)
    230                  self.valid_until = pyDT.date(tmp.tm_year, tmp.tm_mon, tmp.tm_mday)
    231                  if self.valid_until < pyDT.date.today():
    232                          self.card_is_expired = True
    233
    234                  # DTO source
    235                  src_attrs = []
    236                  if self.card_is_expired:
    237                          src_attrs.append(_('expired'))
    238                  if self.card_is_rejected:
    239                          _log.info('eGK contains insurance information but KBV says it must be rejected because it is of generation 0')
    240                          src_attrs.append(_('rejected'))
    241                  src_attrs.append('CCReader')
    242                  self.source = '%s (%s)' % (
    243                          self.raw_data['Karte'],
    244                          ', '.join(src_attrs)
    245                  )
    246
    247                  # name / gender
    248                  self.firstnames = self.raw_data['Vorname']
    249                  self.lastnames = self.raw_data['Nachname']
    250                  self.gender = map_CCRdr_gender2gm[self.raw_data['Geschlecht']]
    251
    252                  # title
    253                  title_parts = []
    254                  for part in ['Titel', 'Namenszusatz', 'Vorsatzwort']:
    255                          tmp = self.raw_data[part].strip()
    256                          if tmp == '':
    257                                  continue
    258                          title_parts.append(tmp)
    259                  if len(title_parts) > 0:
    260                          self.title = ' '.join(title_parts)
    261
    262                  # dob
    263                  dob_str = self.raw_data['Geburtsdatum']
    264                  year_str = dob_str[:4]
    265                  month_str = dob_str[4:6]
    266                  day_str = dob_str[6:8]
    267                  self.preformatted_dob = '%s.%s.%s' % (day_str, month_str, year_str)     # pre-format for printing including "0"-parts
    268                  if year_str == '0000':
    269                          self.dob = None                 # redundant but explicit is good
    270                  else:
    271                          if day_str == '00':
    272                                  self.dob_is_estimated = True
    273                                  day_str = '01'
    274                          if month_str == '00':
    275                                  self.dob_is_estimated = True
    276                                  month_str = '01'
    277                          dob_str = year_str + month_str + day_str
    278                          tmp = time.strptime(dob_str, self.date_format)
    279                          self.dob = pyDT.datetime(tmp.tm_year, tmp.tm_mon, tmp.tm_mday, 11, 11, 11, 111, tzinfo = gmDateTime.gmCurrentLocalTimezone)
    280
    281                  # addresses
    282                  # - street
    283                  try:
    284                          adr = self.raw_data['StrassenAdresse']
    285                          try:
    286                                  self.remember_address (
    287                                          adr_type = 'eGK (Wohnadresse)',
    288                                          number = adr['Hausnummer'],
    289                                          subunit = adr['Anschriftenzusatz'],
    290                                          street = adr['Strasse'],
    291                                          urb = adr['Ort'],
    292                                          region_code = '',
    293                                          zip = adr['Postleitzahl'],
    294                                          country_code = map_CCRdr_region_code2country[adr['Wohnsitzlaendercode']]
    295                                  )
    296                          except ValueError:
    297                                  _log.exception('invalid street address on card')
    298                          except KeyError:
    299                                  _log.error('unknown country code [%s] on card in street address', adr['Wohnsitzlaendercode'])
    300                  except KeyError:
    301                          _log.warning('no street address on card')
    302                  # PO Box
    303                  try:
    304                          adr = self.raw_data['PostfachAdresse']
    305                          try:
    306                                  self.remember_address (
    307                                          adr_type = 'eGK (Postfach)',
    308                                          number = adr['Postfach'],
    309                                          #subunit = adr['Anschriftenzusatz'],
    310                                          street = _('PO Box'),
    311                                          urb = adr['PostfachOrt'],
    312                                          region_code = '',
    313                                          zip = adr['PostfachPLZ'],
    314                                          country_code = map_CCRdr_region_code2country[adr['PostfachWohnsitzlaendercode']]
    315                                  )
    316                          except ValueError:
    317                                  _log.exception('invalid PO Box address on card')
    318                          except KeyError:
    319                                  _log.error('unknown country code [%s] on card in PO Box address', adr['Wohnsitzlaendercode'])
    320                  except KeyError:
    321                          _log.warning('no PO Box address on card')
    322
    323                  if not (self.card_is_expired or self.card_is_rejected):
    324                          self.insuree_number = None
    325                          try:
    326                                  self.insuree_number = self.raw_data['Versicherten_ID']
    327                          except KeyError:
    328                                  pass
    329                          try:
    330                                  self.insuree_number = self.raw_data['Versicherten_ID_KVK']
    331                          except KeyError:
    332                                  pass
    333                          try:
    334                                  self.insuree_number = self.raw_data['Versicherten_ID_PKV']
    335                          except KeyError:
    336                                  pass
    337                          if self.insuree_number is not None:
    338                                  try:
    339                                          self.remember_external_id (
    340                                                  name = '%s (%s)' % (
    341                                                          EXTERNAL_ID_TYPE_VK_INSUREE_NUMBER,
    342                                                          self.raw_data['Karte']
    343                                                  ),
    344                                                  value = self.insuree_number,
    345                                                  issuer = EXTERNAL_ID_ISSUER_TEMPLATE % (self.raw_data['KostentraegerName'], self.raw_data['Kostentraegerkennung']),
    346                                                  comment = 'Nummer (eGK) des Versicherten bei der Krankenkasse, gültig: %s - %s' % (
    347                                                          gmDateTime.pydt_strftime(self.valid_since, '%Y %b %d'),
    348                                                          gmDateTime.pydt_strftime(self.valid_until, '%Y %b %d')
    349                                                  )
    350                                          )
    351                                  except KeyError:
    352                                          _log.exception('no insurance information on eGK')
    353
    354  #============================================================
    355 -class cDTO_eGK(gmPerson.cDTO_person):
    356
    357          kvkd_card_id_string = 'Elektronische Gesundheitskarte'
    358
    359 -        def __init__(self, filename=None, strict=True):
    360                  self.card_type = 'eGK'
    361                  self.dob_format = '%d%m%Y'
    362                  self.valid_since_format = '%d%m%Y'
    363                  self.last_read_time_format = '%H:%M:%S'
    364                  self.last_read_date_format = '%d.%m.%Y'
    365                  self.filename = filename
    366
    367                  self.__parse_egk_file(strict = strict)
    368
    369                  # if we need to interpret KBV requirements by the
    370                  # letter we have to delete the file right here
    371                  #self.delete_from_source()
    372          #--------------------------------------------------------
    373          # external API
    374          #--------------------------------------------------------
    375 -        def get_candidate_identities(self, can_create = False):
    376                  old_idents = gmPerson.cDTO_person.get_candidate_identities(self, can_create = can_create)
    377
    378                  cmd = """
    379  select pk_identity from dem.v_external_ids4identity where
    380          value = %(val)s and
    381          name = %(name)s and
    382          issuer = %(kk)s
    383  """
    384                  args = {
    385                          'val': self.insuree_number,
    386                          'name': EXTERNAL_ID_TYPE_VK_INSUREE_NUMBER,
    387                          'kk': EXTERNAL_ID_ISSUER_TEMPLATE % (self.insurance_company, self.insurance_number)
    388                  }
    389                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    390
    391                  # weed out duplicates
    392                  new_idents = []
    393                  for r in rows:
    394                          for oid in old_idents:
    395                                  if r[0] == oid.ID:
    396                                          break
    397                          new_idents.append(gmPerson.cPerson(aPK_obj = r['pk_identity']))
    398
    399                  old_idents.extend(new_idents)
    400
    401                  return old_idents
    402          #--------------------------------------------------------
    403 -        def import_extra_data(self, identity=None, *args, **kwargs):
    404                  # FIXME: rather use remember_external_id()
    405
    406                  # Versicherungsnummer
    407                  identity.add_external_id (
    408                          type_name = EXTERNAL_ID_TYPE_VK_INSUREE_NUMBER_EGK,
    409                          value = self.insuree_number,
    410                          issuer = EXTERNAL_ID_ISSUER_TEMPLATE % (self.insurance_company, self.insurance_number),
    411                          comment = 'Nummer (eGK) des Versicherten bei der Krankenkasse'
    412                  )
    413                  # address
    414                  street = self.street
    415                  number = regex.findall(' \d+.*', street)
    416                  if len(number) == 0:
    417                          number = None
    418                  else:
    419                          street = street.replace(number[0], '')
    420                          number = number[0].strip()
    421                  identity.link_address (
    422                          number = number,
    423                          street = street,
    424                          postcode = self.zip,
    425                          urb = self.urb,
    426                          region_code = '',                       # actually: map urb2region ?
    427                          country_code = 'DE'             # actually: map urb+region2country_code
    428                  )
    429                  # FIXME: eGK itself
    430          #--------------------------------------------------------
    431 -        def delete_from_source(self):
    432                  try:
    433                          os.remove(self.filename)
    434                          self.filename = None
    435                  except:
    436                          _log.exception('cannot delete kvkd file [%s]' % self.filename, verbose = False)
    437          #--------------------------------------------------------
    438          # internal helpers
    439          #--------------------------------------------------------
    440 -        def __parse_egk_file(self, strict=True):
    441
    442                  _log.debug('parsing eGK data in [%s]', self.filename)
    443
    444                  egk_file = io.open(self.filename, mode = 'rt', encoding = 'utf8')
    445
    446                  card_type_seen = False
    447                  for line in egk_file:
    448                          line = line.replace('\n', '').replace('\r', '')
    449                          tag, content = line.split(':', 1)
    450                          content = content.strip()
    451
    452                          if tag == 'Kartentyp':
    453                                  card_type_seen = True
    454                                  if content != cDTO_eGK.kvkd_card_id_string:
    455                                          _log.error('parsing wrong card type')
    456                                          _log.error('found   : %s', content)
    457                                          _log.error('expected: %s', cDTO_KVK.kvkd_card_id_string)
    458                                  if strict:
    459                                          raise ValueError('wrong card type: %s, expected %s', content, cDTO_KVK.kvkd_card_id_string)
    460                                  else:
    461                                          _log.debug('trying to parse anyway')
    462
    463                          if tag == 'Geburtsdatum':
    464                                  tmp = time.strptime(content, self.dob_format)
    465                                  content = pyDT.datetime(tmp.tm_year, tmp.tm_mon, tmp.tm_mday, tzinfo = gmDateTime.gmCurrentLocalTimezone)
    466
    467                          try:
    468                                  setattr(self, map_kvkd_tags2dto[tag], content)
    469                          except KeyError:
    470                                  _log.exception('unknown KVKd eGK file key [%s]' % tag)
    471
    472                  # valid_since -> valid_since_timestamp
    473                  ts = time.strptime (
    474                          '%s20%s' % (self.valid_since[:4], self.valid_since[4:]),
    475                          self.valid_since_format
    476                  )
    477
    478                  # last_read_date and last_read_time -> last_read_timestamp
    479                  ts = time.strptime (
    480                          '%s %s' % (self.last_read_date, self.last_read_time),
    481                          '%s %s' % (self.last_read_date_format, self.last_read_time_format)
    482                  )
    483                  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)
    484
    485                  # guess gender from firstname
    486                  self.gender = gmTools.coalesce(gmPerson.map_firstnames2gender(firstnames=self.firstnames), 'f')
    487
    488                  if not card_type_seen:
    489                          _log.warning('no line with card type found, unable to verify')
    490
    491  #============================================================
    492 -class cDTO_KVK(gmPerson.cDTO_person):
    493
    494          kvkd_card_id_string = 'Krankenversichertenkarte'
    495
    496 -        def __init__(self, filename=None, strict=True):
    497                  self.card_type = 'KVK'
    498                  self.dob_format = '%d%m%Y'
    499                  self.valid_until_format = '%d%m%Y'
    500                  self.last_read_time_format = '%H:%M:%S'
    501                  self.last_read_date_format = '%d.%m.%Y'
    502                  self.filename = filename
    503
    504                  self.__parse_kvk_file(strict = strict)
    505
    506                  # if we need to interpret KBV requirements by the
    507                  # letter we have to delete the file right here
    508                  #self.delete_from_source()
    509          #--------------------------------------------------------
    510          # external API
    511          #--------------------------------------------------------
    512 -        def get_candidate_identities(self, can_create = False):
    513                  old_idents = gmPerson.cDTO_person.get_candidate_identities(self, can_create = can_create)
    514
    515                  cmd = """
    516  select pk_identity from dem.v_external_ids4identity where
    517          value = %(val)s and
    518          name = %(name)s and
    519          issuer = %(kk)s
    520  """
    521                  args = {
    522                          'val': self.insuree_number,
    523                          'name': EXTERNAL_ID_TYPE_VK_INSUREE_NUMBER,
    524                          'kk': EXTERNAL_ID_ISSUER_TEMPLATE % (self.insurance_company, self.insurance_number)
    525                  }
    526                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    527
    528                  # weed out duplicates
    529                  new_idents = []
    530                  for r in rows:
    531                          for oid in old_idents:
    532                                  if r[0] == oid.ID:
    533                                          break
    534                          new_idents.append(gmPerson.cPerson(aPK_obj = r['pk_identity']))
    535
    536                  old_idents.extend(new_idents)
    537
    538                  return old_idents
    539          #--------------------------------------------------------
    540 -        def import_extra_data(self, identity=None, *args, **kwargs):
    541                  # Versicherungsnummer
    542                  identity.add_external_id (
    543                          type_name = EXTERNAL_ID_TYPE_VK_INSUREE_NUMBER,
    544                          value = self.insuree_number,
    545                          issuer = EXTERNAL_ID_ISSUER_TEMPLATE % (self.insurance_company, self.insurance_number),
    546                          comment = 'Nummer des Versicherten bei der Krankenkasse'
    547                  )
    548                  # address
    549                  street = self.street
    550                  number = regex.findall(' \d+.*', street)
    551                  if len(number) == 0:
    552                          number = None
    553                  else:
    554                          street = street.replace(number[0], '')
    555                          number = number[0].strip()
    556                  identity.link_address (
    557                          number = number,
    558                          street = street,
    559                          postcode = self.zip,
    560                          urb = self.urb,
    561                          region_code = '',
    562                          country_code = 'DE'             # actually: map urb_region_code
    563                  )
    564                  # FIXME: kvk itself
    565          #--------------------------------------------------------
    566 -        def delete_from_source(self):
    567                  try:
    568                          os.remove(self.filename)
    569                          self.filename = None
    570                  except:
    571                          _log.exception('cannot delete kvkd file [%s]' % self.filename, verbose = False)
    572          #--------------------------------------------------------
    573          # internal helpers
    574          #--------------------------------------------------------
    575 -        def __parse_kvk_file(self, strict=True):
    576
    577                  _log.debug('parsing KVK data in [%s]', self.filename)
    578
    579                  kvk_file = io.open(self.filename, mode = 'rt', encoding = 'utf8')
    580
    581                  card_type_seen = False
    582                  for line in kvk_file:
    583                          line = line.replace('\n', '').replace('\r', '')
    584                          tag, content = line.split(':', 1)
    585                          content = content.strip()
    586
    587                          if tag == 'Kartentyp':
    588                                  card_type_seen = True
    589                                  if content != cDTO_KVK.kvkd_card_id_string:
    590                                          _log.error('parsing wrong card type')
    591                                          _log.error('found   : %s', content)
    592                                          _log.error('expected: %s', cDTO_KVK.kvkd_card_id_string)
    593                                  if strict:
    594                                          raise ValueError('wrong card type: %s, expected %s', content, cDTO_KVK.kvkd_card_id_string)
    595                                  else:
    596                                          _log.debug('trying to parse anyway')
    597
    598                          if tag == 'Geburtsdatum':
    599                                  tmp = time.strptime(content, self.dob_format)
    600                                  content = pyDT.datetime(tmp.tm_year, tmp.tm_mon, tmp.tm_mday, tzinfo = gmDateTime.gmCurrentLocalTimezone)
    601
    602                          try:
    603                                  setattr(self, map_kvkd_tags2dto[tag], content)
    604                          except KeyError:
    605                                  _log.exception('unknown KVKd kvk file key [%s]' % tag)
    606
    607                  # valid_until -> valid_until_timestamp
    608                  ts = time.strptime (
    609                          '28%s20%s' % (self.valid_until[:2], self.valid_until[2:]),
    610                          self.valid_until_format
    611                  )
    612
    613                  # last_read_date and last_read_time -> last_read_timestamp
    614                  ts = time.strptime (
    615                          '%s %s' % (self.last_read_date, self.last_read_time),
    616                          '%s %s' % (self.last_read_date_format, self.last_read_time_format)
    617                  )
    618                  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)
    619
    620                  # guess gender from firstname
    621                  self.gender = gmTools.coalesce(gmPerson.map_firstnames2gender(firstnames=self.firstnames), 'f')
    622
    623                  if not card_type_seen:
    624                          _log.warning('no line with card type found, unable to verify')
    625  #============================================================
    626 -def detect_card_type(card_file=None):
    627
    628          data_file = io.open(card_file, mode = 'rt', encoding = 'utf8')
    629
    630          for line in kvk_file:
    631                  line = line.replace('\n', '').replace('\r', '')
    632                  tag, content = line.split(':', 1)
    633                  content = content.strip()
    634
    635                  if tag == 'Kartentyp':
    636                          pass
    637  #============================================================
    638 -def get_available_kvks_as_dtos(spool_dir = None):
    639
    640          kvk_files = glob.glob(os.path.join(spool_dir, 'KVK-*.dat'))
    641          dtos = []
    642          for kvk_file in kvk_files:
    643                  try:
    644                          dto = cDTO_KVK(filename = kvk_file)
    645                  except:
    646                          _log.exception('probably not a KVKd KVK file: [%s]' % kvk_file)
    647                          continue
    648                  dtos.append(dto)
    649
    650          return dtos
    651  #------------------------------------------------------------
    652 -def get_available_egks_as_dtos(spool_dir = None):
    653
    654          egk_files = glob.glob(os.path.join(spool_dir, 'eGK-*.dat'))
    655          dtos = []
    656          for egk_file in egk_files:
    657                  try:
    658                          dto = cDTO_eGK(filename = egk_file)
    659                  except:
    660                          _log.exception('probably not a KVKd eGK file: [%s]' % egk_file)
    661                          continue
    662                  dtos.append(dto)
    663
    664          return dtos
    665  #------------------------------------------------------------
    666 -def get_available_CCRdr_files_as_dtos(spool_dir = None):
    667
    668          ccrdr_files = glob.glob(os.path.join(spool_dir, 'CCReader-*.dat'))
    669          dtos = []
    670          for ccrdr_file in ccrdr_files:
    671                  try:
    672                          dto = cDTO_CCRdr(filename = ccrdr_file)
    673                  except:
    674                          _log.exception('probably not a CCReader file: [%s]' % ccrdr_file)
    675                          continue
    676                  dtos.append(dto)
    677
    678          return dtos
    679  #------------------------------------------------------------
    680 -def get_available_cards_as_dtos(spool_dir = None):
    681          dtos = []
    682  #       dtos.extend(get_available_CCRdr_files_as_dtos(spool_dir = spool_dir))
    683          dtos.extend(get_available_kvks_as_dtos(spool_dir = spool_dir))
    684          dtos.extend(get_available_egks_as_dtos(spool_dir = spool_dir))
    685
    686          return dtos
    687
    688  #============================================================
    689  # main
    690  #------------------------------------------------------------
    691  if __name__ == "__main__":
    692
    693          from Gnumed.pycommon import gmI18N
    694
    695          gmI18N.activate_locale()
    696          gmDateTime.init()
    697
    698 -        def test_vks():
    699                  dtos = get_available_CCRdr_files_as_dtos(spool_dir = sys.argv[2])
    700                  for dto in dtos:
    701                          print(dto)
    702
    703 -        def test_egk_dto():
    704                  # test cKVKd_file object
    705                  kvkd_file = sys.argv[2]
    706                  print("reading eGK data from KVKd file", kvkd_file)
    707                  dto = cDTO_eGK(filename = kvkd_file, strict = False)
    708                  print(dto)
    709                  for attr in true_egk_fields:
    710                          print(getattr(dto, attr))
    711
    712 -        def test_kvk_dto():
    713                  # test cKVKd_file object
    714                  kvkd_file = sys.argv[2]
    715                  print("reading KVK data from KVKd file", kvkd_file)
    716                  dto = cDTO_KVK(filename = kvkd_file, strict = False)
    717                  print(dto)
    718                  for attr in true_kvk_fields:
    719                          print(getattr(dto, attr))
    720
    721 -        def test_get_available_kvks_as_dto():
    722                  dtos = get_available_kvks_as_dtos(spool_dir = sys.argv[2])
    723                  for dto in dtos:
    724                          print(dto)
    725
    726          if (len(sys.argv)) > 1 and (sys.argv[1] == 'test'):
    727                  if len(sys.argv) < 3:
    728                          print("give name of KVKd file as first argument")
    729                          sys.exit(-1)
    730                  test_vks()
    731                  #test_egk_dto()
    732                  #test_kvk_dto()
    733                  #test_get_available_kvks_as_dto()
    734
    735  #============================================================
    736  # docs
    737  #------------------------------------------------------------
    738  #       name       | mandat | type | length | format
    739  #       --------------------------------------------
    740  #       Name Kasse |  x     | str  | 2-28
    741  #       Nr. Kasse  |  x     | int  | 7
    742  #          VKNR    |        | int  | 5                                          # MUST be derived from Stammdaten-file, not from KVK
    743  #       Nr. Pat    |  x     | int  | 6-12
    744  #       Status Pat |  x     | str  | 1 or 4
    745  #       Statuserg. |        | str  | 1-3
    746  #       Titel Pat  |        | str  | 3-15
    747  #       Vorname    |        | str  | 2-28
    748  #       Adelspraed.|        | str  | 1-15
    749  #       Nachname   |  x     | str  | 2-28
    750  #       geboren    |  x     | int  | 8      | DDMMYYYY
    751  #       Straße     |        | str  | 1-28
    752  #       Ländercode |        | str  | 1-3
    753  #       PLZ        |  x     | int  | 4-7
    754  #       Ort        |  x     | str  | 2-23
    755  #       gültig bis |        | int  | 4      | MMYY
    756