Gnumed.business.gmKVK

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