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
21 """UI independant i18n aware patient searcher."""
23 self._generate_queries = self._generate_queries_de
24
25 self.conn = gmPG2.get_connection()
26 self.curs = self.conn.cursor()
27
29 try:
30 self.curs.close()
31 except: pass
32 try:
33 self.conn.close()
34 except: pass
35
36
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
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
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
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
78 identities = []
79
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
104
106 """Transform some characters into a regex."""
107 if aString.strip() == u'':
108 return aString
109
110
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
120
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
126 normalized = normalized.replace(u'v', u'***DUMMY***')
127 normalized = normalized.replace(u'f', u'***DUMMY***')
128 normalized = normalized.replace(u'ph', u'***DUMMY***')
129 normalized = normalized.replace(u'***DUMMY***', u'(v|f|ph)')
130
131
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
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
150
151 _log.debug('[%s] -> [%s]' % (aString, normalized))
152
153 return normalized
154
155
156
157
158
159
160
162 """Compose queries if search term seems unambigous."""
163 queries = []
164
165 raw = raw.strip().rstrip(u',').rstrip(u';').strip()
166
167
168 if regex.match(u"^(\s|\t)*\d+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE):
169 _log.debug("[%s]: a PK or DOB" % raw)
170 tmp = raw.strip()
171 queries.append ({
172 'cmd': u"SELECT *, %s::text AS match_type FROM dem.v_basic_person WHERE pk_identity = %s ORDER BY lastnames, firstnames, dob",
173 'args': [_('internal patient ID'), tmp]
174 })
175 if len(tmp) > 7:
176 queries.append ({
177 '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",
178 'args': [_('date of birth'), tmp.replace(',', '.')]
179 })
180 queries.append ({
181 'cmd': u"""
182 SELECT vba.*, %s::text AS match_type
183 FROM
184 dem.lnk_identity2ext_id li2ext_id,
185 dem.v_basic_person vba
186 WHERE
187 vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s)
188 ORDER BY
189 lastnames, firstnames, dob
190 """,
191 'args': [_('external patient ID'), tmp]
192 })
193 return queries
194
195
196 if regex.match(u"^(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE):
197 _log.debug("[%s]: a DOB or PK" % raw)
198 queries.append ({
199 '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",
200 'args': [_('date of birth'), raw.replace(',', '.')]
201 })
202 tmp = raw.replace(u' ', u'')
203 tmp = tmp.replace(u'\t', u'')
204 queries.append ({
205 'cmd': u"SELECT *, %s::text AS match_type FROM dem.v_basic_person WHERE pk_identity LIKE %s%%",
206 'args': [_('internal patient ID'), tmp]
207 })
208 return queries
209
210
211 if regex.match(u"^(\s|\t)*#(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE):
212 _log.debug("[%s]: a PK or external ID" % raw)
213 tmp = raw.replace(u'#', u'')
214 tmp = tmp.strip()
215 tmp = tmp.replace(u' ', u'')
216 tmp = tmp.replace(u'\t', u'')
217
218 queries.append ({
219 'cmd': u"SELECT *, %s::text AS match_type FROM dem.v_basic_person WHERE pk_identity = %s ORDER BY lastnames, firstnames, dob",
220 'args': [_('internal patient ID'), tmp]
221 })
222
223 tmp = raw.replace(u'#', u'')
224 tmp = tmp.strip()
225 tmp = tmp.replace(u' ', u'***DUMMY***')
226 tmp = tmp.replace(u'\t', u'***DUMMY***')
227 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*')
228 queries.append ({
229 'cmd': u"""
230 SELECT vba.*, %s::text AS match_type FROM dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba
231 WHERE vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s)
232 ORDER BY lastnames, firstnames, dob""",
233 'args': [_('external patient ID'), tmp]
234 })
235 return queries
236
237
238 if regex.match(u"^(\s|\t)*#.+$", raw, flags = regex.LOCALE | regex.UNICODE):
239 _log.debug("[%s]: an external ID" % raw)
240 tmp = raw.replace(u'#', u'')
241 tmp = tmp.strip()
242 tmp = tmp.replace(u' ', u'***DUMMY***')
243 tmp = tmp.replace(u'\t', u'***DUMMY***')
244 tmp = tmp.replace(u'-', u'***DUMMY***')
245 tmp = tmp.replace(u'/', u'***DUMMY***')
246 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*')
247 queries.append ({
248 'cmd': u"""
249 SELECT
250 vba.*,
251 %s::text AS match_type
252 FROM
253 dem.lnk_identity2ext_id li2ext_id,
254 dem.v_basic_person vba
255 WHERE
256 vba.pk_identity = li2ext_id.id_identity
257 AND
258 lower(li2ext_id.external_id) ~* lower(%s)
259 ORDER BY
260 lastnames, firstnames, dob""",
261 'args': [_('external patient ID'), tmp]
262 })
263 return queries
264
265
266 if regex.match(u"^(\s|\t)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.)*$", raw, flags = regex.LOCALE | regex.UNICODE):
267 _log.debug("[%s]: a DOB" % raw)
268 tmp = raw.strip()
269 while u'\t\t' in tmp: tmp = tmp.replace(u'\t\t', u' ')
270 while u' ' in tmp: tmp = tmp.replace(u' ', u' ')
271
272
273
274 queries.append ({
275 '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",
276 'args': [_('date of birth'), tmp.replace(',', '.')]
277 })
278 return queries
279
280
281 if regex.match(u"^(\s|\t)*,(\s|\t)*([^0-9])+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE):
282 _log.debug("[%s]: a firstname" % raw)
283 tmp = self._normalize_soundalikes(raw[1:].strip())
284 cmd = u"""
285 SELECT DISTINCT ON (pk_identity) * FROM (
286 SELECT *, %s AS match_type FROM ((
287 SELECT vbp.*
288 FROM dem.names, dem.v_basic_person vbp
289 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity
290 ) union all (
291 SELECT vbp.*
292 FROM dem.names, dem.v_basic_person vbp
293 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity
294 )) AS super_list ORDER BY lastnames, firstnames, dob
295 ) AS sorted_list"""
296 queries.append ({
297 'cmd': cmd,
298 'args': [_('first name'), '^' + gmTools.capitalize(tmp, mode=gmTools.CAPS_NAMES), '^' + tmp]
299 })
300 return queries
301
302
303 if regex.match(u"^(\s|\t)*(\*|\$).+$", raw, flags = regex.LOCALE | regex.UNICODE):
304 _log.debug("[%s]: a DOB" % raw)
305 tmp = raw.replace(u'*', u'')
306 tmp = tmp.replace(u'$', u'')
307 queries.append ({
308 '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",
309 'args': [_('date of birth'), tmp.replace(u',', u'.')]
310 })
311 return queries
312
313 return queries
314
315
316
318 """Generate generic queries.
319
320 - not locale dependant
321 - data -> firstnames, lastnames, dob, gender
322 """
323 _log.debug(u'_generate_queries_from_dto("%s")' % dto)
324
325 if not isinstance(dto, gmPerson.cDTO_person):
326 return None
327
328 vals = [_('name, gender, date of birth')]
329 where_snippets = []
330
331 vals.append(dto.firstnames)
332 where_snippets.append(u'firstnames=%s')
333 vals.append(dto.lastnames)
334 where_snippets.append(u'lastnames=%s')
335
336 if dto.dob is not None:
337 vals.append(dto.dob)
338
339 where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s)")
340
341 if dto.gender is not None:
342 vals.append(dto.gender)
343 where_snippets.append('gender=%s')
344
345
346 if len(where_snippets) == 0:
347 _log.error('invalid search dict structure')
348 _log.debug(data)
349 return None
350
351 cmd = u"""
352 SELECT *, %%s AS match_type FROM dem.v_basic_person
353 WHERE pk_identity in (
354 SELECT id_identity FROM dem.names WHERE %s
355 ) ORDER BY lastnames, firstnames, dob""" % ' and '.join(where_snippets)
356
357 queries = [
358 {'cmd': cmd, 'args': vals}
359 ]
360
361
362
363 return queries
364
365
366
368
369 if search_term is None:
370 return []
371
372
373 queries = self._generate_simple_query(search_term)
374 if len(queries) > 0:
375 _log.debug('[%s]: search term with a simple, unambigous structure' % search_term)
376 return queries
377
378
379 _log.debug('[%s]: not a search term with a simple, unambigous structure' % search_term)
380
381 search_term = search_term.strip().strip(u',').strip(u';').strip()
382 normalized = self._normalize_soundalikes(search_term)
383
384 queries = []
385
386
387
388 if regex.match(u"^(\s|\t)*[a-zäöüßéáúóçøA-ZÄÖÜÇØ]+(\s|\t)*$", search_term, flags = regex.LOCALE | regex.UNICODE):
389 _log.debug("[%s]: a single name part", search_term)
390
391 cmd = u"""
392 SELECT DISTINCT ON (pk_identity) * FROM (
393 SELECT * FROM ((
394 -- lastname
395 SELECT vbp.*, %s::text AS match_type FROM dem.v_basic_person vbp, dem.names n
396 WHERE vbp.pk_identity = n.id_identity and lower(n.lastnames) ~* lower(%s)
397 ) union all (
398 -- firstname
399 SELECT vbp.*, %s::text AS match_type FROM dem.v_basic_person vbp, dem.names n
400 WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s)
401 ) union all (
402 -- nickname
403 SELECT vbp.*, %s::text AS match_type FROM dem.v_basic_person vbp, dem.names n
404 WHERE vbp.pk_identity = n.id_identity and lower(n.preferred) ~* lower(%s)
405 ) union all (
406 -- anywhere in name
407 SELECT
408 vbp.*,
409 %s::text AS match_type
410 FROM
411 dem.v_basic_person vbp,
412 dem.names n
413 WHERE
414 vbp.pk_identity = n.id_identity
415 AND
416 lower(n.firstnames || ' ' || n.lastnames || ' ' || coalesce(n.preferred, '')) ~* lower(%s)
417 )) AS super_list ORDER BY lastnames, firstnames, dob
418 ) AS sorted_list
419 """
420 tmp = normalized.strip()
421 args = []
422 args.append(_('lastname'))
423 args.append('^' + tmp)
424 args.append(_('firstname'))
425 args.append('^' + tmp)
426 args.append(_('nickname'))
427 args.append('^' + tmp)
428 args.append(_('any name part'))
429 args.append(tmp)
430
431 queries.append ({
432 'cmd': cmd,
433 'args': args
434 })
435 return queries
436
437
438 parts_list = regex.split(u",|;", normalized)
439
440
441 parts_list = [ p.strip() for p in parts_list if p.strip() != u'' ]
442
443
444 if len(parts_list) == 1:
445
446 sub_parts_list = regex.split(u"\s*|\t*", normalized)
447
448 sub_parts_list = [ p.strip() for p in sub_parts_list if p.strip() != u'' ]
449
450
451 date_count = 0
452 name_parts = []
453 for part in sub_parts_list:
454
455 if part.strip() == u'':
456 continue
457
458
459 if regex.search(u"\d", part, flags = regex.LOCALE | regex.UNICODE):
460 date_count = date_count + 1
461 date_part = part
462 else:
463 name_parts.append(part)
464
465
466 if len(sub_parts_list) == 2:
467
468 if date_count == 0:
469
470 queries.append ({
471 '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",
472 'args': [_('name: first-last'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES)]
473 })
474 queries.append ({
475 '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)",
476 'args': [_('name: first-last'), '^' + name_parts[0], '^' + name_parts[1]]
477 })
478
479 queries.append ({
480 '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",
481 'args': [_('name: last-first'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES)]
482 })
483 queries.append ({
484 '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)",
485 'args': [_('name: last-first'), '^' + name_parts[1], '^' + name_parts[0]]
486 })
487 print "before nick"
488 print queries
489
490 queries.append ({
491 '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.preferred ~ %s AND n.lastnames ~ %s",
492 'args': [_('name: last-nick'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES)]
493 })
494 queries.append ({
495 '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.preferred) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)",
496 'args': [_('name: last-nick'), '^' + name_parts[1], '^' + name_parts[0]]
497 })
498 print "after nick"
499 print queries
500
501 queries.append ({
502 'cmd': u"""SELECT DISTINCT ON (id_identity)
503 vbp.*,
504 %s::text AS match_type
505 FROM
506 dem.v_basic_person vbp,
507 dem.names n
508 WHERE
509 vbp.pk_identity = n.id_identity
510 AND
511 -- name_parts[0]
512 lower(n.firstnames || ' ' || n.lastnames) ~* lower(%s)
513 AND
514 -- name_parts[1]
515 lower(n.firstnames || ' ' || n.lastnames) ~* lower(%s)""",
516 'args': [_('name'), name_parts[0], name_parts[1]]
517 })
518 return queries
519
520 _log.error("don't know how to generate queries for [%s]" % search_term)
521 return queries
522
523
524 if len(sub_parts_list) == 3:
525
526 if date_count == 1:
527
528 queries.append ({
529 '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)",
530 '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'.')]
531 })
532 queries.append ({
533 '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)",
534 'args': [_('names: first-last, date of birth'), '^' + name_parts[0], '^' + name_parts[1], date_part.replace(u',', u'.')]
535 })
536
537 queries.append ({
538 '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)",
539 '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'.')]
540 })
541 queries.append ({
542 '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)",
543 'args': [_('names: last-first, dob'), '^' + name_parts[1], '^' + name_parts[0], date_part.replace(u',', u'.')]
544 })
545
546 queries.append ({
547 'cmd': u"""SELECT DISTINCT ON (id_identity)
548 vbp.*,
549 %s::text AS match_type
550 FROM
551 dem.v_basic_person vbp,
552 dem.names n
553 WHERE
554 vbp.pk_identity = n.id_identity
555 AND
556 lower(n.firstnames || ' ' || n.lastnames) ~* lower(%s)
557 AND
558 lower(n.firstnames || ' ' || n.lastnames) ~* lower(%s)
559 AND
560 dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)
561 """,
562 'args': [_('name, date of birth'), name_parts[0], name_parts[1], date_part.replace(u',', u'.')]
563 })
564 return queries
565
566 queries.append(self._generate_dumb_brute_query(search_term))
567 return queries
568
569
570 queries.append(self._generate_dumb_brute_query(search_term))
571 return queries
572
573
574 else:
575
576 date_parts = []
577 name_parts = []
578 name_count = 0
579 for part in parts_list:
580 if part.strip() == u'':
581 continue
582
583 if regex.search(u"\d+", part, flags = regex.LOCALE | regex.UNICODE):
584
585 date_parts.append(part)
586 else:
587 tmp = part.strip()
588 tmp = regex.split(u"\s*|\t*", tmp)
589 name_count = name_count + len(tmp)
590 name_parts.append(tmp)
591
592 where_parts = []
593
594
595 if (len(name_parts) == 1) and (name_count == 2):
596
597 where_parts.append ({
598 'conditions': u"firstnames ~ %s and lastnames ~ %s",
599 'args': [_('names: first last'), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES)]
600 })
601 where_parts.append ({
602 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)",
603 'args': [_('names: first last'), '^' + name_parts[0][0], '^' + name_parts[0][1]]
604 })
605
606 where_parts.append ({
607 'conditions': u"firstnames ~ %s and lastnames ~ %s",
608 'args': [_('names: last, first'), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES)]
609 })
610 where_parts.append ({
611 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)",
612 'args': [_('names: last, first'), '^' + name_parts[0][1], '^' + name_parts[0][0]]
613 })
614
615 where_parts.append ({
616 'conditions': u"lower(firstnames || ' ' || lastnames) ~* lower(%s) OR lower(firstnames || ' ' || lastnames) ~* lower(%s)",
617 'args': [_('name'), name_parts[0][0], name_parts[0][1]]
618 })
619
620
621 elif len(name_parts) == 2:
622
623 where_parts.append ({
624 'conditions': u"firstnames ~ %s AND lastnames ~ %s",
625 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[1])), '^' + ' '.join(map(gmTools.capitalize, name_parts[0]))]
626 })
627 where_parts.append ({
628 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)",
629 'args': [_('name: last, first'), '^' + ' '.join(name_parts[1]), '^' + ' '.join(name_parts[0])]
630 })
631
632 where_parts.append ({
633 'conditions': u"firstnames ~ %s AND lastnames ~ %s",
634 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[0])), '^' + ' '.join(map(gmTools.capitalize, name_parts[1]))]
635 })
636 where_parts.append ({
637 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)",
638 'args': [_('name: last, first'), '^' + ' '.join(name_parts[0]), '^' + ' '.join(name_parts[1])]
639 })
640
641 where_parts.append ({
642 'conditions': u"preferred ~ %s AND lastnames ~ %s",
643 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[1])), '^' + ' '.join(map(gmTools.capitalize, name_parts[0]))]
644 })
645 where_parts.append ({
646 'conditions': u"lower(preferred) ~* lower(%s) AND lower(lastnames) ~* lower(%s)",
647 'args': [_('name: last, first'), '^' + ' '.join(name_parts[1]), '^' + ' '.join(name_parts[0])]
648 })
649
650
651 where_parts.append ({
652 'conditions': u"lower(firstnames || ' ' || lastnames) ~* lower(%s) AND lower(firstnames || ' ' || lastnames) ~* lower(%s)",
653 'args': [_('name'), ' '.join(name_parts[0]), ' '.join(name_parts[1])]
654 })
655
656
657 else:
658
659 if len(name_parts) == 1:
660 for part in name_parts[0]:
661 where_parts.append ({
662 'conditions': u"lower(firstnames || ' ' || lastnames) ~* lower(%s)",
663 'args': [_('name'), part]
664 })
665 else:
666 tmp = []
667 for part in name_parts:
668 tmp.append(' '.join(part))
669 for part in tmp:
670 where_parts.append ({
671 'conditions': u"lower(firstnames || ' ' || lastnames) ~* lower(%s)",
672 'args': [_('name'), part]
673 })
674
675
676
677 if len(date_parts) == 1:
678 if len(where_parts) == 0:
679 where_parts.append ({
680 'conditions': u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
681 'args': [_('date of birth'), date_parts[0].replace(u',', u'.')]
682 })
683 if len(where_parts) > 0:
684 where_parts[0]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)"
685 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.'))
686 where_parts[0]['args'][0] += u', ' + _('date of birth')
687 if len(where_parts) > 1:
688 where_parts[1]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)"
689 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.'))
690 where_parts[1]['args'][0] += u', ' + _('date of birth')
691 elif len(date_parts) > 1:
692 if len(where_parts) == 0:
693 where_parts.append ({
694 'conditions': u"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)",
695 'args': [_('date of birth/death'), date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')]
696 })
697 if len(where_parts) > 0:
698 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)",
699 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.'))
700 where_parts[0]['args'][0] += u', ' + _('date of birth/death')
701 if len(where_parts) > 1:
702 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)",
703 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.'))
704 where_parts[1]['args'][0] += u', ' + _('date of birth/death')
705
706
707 for where_part in where_parts:
708 queries.append ({
709 'cmd': u"SELECT *, %%s::text AS match_type FROM dem.v_basic_person WHERE %s" % where_part['conditions'],
710 'args': where_part['args']
711 })
712 return queries
713
714 return []
715
717
718 _log.debug('_generate_dumb_brute_query("%s")' % search_term)
719
720 where_clause = ''
721 args = []
722
723 for arg in search_term.strip().split():
724 where_clause += u" AND lower(coalesce(vbp.title, '') || ' ' || vbp.firstnames || ' ' || vbp.lastnames) ~* lower(%s)"
725 args.append(arg)
726
727 query = u"""
728 SELECT DISTINCT ON (pk_identity) * FROM (
729 SELECT
730 vbp.*,
731 '%s'::text AS match_type
732 FROM
733 dem.v_basic_person vbp,
734 dem.names n
735 WHERE
736 vbp.pk_identity = n.id_identity
737 %s
738 ORDER BY
739 lastnames,
740 firstnames,
741 dob
742 ) AS ordered_list""" % (_('full name'), where_clause)
743
744 return ({'cmd': query, 'args': args})
745
747 """Text mode UI function to ask for patient."""
748
749 person_searcher = cPatientSearcher_SQL()
750
751 while True:
752 search_fragment = gmTools.prompted_input(prompt = "\nEnter person search term or leave blank to exit")
753
754 if search_fragment in ['exit', 'quit', 'bye', None]:
755 print "user cancelled patient search"
756 return None
757
758 pats = person_searcher.get_patients(search_term = search_fragment)
759
760 if (pats is None) or (len(pats) == 0):
761 print "No patient matches the query term."
762 print ""
763 continue
764
765 if len(pats) > 1:
766 print "Several patients match the query term:"
767 print ""
768 for pat in pats:
769 print pat
770 print ""
771 continue
772
773 return pats[0]
774
775 return None
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 = [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']
814 for name in data:
815 print '%s: %s' % (name, searcher._normalize_soundalikes(name))
816
817 raw_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 raw_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 raw_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 raw_input('press [ENTER] to continue')
855 print "============"
856
857 qs = searcher._generate_queries_de(u'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 raw_input('press [ENTER] to continue')
863 print "============"
864
865 qs = searcher._generate_queries_de(u'özdemir')
866 for q in qs:
867 print " match on:", q['args'][0]
868 print " query :", q['cmd']
869 print " args :", q['args']
870 raw_input('press [ENTER] to continue')
871 print "============"
872
873 qs = searcher._generate_queries_de(u'Özdemir')
874 for q in qs:
875 print " match on:", q['args'][0]
876 print " query :", q['cmd']
877 print " args :", q['args']
878 raw_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 raw_input('press [ENTER] to continue')
889
900
901
902
903
904
905
906
907 test_search_by_dto()
908
909
910