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

Source Code for Module Gnumed.business.gmPersonSearch

  1  # -*- coding: utf8 -*- 
  2  """GNUmed person searching code.""" 
  3  #============================================================ 
  4  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  5  __license__ = "GPL" 
  6   
  7  # std lib 
  8  import sys, logging, re as regex 
  9   
 10   
 11  # GNUmed 
 12  if __name__ == '__main__': 
 13          sys.path.insert(0, '../../') 
 14  from Gnumed.pycommon import gmPG2, gmI18N, gmTools, gmDateTime 
 15  from Gnumed.business import gmPerson 
 16   
 17   
 18  _log = logging.getLogger('gm.person') 
 19  #============================================================ 
20 -class cPatientSearcher_SQL:
21 """UI independant i18n aware patient searcher."""
22 - def __init__(self):
23 self._generate_queries = self._generate_queries_de 24 # make a cursor 25 self.conn = gmPG2.get_connection() 26 self.curs = self.conn.cursor()
27 #--------------------------------------------------------
28 - def __del__(self):
29 try: 30 self.curs.close() 31 except: pass 32 try: 33 self.conn.close() 34 except: pass
35 #-------------------------------------------------------- 36 # public API 37 #--------------------------------------------------------
38 - def get_patients(self, search_term = None, a_locale = None, dto = None):
39 identities = self.get_identities(search_term, a_locale, dto) 40 if identities is None: 41 return None 42 return [ gmPerson.cPatient(aPK_obj=ident['pk_identity']) for ident in identities ]
43 #--------------------------------------------------------
44 - def get_identities(self, search_term = None, a_locale = None, dto = None):
45 """Get patient identity objects for given parameters. 46 47 - either search term or search dict 48 - dto contains structured data that doesn't need to be parsed (cDTO_person) 49 - dto takes precedence over search_term 50 """ 51 parse_search_term = (dto is None) 52 53 if not parse_search_term: 54 queries = self._generate_queries_from_dto(dto) 55 if queries is None: 56 parse_search_term = True 57 if len(queries) == 0: 58 parse_search_term = True 59 60 if parse_search_term: 61 # temporary change of locale for selecting query generator 62 if a_locale is not None: 63 print "temporary change of locale on patient search not implemented" 64 _log.warning("temporary change of locale on patient search not implemented") 65 # generate queries 66 if search_term is None: 67 raise ValueError('need search term (dto AND search_term are None)') 68 69 queries = self._generate_queries(search_term) 70 71 # anything to do ? 72 if len(queries) == 0: 73 _log.error('query tree empty') 74 _log.error('[%s] [%s] [%s]' % (search_term, a_locale, str(dto))) 75 return None 76 77 # collect IDs here 78 identities = [] 79 # cycle through query list 80 for query in queries: 81 _log.debug("running %s" % query) 82 try: 83 rows, idx = gmPG2.run_ro_queries(queries = [query], get_col_idx=True) 84 except: 85 _log.exception('error running query') 86 continue 87 if len(rows) == 0: 88 continue 89 identities.extend ( 90 [ gmPerson.cIdentity(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ] 91 ) 92 93 pks = [] 94 unique_identities = [] 95 for identity in identities: 96 if identity['pk_identity'] in pks: 97 continue 98 pks.append(identity['pk_identity']) 99 unique_identities.append(identity) 100 101 return unique_identities
102 #-------------------------------------------------------- 103 # internal helpers 104 #--------------------------------------------------------
105 - def _normalize_soundalikes(self, aString = None, aggressive = False):
106 """Transform some characters into a regex.""" 107 if aString.strip() == u'': 108 return aString 109 110 # umlauts 111 normalized = aString.replace(u'Ä', u'(Ä|AE|Ae|A|E)') 112 normalized = normalized.replace(u'Ö', u'(Ö|OE|Oe|O)') 113 normalized = normalized.replace(u'Ü', u'(Ü|UE|Ue|U)') 114 normalized = normalized.replace(u'ä', u'(ä|ae|e|a)') 115 normalized = normalized.replace(u'ö', u'(ö|oe|o)') 116 normalized = normalized.replace(u'ü', u'(ü|ue|u|y)') 117 normalized = normalized.replace(u'ß', u'(ß|sz|ss|s)') 118 119 # common soundalikes 120 # - René, Desiré, Inés ... 121 normalized = normalized.replace(u'é', u'***DUMMY***') 122 normalized = normalized.replace(u'è', u'***DUMMY***') 123 normalized = normalized.replace(u'***DUMMY***', u'(é|e|è|ä|ae)') 124 125 # FIXME: missing i/a/o - but uncommon in German 126 normalized = normalized.replace(u'v', u'***DUMMY***') 127 normalized = normalized.replace(u'f', u'***DUMMY***') 128 normalized = normalized.replace(u'ph', u'***DUMMY***') # now, this is *really* specific for German 129 normalized = normalized.replace(u'***DUMMY***', u'(v|f|ph)') 130 131 # silent characters (Thomas vs Tomas) 132 normalized = normalized.replace(u'Th',u'***DUMMY***') 133 normalized = normalized.replace(u'T', u'***DUMMY***') 134 normalized = normalized.replace(u'***DUMMY***', u'(Th|T)') 135 normalized = normalized.replace(u'th', u'***DUMMY***') 136 normalized = normalized.replace(u't', u'***DUMMY***') 137 normalized = normalized.replace(u'***DUMMY***', u'(th|t)') 138 139 # apostrophes, hyphens et al 140 normalized = normalized.replace(u'"', u'***DUMMY***') 141 normalized = normalized.replace(u"'", u'***DUMMY***') 142 normalized = normalized.replace(u'`', u'***DUMMY***') 143 normalized = normalized.replace(u'***DUMMY***', u"""("|'|`|***DUMMY***|\s)*""") 144 normalized = normalized.replace(u'-', u"""(-|\s)*""") 145 normalized = normalized.replace(u'|***DUMMY***|', u'|-|') 146 147 if aggressive: 148 pass 149 # some more here 150 151 _log.debug('[%s] -> [%s]' % (aString, normalized)) 152 153 return normalized
154 #-------------------------------------------------------- 155 # write your own query generator and add it here: 156 # use compile() for speedup 157 # must escape strings before use !! 158 # ORDER BY ! 159 # FIXME: what about "< 40" ? 160 #--------------------------------------------------------
161 - def _generate_simple_query(self, raw):
162 """Compose queries if search term seems unambigous.""" 163 queries = [] 164 165 # "<digits>" - GNUmed patient PK or DOB 166 if regex.match(u"^(\s|\t)*\d+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE): 167 _log.debug("[%s]: a PK or DOB" % raw) 168 tmp = raw.strip() 169 queries.append ({ 170 'cmd': u"select *, %s::text as match_type FROM dem.v_basic_person WHERE pk_identity = %s order by lastnames, firstnames, dob", 171 'args': [_('internal patient ID'), tmp] 172 }) 173 queries.append ({ 174 'cmd': u"SELECT *, %s::text as match_type FROM dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob", 175 'args': [_('date of birth'), tmp.replace(',', '.')] 176 }) 177 queries.append ({ 178 'cmd': u""" 179 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 180 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 181 order by lastnames, firstnames, dob""", 182 'args': [_('external patient ID'), tmp] 183 }) 184 return queries 185 186 # "<d igi ts>" - DOB or patient PK 187 if regex.match(u"^(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE): 188 _log.debug("[%s]: a DOB or PK" % raw) 189 queries.append ({ 190 'cmd': u"SELECT *, %s::text as match_type FROM dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob", 191 'args': [_('date of birth'), raw.replace(',', '.')] 192 }) 193 tmp = raw.replace(u' ', u'') 194 tmp = tmp.replace(u'\t', u'') 195 queries.append ({ 196 'cmd': u"SELECT *, %s::text as match_type from dem.v_basic_person WHERE pk_identity LIKE %s%%", 197 'args': [_('internal patient ID'), tmp] 198 }) 199 return queries 200 201 # "#<di git s>" - GNUmed patient PK 202 if regex.match(u"^(\s|\t)*#(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE): 203 _log.debug("[%s]: a PK or external ID" % raw) 204 tmp = raw.replace(u'#', u'') 205 tmp = tmp.strip() 206 tmp = tmp.replace(u' ', u'') 207 tmp = tmp.replace(u'\t', u'') 208 # this seemingly stupid query ensures the PK actually exists 209 queries.append ({ 210 'cmd': u"SELECT *, %s::text as match_type from dem.v_basic_person WHERE pk_identity = %s order by lastnames, firstnames, dob", 211 'args': [_('internal patient ID'), tmp] 212 }) 213 # but might also be an external ID 214 tmp = raw.replace(u'#', u'') 215 tmp = tmp.strip() 216 tmp = tmp.replace(u' ', u'***DUMMY***') 217 tmp = tmp.replace(u'\t', u'***DUMMY***') 218 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*') 219 queries.append ({ 220 'cmd': u""" 221 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 222 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 223 order by lastnames, firstnames, dob""", 224 'args': [_('external patient ID'), tmp] 225 }) 226 return queries 227 228 # "#<di/git s or c-hars>" - external ID (or PUPIC) 229 if regex.match(u"^(\s|\t)*#.+$", raw, flags = regex.LOCALE | regex.UNICODE): 230 _log.debug("[%s]: an external ID" % raw) 231 tmp = raw.replace(u'#', u'') 232 tmp = tmp.strip() 233 tmp = tmp.replace(u' ', u'***DUMMY***') 234 tmp = tmp.replace(u'\t', u'***DUMMY***') 235 tmp = tmp.replace(u'-', u'***DUMMY***') 236 tmp = tmp.replace(u'/', u'***DUMMY***') 237 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*') 238 queries.append ({ 239 'cmd': u""" 240 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 241 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 242 order by lastnames, firstnames, dob""", 243 'args': [_('external patient ID'), tmp] 244 }) 245 return queries 246 247 # digits interspersed with "./-" or blank space - DOB 248 if regex.match(u"^(\s|\t)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.)*$", raw, flags = regex.LOCALE | regex.UNICODE): 249 _log.debug("[%s]: a DOB" % raw) 250 tmp = raw.strip() 251 while u'\t\t' in tmp: tmp = tmp.replace(u'\t\t', u' ') 252 while u' ' in tmp: tmp = tmp.replace(u' ', u' ') 253 # apparently not needed due to PostgreSQL smarts... 254 #tmp = tmp.replace('-', '.') 255 #tmp = tmp.replace('/', '.') 256 queries.append ({ 257 'cmd': u"SELECT *, %s as match_type from dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob", 258 'args': [_('date of birth'), tmp.replace(',', '.')] 259 }) 260 return queries 261 262 # " , <alpha>" - first name 263 if regex.match(u"^(\s|\t)*,(\s|\t)*([^0-9])+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE): 264 _log.debug("[%s]: a firstname" % raw) 265 tmp = self._normalize_soundalikes(raw[1:].strip()) 266 cmd = u""" 267 SELECT DISTINCT ON (pk_identity) * from ( 268 select *, %s as match_type from (( 269 select vbp.* 270 FROM dem.names, dem.v_basic_person vbp 271 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity 272 ) union all ( 273 select vbp.* 274 FROM dem.names, dem.v_basic_person vbp 275 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity 276 )) as super_list order by lastnames, firstnames, dob 277 ) as sorted_list""" 278 queries.append ({ 279 'cmd': cmd, 280 'args': [_('first name'), '^' + gmTools.capitalize(tmp, mode=gmTools.CAPS_NAMES), '^' + tmp] 281 }) 282 return queries 283 284 # "*|$<...>" - DOB 285 if regex.match(u"^(\s|\t)*(\*|\$).+$", raw, flags = regex.LOCALE | regex.UNICODE): 286 _log.debug("[%s]: a DOB" % raw) 287 tmp = raw.replace(u'*', u'') 288 tmp = tmp.replace(u'$', u'') 289 queries.append ({ 290 'cmd': u"SELECT *, %s as match_type from dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob", 291 'args': [_('date of birth'), tmp.replace(u',', u'.')] 292 }) 293 return queries 294 295 return queries # = []
296 #-------------------------------------------------------- 297 # generic, locale independant queries 298 #--------------------------------------------------------
299 - def _generate_queries_from_dto(self, dto = None):
300 """Generate generic queries. 301 302 - not locale dependant 303 - data -> firstnames, lastnames, dob, gender 304 """ 305 _log.debug(u'_generate_queries_from_dto("%s")' % dto) 306 307 if not isinstance(dto, gmPerson.cDTO_person): 308 return None 309 310 vals = [_('name, gender, date of birth')] 311 where_snippets = [] 312 313 vals.append(dto.firstnames) 314 where_snippets.append(u'firstnames=%s') 315 vals.append(dto.lastnames) 316 where_snippets.append(u'lastnames=%s') 317 318 if dto.dob is not None: 319 vals.append(dto.dob) 320 #where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)") 321 where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s)") 322 323 if dto.gender is not None: 324 vals.append(dto.gender) 325 where_snippets.append('gender=%s') 326 327 # sufficient data ? 328 if len(where_snippets) == 0: 329 _log.error('invalid search dict structure') 330 _log.debug(data) 331 return None 332 333 cmd = u""" 334 select *, %%s as match_type from dem.v_basic_person 335 where pk_identity in ( 336 select id_identity from dem.names where %s 337 ) order by lastnames, firstnames, dob""" % ' and '.join(where_snippets) 338 339 queries = [ 340 {'cmd': cmd, 'args': vals} 341 ] 342 343 # shall we mogrify name parts ? probably not 344 345 return queries
346 #-------------------------------------------------------- 347 # queries for DE 348 #--------------------------------------------------------
349 - def _generate_queries_de(self, search_term = None):
350 351 if search_term is None: 352 return [] 353 354 # check to see if we get away with a simple query ... 355 queries = self._generate_simple_query(search_term) 356 if len(queries) > 0: 357 return queries 358 359 _log.debug('[%s]: not a search term with a "suggestive" structure' % search_term) 360 361 # no we don't 362 queries = [] 363 364 # replace Umlauts 365 normalized = self._normalize_soundalikes(search_term) 366 367 # "<CHARS>" - single name part 368 # yes, I know, this is culture specific (did you read the docs ?) 369 if regex.match(u"^(\s|\t)*[a-zäöüßéáúóçøA-ZÄÖÜÇØ]+(\s|\t)*$", search_term, flags = regex.LOCALE | regex.UNICODE): 370 # there's no intermediate whitespace due to the regex 371 cmd = u""" 372 SELECT DISTINCT ON (pk_identity) * from ( 373 select * from (( 374 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.lastnames) ~* lower(%s) 375 ) union all ( 376 -- first name 377 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) 378 ) union all ( 379 -- anywhere in name 380 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames || coalesce(n.preferred, '')) ~* lower(%s) 381 )) as super_list order by lastnames, firstnames, dob 382 ) as sorted_list""" 383 tmp = normalized.strip() 384 args = [] 385 args.append(_('last name')) 386 args.append('^' + tmp) 387 args.append(_('first name')) 388 args.append('^' + tmp) 389 args.append(_('any name part')) 390 args.append(tmp) 391 392 queries.append ({ 393 'cmd': cmd, 394 'args': args 395 }) 396 return queries 397 398 # try to split on (major) part separators 399 parts_list = regex.split(u",|;", normalized) 400 401 # only one "major" part ? (i.e. no ",;" ?) 402 if len(parts_list) == 1: 403 # re-split on whitespace 404 sub_parts_list = regex.split(u"\s*|\t*", normalized) 405 406 # parse into name/date parts 407 date_count = 0 408 name_parts = [] 409 for part in sub_parts_list: 410 # any digit signifies a date 411 # FIXME: what about "<40" ? 412 if regex.search(u"\d", part, flags = regex.LOCALE | regex.UNICODE): 413 date_count = date_count + 1 414 date_part = part 415 else: 416 name_parts.append(part) 417 418 # exactly 2 words ? 419 if len(sub_parts_list) == 2: 420 # no date = "first last" or "last first" 421 if date_count == 0: 422 # assumption: first last 423 queries.append ({ 424 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s", 425 'args': [_('name: first-last'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES)] 426 }) 427 queries.append ({ 428 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)", 429 'args': [_('name: first-last'), '^' + name_parts[0], '^' + name_parts[1]] 430 }) 431 # assumption: last first 432 queries.append ({ 433 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s", 434 'args': [_('name: last-first'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES)] 435 }) 436 queries.append ({ 437 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)", 438 'args': [_('name: last-first'), '^' + name_parts[1], '^' + name_parts[0]] 439 }) 440 # name parts anywhere in name - third order query ... 441 queries.append ({ 442 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames) ~* lower(%s) AND lower(n.firstnames || n.lastnames) ~* lower(%s)", 443 'args': [_('name'), name_parts[0], name_parts[1]] 444 }) 445 return queries 446 # FIXME: either "name date" or "date date" 447 _log.error("don't know how to generate queries for [%s]" % search_term) 448 return queries 449 450 # exactly 3 words ? 451 if len(sub_parts_list) == 3: 452 # special case: 3 words, exactly 1 of them a date, no ",;" 453 if date_count == 1: 454 # assumption: first, last, dob - first order 455 queries.append ({ 456 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 457 'args': [_('names: first-last, date of birth'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), date_part.replace(u',', u'.')] 458 }) 459 queries.append ({ 460 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 461 'args': [_('names: first-last, date of birth'), '^' + name_parts[0], '^' + name_parts[1], date_part.replace(u',', u'.')] 462 }) 463 # assumption: last, first, dob - second order query 464 queries.append ({ 465 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 466 'args': [_('names: last-first, date of birth'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), date_part.replace(u',', u'.')] 467 }) 468 queries.append ({ 469 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 470 'args': [_('names: last-first, dob'), '^' + name_parts[1], '^' + name_parts[0], date_part.replace(u',', u'.')] 471 }) 472 # name parts anywhere in name - third order query ... 473 queries.append ({ 474 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames) ~* lower(%s) AND lower(n.firstnames || n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 475 'args': [_('name, date of birth'), name_parts[0], name_parts[1], date_part.replace(u',', u'.')] 476 }) 477 return queries 478 # FIXME: "name name name" or "name date date" 479 queries.append(self._generate_dumb_brute_query(search_term)) 480 return queries 481 482 # FIXME: no ',;' but neither "name name" nor "name name date" 483 queries.append(self._generate_dumb_brute_query(search_term)) 484 return queries 485 486 # more than one major part (separated by ';,') 487 else: 488 # parse into name and date parts 489 date_parts = [] 490 name_parts = [] 491 name_count = 0 492 for part in parts_list: 493 # any digits ? 494 if regex.search(u"\d+", part, flags = regex.LOCALE | regex.UNICODE): 495 # FIXME: parse out whitespace *not* adjacent to a *word* 496 date_parts.append(part) 497 else: 498 tmp = part.strip() 499 tmp = regex.split(u"\s*|\t*", tmp) 500 name_count = name_count + len(tmp) 501 name_parts.append(tmp) 502 503 where_parts = [] 504 # first, handle name parts 505 # special case: "<date(s)>, <name> <name>, <date(s)>" 506 if (len(name_parts) == 1) and (name_count == 2): 507 # usually "first last" 508 where_parts.append ({ 509 'conditions': u"firstnames ~ %s and lastnames ~ %s", 510 'args': [_('names: first last'), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES)] 511 }) 512 where_parts.append ({ 513 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)", 514 'args': [_('names: first last'), '^' + name_parts[0][0], '^' + name_parts[0][1]] 515 }) 516 # but sometimes "last first"" 517 where_parts.append ({ 518 'conditions': u"firstnames ~ %s and lastnames ~ %s", 519 'args': [_('names: last, first'), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES)] 520 }) 521 where_parts.append ({ 522 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)", 523 'args': [_('names: last, first'), '^' + name_parts[0][1], '^' + name_parts[0][0]] 524 }) 525 # or even substrings anywhere in name 526 where_parts.append ({ 527 'conditions': u"lower(firstnames || lastnames) ~* lower(%s) OR lower(firstnames || lastnames) ~* lower(%s)", 528 'args': [_('name'), name_parts[0][0], name_parts[0][1]] 529 }) 530 531 # special case: "<date(s)>, <name(s)>, <name(s)>, <date(s)>" 532 elif len(name_parts) == 2: 533 # usually "last, first" 534 where_parts.append ({ 535 'conditions': u"firstnames ~ %s AND lastnames ~ %s", 536 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[1])), '^' + ' '.join(map(gmTools.capitalize, name_parts[0]))] 537 }) 538 where_parts.append ({ 539 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)", 540 'args': [_('name: last, first'), '^' + ' '.join(name_parts[1]), '^' + ' '.join(name_parts[0])] 541 }) 542 # but sometimes "first, last" 543 where_parts.append ({ 544 'conditions': u"firstnames ~ %s AND lastnames ~ %s", 545 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[0])), '^' + ' '.join(map(gmTools.capitalize, name_parts[1]))] 546 }) 547 where_parts.append ({ 548 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)", 549 'args': [_('name: last, first'), '^' + ' '.join(name_parts[0]), '^' + ' '.join(name_parts[1])] 550 }) 551 # or even substrings anywhere in name 552 where_parts.append ({ 553 'conditions': u"lower(firstnames || lastnames) ~* lower(%s) AND lower(firstnames || lastnames) ~* lower(%s)", 554 'args': [_('name'), ' '.join(name_parts[0]), ' '.join(name_parts[1])] 555 }) 556 557 # big trouble - arbitrary number of names 558 else: 559 # FIXME: deep magic, not sure of rationale ... 560 if len(name_parts) == 1: 561 for part in name_parts[0]: 562 where_parts.append ({ 563 'conditions': u"lower(firstnames || lastnames) ~* lower(%s)", 564 'args': [_('name'), part] 565 }) 566 else: 567 tmp = [] 568 for part in name_parts: 569 tmp.append(' '.join(part)) 570 for part in tmp: 571 where_parts.append ({ 572 'conditions': u"lower(firstnames || lastnames) ~* lower(%s)", 573 'args': [_('name'), part] 574 }) 575 576 # secondly handle date parts 577 # FIXME: this needs a considerable smart-up ! 578 if len(date_parts) == 1: 579 if len(where_parts) == 0: 580 where_parts.append ({ 581 'conditions': u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 582 'args': [_('date of birth'), date_parts[0].replace(u',', u'.')] 583 }) 584 if len(where_parts) > 0: 585 where_parts[0]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)" 586 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.')) 587 where_parts[0]['args'][0] += u', ' + _('date of birth') 588 if len(where_parts) > 1: 589 where_parts[1]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)" 590 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.')) 591 where_parts[1]['args'][0] += u', ' + _('date of birth') 592 elif len(date_parts) > 1: 593 if len(where_parts) == 0: 594 where_parts.append ({ 595 'conditions': u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp witih time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 596 'args': [_('date of birth/death'), date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')] 597 }) 598 if len(where_parts) > 0: 599 where_parts[0]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 600 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')) 601 where_parts[0]['args'][0] += u', ' + _('date of birth/death') 602 if len(where_parts) > 1: 603 where_parts[1]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 604 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')) 605 where_parts[1]['args'][0] += u', ' + _('date of birth/death') 606 607 # and finally generate the queries ... 608 for where_part in where_parts: 609 queries.append ({ 610 'cmd': u"select *, %%s::text as match_type from dem.v_basic_person where %s" % where_part['conditions'], 611 'args': where_part['args'] 612 }) 613 return queries 614 615 return []
616 #--------------------------------------------------------
617 - def _generate_dumb_brute_query(self, search_term=''):
618 619 _log.debug('_generate_dumb_brute_query("%s")' % search_term) 620 621 where_clause = '' 622 args = [] 623 # FIXME: split on more than just ' ' 624 for arg in search_term.strip().split(): 625 where_clause += u' and lower(vbp.title || vbp.firstnames || vbp.lastnames) ~* lower(%s)' 626 args.append(arg) 627 628 query = u""" 629 select distinct on (pk_identity) * from ( 630 select 631 vbp.*, '%s'::text as match_type 632 from 633 dem.v_basic_person vbp, 634 dem.names n 635 where 636 vbp.pk_identity = n.id_identity 637 %s 638 order by 639 lastnames, 640 firstnames, 641 dob 642 ) as ordered_list""" % (_('full name'), where_clause) 643 644 return ({'cmd': query, 'args': args})
645 #============================================================
646 -def ask_for_patient():
647 """Text mode UI function to ask for patient.""" 648 649 person_searcher = cPatientSearcher_SQL() 650 651 while True: 652 search_fragment = gmTools.prompted_input(prompt = "\nEnter person search term or leave blank to exit") 653 654 if search_fragment in ['exit', 'quit', 'bye', None]: 655 print "user cancelled patient search" 656 return None 657 658 pats = person_searcher.get_patients(search_term = search_fragment) 659 660 if (pats is None) or (len(pats) == 0): 661 print "No patient matches the query term." 662 print "" 663 continue 664 665 if len(pats) > 1: 666 print "Several patients match the query term:" 667 print "" 668 for pat in pats: 669 print pat 670 print "" 671 continue 672 673 return pats[0] 674 675 return None
676 #============================================================ 677 # main/testing 678 #============================================================ 679 if __name__ == '__main__': 680 681 if len(sys.argv) == 1: 682 sys.exit() 683 684 if sys.argv[1] != 'test': 685 sys.exit() 686 687 import datetime 688 689 gmI18N.activate_locale() 690 gmI18N.install_domain() 691 gmDateTime.init() 692 693 #--------------------------------------------------------
694 - def test_search_by_dto():
695 dto = gmPerson.cDTO_person() 696 dto.firstnames = 'Sigrid' 697 dto.lastnames = 'Kiesewetter' 698 dto.gender = 'female' 699 # dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 700 dto.dob = datetime.datetime(1939,6,24,23,0,0,0,gmDateTime.gmCurrentLocalTimezone) 701 print dto 702 703 searcher = cPatientSearcher_SQL() 704 pats = searcher.get_patients(dto = dto) 705 print pats
706 #--------------------------------------------------------
707 - def test_patient_search_queries():
708 searcher = cPatientSearcher_SQL() 709 710 print "testing _normalize_soundalikes()" 711 print "--------------------------------" 712 # FIXME: support Ähler -> Äler and Dähler -> Däler 713 data = [u'Krüger', u'Krueger', u'Kruger', u'Überle', u'Böger', u'Boger', u'Öder', u'Ähler', u'Däler', u'Großer', u'müller', u'Özdemir', u'özdemir'] 714 for name in data: 715 print '%s: %s' % (name, searcher._normalize_soundalikes(name)) 716 717 raw_input('press [ENTER] to continue') 718 print "============" 719 720 print "testing _generate_simple_query()" 721 print "----------------------------" 722 data = ['51234', '1 134 153', '#13 41 34', '#3-AFY322.4', '22-04-1906', '1235/32/3525', ' , johnny'] 723 for fragment in data: 724 print "fragment:", fragment 725 qs = searcher._generate_simple_query(fragment) 726 for q in qs: 727 print " match on:", q['args'][0] 728 print " query :", q['cmd'] 729 raw_input('press [ENTER] to continue') 730 print "============" 731 732 print "testing _generate_queries_from_dto()" 733 print "------------------------------------" 734 dto = cDTO_person() 735 dto.gender = 'm' 736 dto.lastnames = 'Kirk' 737 dto.firstnames = 'James' 738 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 739 q = searcher._generate_queries_from_dto(dto)[0] 740 print "dto:", dto 741 print " match on:", q['args'][0] 742 print " query:", q['cmd'] 743 744 raw_input('press [ENTER] to continue') 745 print "============" 746 747 print "testing _generate_queries_de()" 748 print "------------------------------" 749 qs = searcher._generate_queries_de('Kirk, James') 750 for q in qs: 751 print " match on:", q['args'][0] 752 print " query :", q['cmd'] 753 print " args :", q['args'] 754 raw_input('press [ENTER] to continue') 755 print "============" 756 757 qs = searcher._generate_queries_de(u'müller') 758 for q in qs: 759 print " match on:", q['args'][0] 760 print " query :", q['cmd'] 761 print " args :", q['args'] 762 raw_input('press [ENTER] to continue') 763 print "============" 764 765 qs = searcher._generate_queries_de(u'özdemir') 766 for q in qs: 767 print " match on:", q['args'][0] 768 print " query :", q['cmd'] 769 print " args :", q['args'] 770 raw_input('press [ENTER] to continue') 771 print "============" 772 773 qs = searcher._generate_queries_de(u'Özdemir') 774 for q in qs: 775 print " match on:", q['args'][0] 776 print " query :", q['cmd'] 777 print " args :", q['args'] 778 raw_input('press [ENTER] to continue') 779 print "============" 780 781 print "testing _generate_dumb_brute_query()" 782 print "------------------------------------" 783 q = searcher._generate_dumb_brute_query('Kirk, James Tiberius') 784 print " match on:", q['args'][0] 785 print " query:", q['cmd'] 786 print " args:", q['args'] 787 788 raw_input('press [ENTER] to continue')
789 #--------------------------------------------------------
790 - def test_ask_for_patient():
791 while 1: 792 myPatient = ask_for_patient() 793 if myPatient is None: 794 break 795 print "ID ", myPatient.ID 796 print "names ", myPatient.get_names() 797 print "addresses:", myPatient.get_addresses(address_type='home') 798 print "recent birthday:", myPatient.dob_in_range() 799 myPatient.export_as_gdt(filename='apw.gdt', encoding = 'cp850')
800 # docs = myPatient.get_document_folder() 801 # print "docs ", docs 802 # emr = myPatient.get_emr() 803 # print "EMR ", emr 804 #-------------------------------------------------------- 805 #test_patient_search_queries() 806 #test_ask_for_patient() 807 test_search_by_dto() 808 809 #============================================================ 810