1
2 """GNUmed person searching code."""
3
4 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL"
6
7
8 import sys, logging, re as regex
9
10
11
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
22 """UI independant i18n aware patient searcher."""
24 self._generate_queries = self._generate_queries_de
25
26 self.conn = gmPG2.get_connection()
27 self.curs = self.conn.cursor()
28
30 try:
31 self.curs.close()
32 except: pass
33 try:
34 self.conn.close()
35 except: pass
36
37
38
39 - def get_patients(self, search_term = None, a_locale = None, dto = None):
40 identities = self.get_identities(search_term, a_locale, dto)
41 if identities is None:
42 return None
43 return [ gmPerson.cPatient(aPK_obj=ident['pk_identity']) for ident in identities ]
44
45 - def get_identities(self, search_term = None, a_locale = None, dto = None):
46 """Get patient identity objects for given parameters.
47
48 - either search term or search dict
49 - dto contains structured data that doesn't need to be parsed (cDTO_person)
50 - dto takes precedence over search_term
51 """
52 parse_search_term = (dto is None)
53
54 if not parse_search_term:
55 queries = self._generate_queries_from_dto(dto)
56 if queries is None:
57 parse_search_term = True
58 if len(queries) == 0:
59 parse_search_term = True
60
61 if parse_search_term:
62
63 if a_locale is not None:
64 print("temporary change of locale on patient search not implemented")
65 _log.warning("temporary change of locale on patient search not implemented")
66
67 if search_term is None:
68 raise ValueError('need search term (dto AND search_term are None)')
69
70 queries = self._generate_queries(search_term)
71
72
73 if len(queries) == 0:
74 _log.error('query tree empty')
75 _log.error('[%s] [%s] [%s]' % (search_term, a_locale, str(dto)))
76 return None
77
78
79 identities = []
80
81 for query in queries:
82 _log.debug("running %s" % query)
83 try:
84 rows, idx = gmPG2.run_ro_queries(queries = [query], get_col_idx=True)
85 except:
86 _log.exception('error running query')
87 continue
88 if len(rows) == 0:
89 continue
90 identities.extend (
91 [ gmPerson.cPerson(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ]
92 )
93
94 pks = []
95 unique_identities = []
96 for identity in identities:
97 if identity['pk_identity'] in pks:
98 continue
99 pks.append(identity['pk_identity'])
100 unique_identities.append(identity)
101
102 return unique_identities
103
104
105
107 """Transform some characters into a regex."""
108 if aString.strip() == '':
109 return aString
110
111
112 normalized = aString.replace('Ä', '(Ä|AE|Ae|A|E)')
113 normalized = normalized.replace('Ö', '(Ö|OE|Oe|O)')
114 normalized = normalized.replace('Ü', '(Ü|UE|Ue|U)')
115 normalized = normalized.replace('ä', '(ä|ae|e|a)')
116 normalized = normalized.replace('ö', '(ö|oe|o)')
117 normalized = normalized.replace('ü', '(ü|ue|u|y)')
118 normalized = normalized.replace('ß', '(ß|sz|ss|s)')
119
120
121
122 normalized = normalized.replace('é', '***DUMMY***')
123 normalized = normalized.replace('è', '***DUMMY***')
124 normalized = normalized.replace('***DUMMY***', '(é|e|è|ä|ae)')
125
126
127 normalized = normalized.replace('v', '***DUMMY***')
128 normalized = normalized.replace('f', '***DUMMY***')
129 normalized = normalized.replace('ph', '***DUMMY***')
130 normalized = normalized.replace('***DUMMY***', '(v|f|ph)')
131
132
133 normalized = normalized.replace('Th','***DUMMY***')
134 normalized = normalized.replace('T', '***DUMMY***')
135 normalized = normalized.replace('***DUMMY***', '(Th|T)')
136 normalized = normalized.replace('th', '***DUMMY***')
137 normalized = normalized.replace('t', '***DUMMY***')
138 normalized = normalized.replace('***DUMMY***', '(th|t)')
139
140
141 normalized = normalized.replace('"', '***DUMMY***')
142 normalized = normalized.replace("'", '***DUMMY***')
143 normalized = normalized.replace('`', '***DUMMY***')
144 normalized = normalized.replace('***DUMMY***', """("|'|`|***DUMMY***|\s)*""")
145 normalized = normalized.replace('-', """(-|\s)*""")
146 normalized = normalized.replace('|***DUMMY***|', '|-|')
147
148 if aggressive:
149 pass
150
151
152 _log.debug('[%s] -> [%s]' % (aString, normalized))
153
154 return normalized
155
156
157
158
159
160
161
163 """Compose queries if search term seems unambigous."""
164 queries = []
165
166 raw = raw.strip().rstrip(',').rstrip(';').strip()
167
168
169 if regex.match("^(\s|\t)*\d+(\s|\t)*$", raw, flags = regex.UNICODE):
170 _log.debug("[%s]: a PK or DOB" % raw)
171 tmp = raw.strip()
172 queries.append ({
173 'cmd': "SELECT *, %s::text AS match_type FROM dem.v_active_persons WHERE pk_identity = %s ORDER BY lastnames, firstnames, dob",
174 'args': [_('internal patient ID'), tmp]
175 })
176 if len(tmp) > 7:
177 queries.append ({
178 'cmd': "SELECT *, %s::text AS match_type FROM dem.v_active_persons WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) ORDER BY lastnames, firstnames, dob",
179 'args': [_('date of birth'), tmp.replace(',', '.')]
180 })
181 queries.append ({
182 'cmd': """
183 SELECT vba.*, %s::text AS match_type
184 FROM
185 dem.lnk_identity2ext_id li2ext_id,
186 dem.v_active_persons vba
187 WHERE
188 vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s)
189 ORDER BY
190 lastnames, firstnames, dob
191 """,
192 'args': [_('external patient ID'), tmp]
193 })
194 return queries
195
196
197 if regex.match("^(\d|\s|\t)+$", raw, flags = regex.UNICODE):
198 _log.debug("[%s]: a DOB or PK" % raw)
199 queries.append ({
200 'cmd': "SELECT *, %s::text AS match_type FROM dem.v_active_persons WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) ORDER BY lastnames, firstnames, dob",
201 'args': [_('date of birth'), raw.replace(',', '.')]
202 })
203 tmp = raw.replace(' ', '')
204 tmp = tmp.replace('\t', '')
205 queries.append ({
206 'cmd': "SELECT *, %s::text AS match_type FROM dem.v_active_persons WHERE pk_identity LIKE %s%%",
207 'args': [_('internal patient ID'), tmp]
208 })
209 return queries
210
211
212 if regex.match("^(\s|\t)*#(\d|\s|\t)+$", raw, flags = regex.UNICODE):
213 _log.debug("[%s]: a PK or external ID" % raw)
214 tmp = raw.replace('#', '')
215 tmp = tmp.strip()
216 tmp = tmp.replace(' ', '')
217 tmp = tmp.replace('\t', '')
218
219 queries.append ({
220 'cmd': "SELECT *, %s::text AS match_type FROM dem.v_active_persons WHERE pk_identity = %s ORDER BY lastnames, firstnames, dob",
221 'args': [_('internal patient ID'), tmp]
222 })
223
224 tmp = raw.replace('#', '')
225 tmp = tmp.strip()
226 tmp = tmp.replace(' ', '***DUMMY***')
227 tmp = tmp.replace('\t', '***DUMMY***')
228 tmp = tmp.replace('***DUMMY***', '(\s|\t|-|/)*')
229 queries.append ({
230 'cmd': """
231 SELECT vba.*, %s::text AS match_type FROM dem.lnk_identity2ext_id li2ext_id, dem.v_active_persons vba
232 WHERE vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s)
233 ORDER BY lastnames, firstnames, dob""",
234 'args': [_('external patient ID'), tmp]
235 })
236 return queries
237
238
239 if regex.match("^(\s|\t)*#.+$", raw, flags = regex.UNICODE):
240 _log.debug("[%s]: an external ID" % raw)
241 tmp = raw.replace('#', '')
242 tmp = tmp.strip()
243 tmp = tmp.replace(' ', '***DUMMY***')
244 tmp = tmp.replace('\t', '***DUMMY***')
245 tmp = tmp.replace('-', '***DUMMY***')
246 tmp = tmp.replace('/', '***DUMMY***')
247 tmp = tmp.replace('***DUMMY***', '(\s|\t|-|/)*')
248 queries.append ({
249 'cmd': """
250 SELECT
251 vba.*,
252 %s::text AS match_type
253 FROM
254 dem.lnk_identity2ext_id li2ext_id,
255 dem.v_active_persons vba
256 WHERE
257 vba.pk_identity = li2ext_id.id_identity
258 AND
259 lower(li2ext_id.external_id) ~* lower(%s)
260 ORDER BY
261 lastnames, firstnames, dob""",
262 'args': [_('external patient ID'), tmp]
263 })
264 return queries
265
266
267 if regex.match("^(\s|\t)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.)*$", raw, flags = regex.UNICODE):
268 _log.debug("[%s]: a DOB" % raw)
269 tmp = raw.strip()
270 while '\t\t' in tmp: tmp = tmp.replace('\t\t', ' ')
271 while ' ' in tmp: tmp = tmp.replace(' ', ' ')
272
273
274
275 queries.append ({
276 'cmd': "SELECT *, %s AS match_type FROM dem.v_active_persons WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) ORDER BY lastnames, firstnames, dob",
277 'args': [_('date of birth'), tmp.replace(',', '.')]
278 })
279 return queries
280
281
282 if regex.match("^(\s|\t)*,(\s|\t)*([^0-9])+(\s|\t)*$", raw, flags = regex.UNICODE):
283 _log.debug("[%s]: a firstname" % raw)
284 tmp = self._normalize_soundalikes(raw[1:].strip())
285 cmd = """
286 SELECT DISTINCT ON (pk_identity) * FROM (
287 SELECT *, %s AS match_type FROM ((
288 SELECT d_vap.*
289 FROM dem.names, dem.v_active_persons d_vap
290 WHERE dem.names.firstnames ~ %s and d_vap.pk_identity = dem.names.id_identity
291 ) union all (
292 SELECT d_vap.*
293 FROM dem.names, dem.v_active_persons d_vap
294 WHERE dem.names.firstnames ~ %s and d_vap.pk_identity = dem.names.id_identity
295 )) AS super_list ORDER BY lastnames, firstnames, dob
296 ) AS sorted_list"""
297 queries.append ({
298 'cmd': cmd,
299 'args': [_('first name'), '^' + gmTools.capitalize(tmp, mode=gmTools.CAPS_NAMES), '^' + tmp]
300 })
301 return queries
302
303
304 if regex.match("^(\s|\t)*(\*|\$).+$", raw, flags = regex.UNICODE):
305 _log.debug("[%s]: a DOB" % raw)
306 tmp = raw.replace('*', '')
307 tmp = tmp.replace('$', '')
308 queries.append ({
309 'cmd': "SELECT *, %s AS match_type FROM dem.v_active_persons WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) ORDER BY lastnames, firstnames, dob",
310 'args': [_('date of birth'), tmp.replace(',', '.')]
311 })
312 return queries
313
314 return queries
315
316
317
319 """Generate generic queries.
320
321 - not locale dependant
322 - data -> firstnames, lastnames, dob, gender
323 """
324 _log.debug('_generate_queries_from_dto("%s")' % dto)
325
326 if not isinstance(dto, gmPerson.cDTO_person):
327 return None
328
329 vals = [_('name, gender, date of birth')]
330 where_snippets = []
331
332 vals.append(dto.firstnames)
333 where_snippets.append('firstnames=%s')
334 vals.append(dto.lastnames)
335 where_snippets.append('lastnames=%s')
336
337 if dto.dob is not None:
338 vals.append(dto.dob)
339
340 where_snippets.append("dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s)")
341
342 if dto.gender is not None:
343 vals.append(dto.gender)
344 where_snippets.append('gender=%s')
345
346
347 if len(where_snippets) == 0:
348 _log.error('invalid search dict structure')
349 _log.debug(data)
350 return None
351
352 cmd = """
353 SELECT *, %%s AS match_type FROM dem.v_active_persons
354 WHERE pk_identity in (
355 SELECT id_identity FROM dem.names WHERE %s
356 ) ORDER BY lastnames, firstnames, dob""" % ' and '.join(where_snippets)
357
358 queries = [
359 {'cmd': cmd, 'args': vals}
360 ]
361
362
363
364 return queries
365
366
367
369
370 if search_term is None:
371 return []
372
373
374 queries = self._generate_simple_query(search_term)
375 if len(queries) > 0:
376 _log.debug('[%s]: search term with a simple, unambigous structure' % search_term)
377 return queries
378
379
380 _log.debug('[%s]: not a search term with a simple, unambigous structure' % search_term)
381
382 search_term = search_term.strip().strip(',').strip(';').strip()
383 normalized = self._normalize_soundalikes(search_term)
384
385 queries = []
386
387
388
389 if regex.match("^(\s|\t)*[a-zäöüßéáúóçøA-ZÄÖÜÇØ]+(\s|\t)*$", search_term, flags = regex.UNICODE):
390 _log.debug("[%s]: a single name part", search_term)
391
392 cmd = """
393 SELECT DISTINCT ON (pk_identity) * FROM (
394 SELECT * FROM ((
395 -- lastname
396 SELECT d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n
397 WHERE d_vap.pk_identity = n.id_identity and lower(n.lastnames) ~* lower(%s)
398 ) union all (
399 -- firstname
400 SELECT d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n
401 WHERE d_vap.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s)
402 ) union all (
403 -- nickname
404 SELECT d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n
405 WHERE d_vap.pk_identity = n.id_identity and lower(n.preferred) ~* lower(%s)
406 ) union all (
407 -- anywhere in name
408 SELECT
409 d_vap.*,
410 %s::text AS match_type
411 FROM
412 dem.v_active_persons d_vap,
413 dem.names n
414 WHERE
415 d_vap.pk_identity = n.id_identity
416 AND
417 lower(n.firstnames || ' ' || n.lastnames || ' ' || coalesce(n.preferred, '')) ~* lower(%s)
418 )) AS super_list ORDER BY lastnames, firstnames, dob
419 ) AS sorted_list
420 """
421 tmp = normalized.strip()
422 args = []
423 args.append(_('lastname'))
424 args.append('^' + tmp)
425 args.append(_('firstname'))
426 args.append('^' + tmp)
427 args.append(_('nickname'))
428 args.append('^' + tmp)
429 args.append(_('any name part'))
430 args.append(tmp)
431
432 queries.append ({
433 'cmd': cmd,
434 'args': args
435 })
436 return queries
437
438
439 parts_list = regex.split(",|;", normalized)
440
441
442 parts_list = [ p.strip() for p in parts_list if p.strip() != '' ]
443
444
445 if len(parts_list) == 1:
446
447 sub_parts_list = regex.split("\s*|\t*", normalized)
448
449 sub_parts_list = [ p.strip() for p in sub_parts_list if p.strip() != '' ]
450
451
452 date_count = 0
453 name_parts = []
454 for part in sub_parts_list:
455
456 if part.strip() == '':
457 continue
458
459
460 if regex.search("\d", part, flags = regex.UNICODE):
461 date_count = date_count + 1
462 date_part = part
463 else:
464 name_parts.append(part)
465
466
467 if len(sub_parts_list) == 2:
468
469 if date_count == 0:
470
471 queries.append ({
472 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s",
473 'args': [_('name: first-last'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES)]
474 })
475 queries.append ({
476 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)",
477 'args': [_('name: first-last'), '^' + name_parts[0], '^' + name_parts[1]]
478 })
479
480 queries.append ({
481 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s",
482 'args': [_('name: last-first'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES)]
483 })
484 queries.append ({
485 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)",
486 'args': [_('name: last-first'), '^' + name_parts[1], '^' + name_parts[0]]
487 })
488
489 queries.append ({
490 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.pk_identity = n.id_identity and n.preferred ~ %s AND n.lastnames ~ %s",
491 'args': [_('name: last-nick'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES)]
492 })
493 queries.append ({
494 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.pk_identity = n.id_identity and lower(n.preferred) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)",
495 'args': [_('name: last-nick'), '^' + name_parts[1], '^' + name_parts[0]]
496 })
497
498 queries.append ({
499 'cmd': """SELECT DISTINCT ON (id_identity)
500 d_vap.*,
501 %s::text AS match_type
502 FROM
503 dem.v_active_persons d_vap,
504 dem.names n
505 WHERE
506 d_vap.pk_identity = n.id_identity
507 AND
508 -- name_parts[0]
509 lower(n.firstnames || ' ' || n.lastnames) ~* lower(%s)
510 AND
511 -- name_parts[1]
512 lower(n.firstnames || ' ' || n.lastnames) ~* lower(%s)""",
513 'args': [_('name'), name_parts[0], name_parts[1]]
514 })
515 return queries
516
517 _log.error("don't know how to generate queries for [%s]" % search_term)
518 return queries
519
520
521 if len(sub_parts_list) == 3:
522
523 if date_count == 1:
524
525 queries.append ({
526 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.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)",
527 '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(',', '.')]
528 })
529 queries.append ({
530 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.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)",
531 'args': [_('names: first-last, date of birth'), '^' + name_parts[0], '^' + name_parts[1], date_part.replace(',', '.')]
532 })
533
534 queries.append ({
535 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.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)",
536 '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(',', '.')]
537 })
538 queries.append ({
539 'cmd': "SELECT DISTINCT ON (id_identity) d_vap.*, %s::text AS match_type FROM dem.v_active_persons d_vap, dem.names n WHERE d_vap.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)",
540 'args': [_('names: last-first, dob'), '^' + name_parts[1], '^' + name_parts[0], date_part.replace(',', '.')]
541 })
542
543 queries.append ({
544 'cmd': """SELECT DISTINCT ON (id_identity)
545 d_vap.*,
546 %s::text AS match_type
547 FROM
548 dem.v_active_persons d_vap,
549 dem.names n
550 WHERE
551 d_vap.pk_identity = n.id_identity
552 AND
553 lower(n.firstnames || ' ' || n.lastnames) ~* lower(%s)
554 AND
555 lower(n.firstnames || ' ' || n.lastnames) ~* lower(%s)
556 AND
557 dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)
558 """,
559 'args': [_('name, date of birth'), name_parts[0], name_parts[1], date_part.replace(',', '.')]
560 })
561 return queries
562
563 queries.append(self._generate_dumb_brute_query(search_term))
564 return queries
565
566
567 queries.append(self._generate_dumb_brute_query(search_term))
568 return queries
569
570
571 else:
572
573 date_parts = []
574 name_parts = []
575 name_count = 0
576 for part in parts_list:
577 if part.strip() == '':
578 continue
579
580 if regex.search("\d+", part, flags = regex.UNICODE):
581
582 date_parts.append(part)
583 else:
584 tmp = part.strip()
585 tmp = regex.split("\s*|\t*", tmp)
586 name_count = name_count + len(tmp)
587 name_parts.append(tmp)
588
589 where_parts = []
590
591
592 if (len(name_parts) == 1) and (name_count == 2):
593
594 where_parts.append ({
595 'conditions': "firstnames ~ %s and lastnames ~ %s",
596 'args': [_('names: first last'), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES)]
597 })
598 where_parts.append ({
599 'conditions': "lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)",
600 'args': [_('names: first last'), '^' + name_parts[0][0], '^' + name_parts[0][1]]
601 })
602
603 where_parts.append ({
604 'conditions': "firstnames ~ %s and lastnames ~ %s",
605 'args': [_('names: last, first'), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES)]
606 })
607 where_parts.append ({
608 'conditions': "lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)",
609 'args': [_('names: last, first'), '^' + name_parts[0][1], '^' + name_parts[0][0]]
610 })
611
612 where_parts.append ({
613 'conditions': "lower(firstnames || ' ' || lastnames) ~* lower(%s) OR lower(firstnames || ' ' || lastnames) ~* lower(%s)",
614 'args': [_('name'), name_parts[0][0], name_parts[0][1]]
615 })
616
617
618 elif len(name_parts) == 2:
619
620 where_parts.append ({
621 'conditions': "firstnames ~ %s AND lastnames ~ %s",
622 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[1])), '^' + ' '.join(map(gmTools.capitalize, name_parts[0]))]
623 })
624 where_parts.append ({
625 'conditions': "lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)",
626 'args': [_('name: last, first'), '^' + ' '.join(name_parts[1]), '^' + ' '.join(name_parts[0])]
627 })
628
629 where_parts.append ({
630 'conditions': "firstnames ~ %s AND lastnames ~ %s",
631 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[0])), '^' + ' '.join(map(gmTools.capitalize, name_parts[1]))]
632 })
633 where_parts.append ({
634 'conditions': "lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)",
635 'args': [_('name: last, first'), '^' + ' '.join(name_parts[0]), '^' + ' '.join(name_parts[1])]
636 })
637
638 where_parts.append ({
639 'conditions': "preferred ~ %s AND lastnames ~ %s",
640 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[1])), '^' + ' '.join(map(gmTools.capitalize, name_parts[0]))]
641 })
642 where_parts.append ({
643 'conditions': "lower(preferred) ~* lower(%s) AND lower(lastnames) ~* lower(%s)",
644 'args': [_('name: last, first'), '^' + ' '.join(name_parts[1]), '^' + ' '.join(name_parts[0])]
645 })
646
647
648 where_parts.append ({
649 'conditions': "lower(firstnames || ' ' || lastnames) ~* lower(%s) AND lower(firstnames || ' ' || lastnames) ~* lower(%s)",
650 'args': [_('name'), ' '.join(name_parts[0]), ' '.join(name_parts[1])]
651 })
652
653
654 else:
655
656 if len(name_parts) == 1:
657 for part in name_parts[0]:
658 where_parts.append ({
659 'conditions': "lower(firstnames || ' ' || lastnames) ~* lower(%s)",
660 'args': [_('name'), part]
661 })
662 else:
663 tmp = []
664 for part in name_parts:
665 tmp.append(' '.join(part))
666 for part in tmp:
667 where_parts.append ({
668 'conditions': "lower(firstnames || ' ' || lastnames) ~* lower(%s)",
669 'args': [_('name'), part]
670 })
671
672
673
674 if len(date_parts) == 1:
675 if len(where_parts) == 0:
676 where_parts.append ({
677 'conditions': "dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
678 'args': [_('date of birth'), date_parts[0].replace(',', '.')]
679 })
680 if len(where_parts) > 0:
681 where_parts[0]['conditions'] += " AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)"
682 where_parts[0]['args'].append(date_parts[0].replace(',', '.'))
683 where_parts[0]['args'][0] += ', ' + _('date of birth')
684 if len(where_parts) > 1:
685 where_parts[1]['conditions'] += " AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)"
686 where_parts[1]['args'].append(date_parts[0].replace(',', '.'))
687 where_parts[1]['args'][0] += ', ' + _('date of birth')
688 elif len(date_parts) > 1:
689 if len(where_parts) == 0:
690 where_parts.append ({
691 'conditions': "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)",
692 'args': [_('date of birth/death'), date_parts[0].replace(',', '.'), date_parts[1].replace(',', '.')]
693 })
694 if len(where_parts) > 0:
695 where_parts[0]['conditions'] += " 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)",
696 where_parts[0]['args'].append(date_parts[0].replace(',', '.'), date_parts[1].replace(',', '.'))
697 where_parts[0]['args'][0] += ', ' + _('date of birth/death')
698 if len(where_parts) > 1:
699 where_parts[1]['conditions'] += " 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)",
700 where_parts[1]['args'].append(date_parts[0].replace(',', '.'), date_parts[1].replace(',', '.'))
701 where_parts[1]['args'][0] += ', ' + _('date of birth/death')
702
703
704 for where_part in where_parts:
705 queries.append ({
706 'cmd': "SELECT *, %%s::text AS match_type FROM dem.v_active_persons WHERE %s" % where_part['conditions'],
707 'args': where_part['args']
708 })
709 return queries
710
711 return []
712
714
715 _log.debug('_generate_dumb_brute_query("%s")' % search_term)
716
717 where_clause = ''
718 args = []
719
720 for arg in search_term.strip().split():
721 where_clause += " AND lower(coalesce(d_vap.title, '') || ' ' || d_vap.firstnames || ' ' || d_vap.lastnames) ~* lower(%s)"
722 args.append(arg)
723
724 query = """
725 SELECT DISTINCT ON (pk_identity) * FROM (
726 SELECT
727 d_vap.*,
728 '%s'::text AS match_type
729 FROM
730 dem.v_active_persons d_vap,
731 dem.names n
732 WHERE
733 d_vap.pk_identity = n.id_identity
734 %s
735 ORDER BY
736 lastnames,
737 firstnames,
738 dob
739 ) AS ordered_list""" % (_('full name'), where_clause)
740
741 return ({'cmd': query, 'args': args})
742
743
745 """Text mode UI function to ask for patient."""
746
747 person_searcher = cPatientSearcher_SQL()
748
749 while True:
750 search_fragment = gmTools.prompted_input(prompt = "\nEnter person search term or leave blank to exit")
751
752 if search_fragment in ['exit', 'quit', 'bye', None]:
753 print("user cancelled patient search")
754 return None
755
756 pats = person_searcher.get_patients(search_term = search_fragment)
757
758 if (pats is None) or (len(pats) == 0):
759 print("No patient matches the search term.")
760 print("")
761 continue
762
763 if len(pats) > 1:
764 print("Several patients match the search term:")
765 print("")
766 for pat in pats:
767 print(pat)
768 print("")
769 print("Please refine the search term so it matches one patient only.")
770 continue
771
772 return pats[0]
773
774 return None
775
776
777
778
779 if __name__ == '__main__':
780
781 if len(sys.argv) == 1:
782 sys.exit()
783
784 if sys.argv[1] != 'test':
785 sys.exit()
786
787 import datetime
788
789 gmI18N.activate_locale()
790 gmI18N.install_domain()
791 gmDateTime.init()
792
793
806
808 searcher = cPatientSearcher_SQL()
809
810 print("testing _normalize_soundalikes()")
811 print("--------------------------------")
812
813 data = ['Krüger', 'Krueger', 'Kruger', 'Überle', 'Böger', 'Boger', 'Öder', 'Ähler', 'Däler', 'Großer', 'müller', 'Özdemir', 'özdemir']
814 for name in data:
815 print('%s: %s' % (name, searcher._normalize_soundalikes(name)))
816
817 input('press [ENTER] to continue')
818 print("============")
819
820 print("testing _generate_simple_query()")
821 print("----------------------------")
822 data = ['51234', '1 134 153', '#13 41 34', '#3-AFY322.4', '22-04-1906', '1235/32/3525', ' , johnny']
823 for fragment in data:
824 print("fragment:", fragment)
825 qs = searcher._generate_simple_query(fragment)
826 for q in qs:
827 print(" match on:", q['args'][0])
828 print(" query :", q['cmd'])
829 input('press [ENTER] to continue')
830 print("============")
831
832 print("testing _generate_queries_from_dto()")
833 print("------------------------------------")
834 dto = cDTO_person()
835 dto.gender = 'm'
836 dto.lastnames = 'Kirk'
837 dto.firstnames = 'James'
838 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
839 q = searcher._generate_queries_from_dto(dto)[0]
840 print("dto:", dto)
841 print(" match on:", q['args'][0])
842 print(" query:", q['cmd'])
843
844 input('press [ENTER] to continue')
845 print("============")
846
847 print("testing _generate_queries_de()")
848 print("------------------------------")
849 qs = searcher._generate_queries_de('Kirk, James')
850 for q in qs:
851 print(" match on:", q['args'][0])
852 print(" query :", q['cmd'])
853 print(" args :", q['args'])
854 input('press [ENTER] to continue')
855 print("============")
856
857 qs = searcher._generate_queries_de('müller')
858 for q in qs:
859 print(" match on:", q['args'][0])
860 print(" query :", q['cmd'])
861 print(" args :", q['args'])
862 input('press [ENTER] to continue')
863 print("============")
864
865 qs = searcher._generate_queries_de('özdemir')
866 for q in qs:
867 print(" match on:", q['args'][0])
868 print(" query :", q['cmd'])
869 print(" args :", q['args'])
870 input('press [ENTER] to continue')
871 print("============")
872
873 qs = searcher._generate_queries_de('Özdemir')
874 for q in qs:
875 print(" match on:", q['args'][0])
876 print(" query :", q['cmd'])
877 print(" args :", q['args'])
878 input('press [ENTER] to continue')
879 print("============")
880
881 print("testing _generate_dumb_brute_query()")
882 print("------------------------------------")
883 q = searcher._generate_dumb_brute_query('Kirk, James Tiberius')
884 print(" match on:", q['args'][0])
885 print(" query:", q['cmd'])
886 print(" args:", q['args'])
887
888 input('press [ENTER] to continue')
889
900
901
902
903
904
905
906
907 test_search_by_dto()
908
909
910