1 """GNUmed clinical narrative business object."""
2
3 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, Karsten Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = 'GPL v2 or later (for details see http://gnu.org)'
5
6 import sys
7 import logging
8
9
10 if __name__ == '__main__':
11 sys.path.insert(0, '../../')
12 from Gnumed.pycommon import gmPG2
13 from Gnumed.pycommon import gmBusinessDBObject
14 from Gnumed.pycommon import gmTools
15 from Gnumed.pycommon import gmDispatcher
16 from Gnumed.pycommon import gmHooks
17 from Gnumed.pycommon import gmDateTime
18
19 from Gnumed.business import gmCoding
20 from Gnumed.business import gmSoapDefs
21 from Gnumed.business import gmAutoHints
22
23
24 _log = logging.getLogger('gm.emr')
25
26
30
31 gmDispatcher.connect(_on_soap_modified, 'clin.clin_narrative_mod_db')
32
33
34 -class cNarrative(gmBusinessDBObject.cBusinessDBObject):
35 """Represents one clinical free text entry."""
36
37 _cmd_fetch_payload = "SELECT * FROM clin.v_narrative WHERE pk_narrative = %s"
38 _cmds_store_payload = [
39 """update clin.clin_narrative set
40 narrative = %(narrative)s,
41 clin_when = %(date)s,
42 soap_cat = lower(%(soap_cat)s),
43 fk_encounter = %(pk_encounter)s,
44 fk_episode = %(pk_episode)s
45 WHERE
46 pk = %(pk_narrative)s
47 AND
48 xmin = %(xmin_clin_narrative)s
49 RETURNING
50 xmin AS xmin_clin_narrative"""
51 ]
52
53 _updatable_fields = [
54 'narrative',
55 'date',
56 'soap_cat',
57 'pk_episode',
58 'pk_encounter'
59 ]
60
61
64
65
92
93
95 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
96
97 if pk_code in self._payload[self._idx['pk_generic_codes']]:
98 return
99
100 cmd = """
101 INSERT INTO clin.lnk_code2narrative
102 (fk_item, fk_generic_code)
103 SELECT
104 %(item)s,
105 %(code)s
106 WHERE NOT EXISTS (
107 SELECT 1 FROM clin.lnk_code2narrative
108 WHERE
109 fk_item = %(item)s
110 AND
111 fk_generic_code = %(code)s
112 )"""
113 args = {
114 'item': self._payload[self._idx['pk_narrative']],
115 'code': pk_code
116 }
117 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
118 return
119
120
122 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
123 cmd = "DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
124 args = {
125 'item': self._payload[self._idx['pk_narrative']],
126 'code': pk_code
127 }
128 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
129 return True
130
131
132
133
135 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
136 return []
137
138 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
139 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
140 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
141 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
142
144 queries = []
145
146 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
147 queries.append ({
148 'cmd': 'DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(narr)s AND fk_generic_code IN %(codes)s',
149 'args': {
150 'narr': self._payload[self._idx['pk_narrative']],
151 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
152 }
153 })
154
155 for pk_code in pk_codes:
156 queries.append ({
157 'cmd': 'INSERT INTO clin.lnk_code2narrative (fk_item, fk_generic_code) VALUES (%(narr)s, %(pk_code)s)',
158 'args': {
159 'narr': self._payload[self._idx['pk_narrative']],
160 'pk_code': pk_code
161 }
162 })
163 if len(queries) == 0:
164 return
165
166 rows, idx = gmPG2.run_rw_queries(queries = queries)
167 return
168
169 generic_codes = property(_get_generic_codes, _set_generic_codes)
170
171
173 """Create clinical narrative entries.
174
175 <soap>
176 must be a dict, the keys being SOAP categories (including U and
177 None=admin) and the values being text (possibly multi-line)
178
179 Existing but empty ('' or None) categories are skipped.
180 """
181 if soap is None:
182 return True
183
184 if not gmSoapDefs.are_valid_soap_cats(soap.keys(), allow_upper = True):
185 raise ValueError('invalid SOAP category in <soap> dictionary: %s', soap)
186
187 if link_obj is None:
188 link_obj = gmPG2.get_connection(readonly = False)
189 conn_rollback = link_obj.rollback
190 conn_commit = link_obj.commit
191 conn_close = link_obj.close
192 else:
193 conn_rollback = lambda *x:None
194 conn_commit = lambda *x:None
195 conn_close = lambda *x:None
196
197 instances = {}
198 for cat in soap:
199 val = soap[cat]
200 if val is None:
201 continue
202 if ''.join([ v.strip() for v in val ]) == '':
203 continue
204 instance = create_narrative_item (
205 narrative = '\n'.join([ v.strip() for v in val ]),
206 soap_cat = cat,
207 episode_id = episode_id,
208 encounter_id = encounter_id,
209 link_obj = link_obj
210 )
211 if instance is None:
212 continue
213 instances[cat] = instance
214
215 conn_commit()
216 conn_close()
217 return instances
218
219
220 -def create_narrative_item(narrative=None, soap_cat=None, episode_id=None, encounter_id=None, link_obj=None):
221 """Creates a new clinical narrative entry
222
223 narrative - free text clinical narrative
224 soap_cat - soap category
225 episode_id - episodes's primary key
226 encounter_id - encounter's primary key
227
228 any of the args being None (except soap_cat) will fail the SQL code
229 """
230
231 narrative = narrative.strip()
232 if narrative == '':
233 return None
234
235 args = {'enc': encounter_id, 'epi': episode_id, 'soap': soap_cat, 'narr': narrative}
236
237
238
239
240
241 cmd = """
242 INSERT INTO clin.clin_narrative
243 (fk_encounter, fk_episode, narrative, soap_cat)
244 SELECT
245 %(enc)s, %(epi)s, %(narr)s, lower(%(soap)s)
246 WHERE NOT EXISTS (
247 SELECT 1 FROM clin.v_narrative
248 WHERE
249 pk_encounter = %(enc)s
250 AND
251 pk_episode = %(epi)s
252 AND
253 soap_cat = lower(%(soap)s)
254 AND
255 narrative = %(narr)s
256 )
257 RETURNING pk"""
258 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
259 if len(rows) == 1:
260
261 return cNarrative(aPK_obj = rows[0]['pk'], link_obj = link_obj)
262
263 if len(rows) > 1:
264 raise Exception('more than one row returned from single-row INSERT')
265
266
267 cmd = """
268 SELECT * FROM clin.v_narrative
269 WHERE
270 pk_encounter = %(enc)s
271 AND
272 pk_episode = %(epi)s
273 AND
274 soap_cat = lower(%(soap)s)
275 AND
276 narrative = %(narr)s
277 """
278 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
279 if len(rows) == 1:
280 return cNarrative(row = {'pk_field': 'pk_narrative', 'data': rows[0], 'idx': idx})
281
282 raise Exception('retrieving known-to-exist narrative row returned 0 or >1 result: %s' % len(rows))
283
284
286 """Deletes a clin.clin_narrative row by it's PK."""
287 cmd = "DELETE FROM clin.clin_narrative WHERE pk=%s"
288 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [narrative]}])
289 return True
290
291
292 -def get_narrative(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, patient=None, order_by=None):
293 """Get SOAP notes pertinent to this encounter.
294
295 since
296 - initial date for narrative items
297 until
298 - final date for narrative items
299 encounters
300 - list of encounters whose narrative are to be retrieved
301 episodes
302 - list of episodes whose narrative are to be retrieved
303 issues
304 - list of health issues whose narrative are to be retrieved
305 soap_cats
306 - list of SOAP categories of the narrative to be retrieved
307 """
308 where_parts = ['TRUE']
309 args = {}
310
311 if encounters is not None:
312 where_parts.append('pk_encounter IN %(encs)s')
313 args['encs'] = tuple(encounters)
314
315 if episodes is not None:
316 where_parts.append('pk_episode IN %(epis)s')
317 args['epis'] = tuple(episodes)
318
319 if issues is not None:
320 where_parts.append('pk_health_issue IN %(issues)s')
321 args['issues'] = tuple(issues)
322
323 if patient is not None:
324 where_parts.append('pk_patient = %(pat)s')
325 args['pat'] = patient
326
327 if soap_cats is not None:
328 where_parts.append('c_vn.soap_cat IN %(soap_cats)s')
329 args['soap_cats'] = tuple(soap_cats)
330
331 if order_by is None:
332 order_by = 'ORDER BY date, soap_rank'
333 else:
334 order_by = 'ORDER BY %s' % order_by
335
336 cmd = """
337 SELECT
338 c_vn.*,
339 c_scr.rank AS soap_rank
340 FROM
341 clin.v_narrative c_vn
342 LEFT JOIN clin.soap_cat_ranks c_scr ON c_vn.soap_cat = c_scr.soap_cat
343 WHERE
344 %s
345 %s
346 """ % (
347 ' AND '.join(where_parts),
348 order_by
349 )
350
351 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
352
353 filtered_narrative = [ cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
354
355 if since is not None:
356 filtered_narrative = [ narr for narr in filtered_narrative if narr['date'] >= since ]
357
358 if until is not None:
359 filtered_narrative = [ narr for narr in filtered_narrative if narr['date'] < until ]
360
361 if providers is not None:
362 filtered_narrative = [ narr for narr in filtered_narrative if narr['modified_by'] in providers ]
363
364 return filtered_narrative
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380 -def get_as_journal(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None, patient=None, active_encounter=None):
381
382 if (patient is None) and (episodes is None) and (issues is None) and (encounters is None):
383 raise ValueError('at least one of <patient>, <episodes>, <issues>, <encounters> must not be None')
384
385 if order_by is None:
386 order_by = 'ORDER BY clin_when, pk_episode, scr, modified_when, src_table'
387 else:
388 order_by = 'ORDER BY %s' % order_by
389
390 where_parts = []
391 args = {}
392
393 if patient is not None:
394 where_parts.append('c_vej.pk_patient = %(pat)s')
395 args['pat'] = patient
396
397 if soap_cats is not None:
398
399
400 if None in soap_cats:
401 where_parts.append('((c_vej.soap_cat IN %(soap_cat)s) OR (c_vej.soap_cat IS NULL))')
402 soap_cats.remove(None)
403 else:
404 where_parts.append('c_vej.soap_cat IN %(soap_cat)s')
405 args['soap_cat'] = tuple(soap_cats)
406
407 if time_range is not None:
408 where_parts.append("c_vej.clin_when > (now() - '%s days'::interval)" % time_range)
409
410 if episodes is not None:
411 where_parts.append("c_vej.pk_episode IN %(epis)s")
412 args['epis'] = tuple(episodes)
413
414 if issues is not None:
415 where_parts.append("c_vej.pk_health_issue IN %(issues)s")
416 args['issues'] = tuple(issues)
417
418
419
420 cmd_journal = """
421 SELECT
422 to_char(c_vej.clin_when, 'YYYY-MM-DD') AS date,
423 c_vej.clin_when,
424 coalesce(c_vej.soap_cat, '') as soap_cat,
425 c_vej.narrative,
426 c_vej.src_table,
427 c_scr.rank AS scr,
428 c_vej.modified_when,
429 to_char(c_vej.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified,
430 c_vej.modified_by,
431 c_vej.row_version,
432 c_vej.pk_episode,
433 c_vej.pk_encounter,
434 c_vej.soap_cat as real_soap_cat,
435 c_vej.src_pk,
436 c_vej.pk_health_issue,
437 c_vej.health_issue,
438 c_vej.episode,
439 c_vej.issue_active,
440 c_vej.issue_clinically_relevant,
441 c_vej.episode_open,
442 c_vej.encounter_started,
443 c_vej.encounter_last_affirmed,
444 c_vej.encounter_l10n_type,
445 c_vej.pk_patient
446 FROM
447 clin.v_emr_journal c_vej
448 join clin.soap_cat_ranks c_scr on (c_scr.soap_cat IS NOT DISTINCT FROM c_vej.soap_cat)
449 WHERE
450 %s
451 """ % '\n\t\t\t\t\tAND\n\t\t\t\t'.join(where_parts)
452
453 if active_encounter is None:
454 cmd = cmd_journal + '\n ' + order_by
455 else:
456 args['pk_enc'] = active_encounter['pk_encounter']
457 args['enc_start'] = active_encounter['started']
458 args['enc_last_affirmed'] = active_encounter['last_affirmed']
459 args['enc_type'] = active_encounter['l10n_type']
460 args['enc_pat'] = active_encounter['pk_patient']
461 cmd_hints = """
462 SELECT
463 to_char(now(), 'YYYY-MM-DD') AS date,
464 now() as clin_when,
465 'u'::text as soap_cat,
466 hints.title || E'\n' || hints.hint
467 as narrative,
468 -- .src_table does not correspond with the
469 -- .src_pk column source because it is generated
470 -- from clin.get_hints_for_patient()
471 'ref.auto_hint'::text as src_table,
472 c_scr.rank AS scr,
473 now() as modified_when,
474 to_char(now(), 'YYYY-MM-DD HH24:MI') AS date_modified,
475 current_user as modified_by,
476 0::integer as row_version,
477 NULL::integer as pk_episode,
478 %(pk_enc)s as pk_encounter,
479 'u'::text as real_soap_cat,
480 hints.pk_auto_hint as src_pk,
481 NULL::integer as pk_health_issue,
482 ''::text as health_issue,
483 ''::text as episode,
484 False as issue_active,
485 False as issue_clinically_relevant,
486 False as episode_open,
487 %(enc_start)s as encounter_started,
488 %(enc_last_affirmed)s as encounter_last_affirmed,
489 %(enc_type)s as encounter_l10n_type,
490 %(enc_pat)s as pk_patient
491 FROM
492 clin.get_hints_for_patient(%(enc_pat)s) AS hints
493 JOIN clin.soap_cat_ranks c_scr ON (c_scr.soap_cat = 'u')
494 """
495 cmd = cmd_journal + '\nUNION ALL\n' + cmd_hints + '\n' + order_by
496
497 journal_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
498
499 return journal_rows
500
501
502
503
504 _VIEW_clin_v_narrative4search = u"""
505 create temporary view v_narrative4search as
506
507 select * from (
508
509 -- clin.clin_root_items
510 select
511 vpi.pk_patient as pk_patient,
512 vpi.soap_cat as soap_cat,
513 vpi.narrative as narrative,
514 vpi.pk_encounter as pk_encounter,
515 vpi.pk_episode as pk_episode,
516 vpi.pk_health_issue as pk_health_issue,
517 vpi.pk_item as src_pk,
518 vpi.src_table as src_table
519 from
520 clin.v_pat_items vpi
521 where
522 src_table not in (
523 'clin.allergy',
524 'clin.test_result',
525 'clin.procedure',
526 'clin.substance_intake',
527 'clin.family_history'
528 )
529
530
531 union all
532
533 select * from clin.v_subst_intake4narr_search
534 where gm.is_null_or_blank_string(narrative) is FALSE
535
536
537 union all
538 select -- clin.procedure
539 (select fk_patient from clin.encounter where pk = cpr.fk_encounter)
540 as pk_patient,
541 cpr.soap_cat
542 as soap_cat,
543 cpr.narrative
544 as narrative,
545 cpr.fk_encounter
546 as pk_encounter,
547 cpr.fk_episode
548 as pk_episode,
549 (select fk_health_issue from clin.episode where pk = cpr.fk_episode)
550 as pk_health_issue,
551 cpr.pk
552 as src_pk,
553 'clin.procedure'
554 as src_table
555 from
556 clin.procedure cpr
557 where
558 cpr.narrative is not NULL
559
560
561 union all
562 select -- test results
563 (select fk_patient from clin.encounter where pk = ctr.fk_encounter)
564 as pk_patient,
565 ctr.soap_cat
566 as soap_cat,
567 coalesce(ctr.narrative, '')
568 || coalesce(' ' || ctr.val_alpha, '')
569 || coalesce(' ' || ctr.val_unit, '')
570 || coalesce(' ' || ctr.val_normal_range, '')
571 || coalesce(' ' || ctr.val_target_range, '')
572 || coalesce(' ' || ctr.norm_ref_group, '')
573 || coalesce(' ' || ctr.note_test_org, '')
574 || coalesce(' ' || ctr.material, '')
575 || coalesce(' ' || ctr.material_detail, '')
576 as narrative,
577 ctr.fk_encounter
578 as pk_encounter,
579 ctr.fk_episode
580 as pk_episode,
581 (select fk_health_issue from clin.episode where pk = ctr.fk_episode)
582 as pk_health_issue,
583 ctr.pk
584 as src_pk,
585 'clin.test_result'
586 as src_table
587 from
588 clin.test_result ctr
589
590
591 union all -- test result reviews
592 select
593 (select fk_patient from clin.encounter where pk =
594 (select fk_encounter from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row)
595 )
596 as pk_patient,
597 'o'::text
598 as soap_cat,
599 crtr.comment
600 as narrative,
601 (select fk_encounter from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row)
602 as pk_encounter,
603 (select fk_episode from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row)
604 as pk_episode,
605 (select fk_health_issue from clin.episode where pk =
606 (select fk_episode from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row)
607 )
608 as pk_health_issue,
609 crtr.pk
610 as src_pk,
611 'clin.reviewed_test_results'
612 as src_table
613 from
614 clin.reviewed_test_results crtr
615
616
617 union all
618 select -- allergy state
619 (select fk_patient from clin.encounter where pk = cas.fk_encounter)
620 as pk_patient,
621 'o'::text
622 as soap_cat,
623 cas.comment
624 as narrative,
625 cas.fk_encounter
626 as pk_encounter,
627 null
628 as pk_episode,
629 null
630 as pk_health_issue,
631 cas.pk
632 as src_pk,
633 'clin.allergy_state'
634 as src_table
635 from
636 clin.allergy_state cas
637 where
638 cas.comment is not NULL
639
640
641 union all
642 select -- allergies
643 (select fk_patient from clin.encounter where pk = ca.fk_encounter)
644 as pk_patient,
645 ca.soap_cat
646 as soap_cat,
647 coalesce(narrative, '')
648 || coalesce(' ' || substance, '')
649 || coalesce(' ' || substance_code, '')
650 || coalesce(' ' || generics, '')
651 || coalesce(' ' || allergene, '')
652 || coalesce(' ' || atc_code, '')
653 as narrative,
654 ca.fk_encounter
655 as pk_encounter,
656 ca.fk_episode
657 as pk_episode,
658 (select fk_health_issue from clin.episode where pk = ca.fk_episode)
659 as pk_health_issue,
660 ca.pk
661 as src_pk,
662 'clin.allergy'
663 as src_table
664 from
665 clin.allergy ca
666
667
668 union all -- health issues
669 select
670 (select fk_patient from clin.encounter where pk = chi.fk_encounter)
671 as pk_patient,
672 'a' as soap_cat,
673 chi.description
674 || coalesce(' ' || chi.summary, '')
675 as narrative,
676 chi.fk_encounter
677 as pk_encounter,
678 null
679 as pk_episode,
680 chi.pk
681 as pk_health_issue,
682 chi.pk
683 as src_pk,
684 'clin.health_issue'
685 as src_table
686 from
687 clin.health_issue chi
688
689
690 union all -- encounters
691 select
692 cenc.fk_patient as pk_patient,
693 's' as soap_cat,
694 coalesce(cenc.reason_for_encounter, '')
695 || coalesce(' ' || cenc.assessment_of_encounter, '')
696 as narrative,
697 cenc.pk as pk_encounter,
698 null as pk_episode,
699 null as pk_health_issue,
700 cenc.pk as src_pk,
701 'clin.encounter' as src_table
702 from
703 clin.encounter cenc
704
705
706 union all -- episodes
707 select
708 (select fk_patient from clin.encounter where pk = cep.fk_encounter)
709 as pk_patient,
710 's' as soap_cat,
711 cep.description
712 || coalesce(' ' || cep.summary, '')
713 as narrative,
714 cep.fk_encounter
715 as pk_encounter,
716 cep.pk
717 as pk_episode,
718 cep.fk_health_issue
719 as pk_health_issue,
720 cep.pk
721 as src_pk,
722 'clin.episode'
723 as src_table
724 from
725 clin.episode cep
726
727
728 union all -- family history
729 select
730 c_vfhx.pk_patient,
731 c_vfhx.soap_cat,
732 (c_vfhx.relation || ' / ' || c_vfhx.l10n_relation || ' '
733 || c_vfhx.name_relative || ': '
734 || c_vfhx.condition
735 ) as narrative,
736 c_vfhx.pk_encounter,
737 c_vfhx.pk_episode,
738 c_vfhx.pk_health_issue,
739 c_vfhx.pk_family_history as src_pk,
740 'clin.family_history' as src_table
741 from
742 clin.v_family_history c_vfhx
743
744
745 union all -- documents
746 select
747 vdm.pk_patient as pk_patient,
748 'o' as soap_cat,
749 (vdm.l10n_type || ' ' ||
750 coalesce(vdm.ext_ref, '') || ' ' ||
751 coalesce(vdm.comment, '')
752 ) as narrative,
753 vdm.pk_encounter as pk_encounter,
754 vdm.pk_episode as pk_episode,
755 vdm.pk_health_issue as pk_health_issue,
756 vdm.pk_doc as src_pk,
757 'blobs.doc_med' as src_table
758 from
759 blobs.v_doc_med vdm
760
761
762 union all -- document objects
763 select
764 vo4d.pk_patient as pk_patient,
765 'o' as soap_cat,
766 vo4d.obj_comment as narrative,
767 vo4d.pk_encounter as pk_encounter,
768 vo4d.pk_episode as pk_episode,
769 vo4d.pk_health_issue as pk_health_issue,
770 vo4d.pk_obj as src_pk,
771 'blobs.doc_obj' as src_table
772 from
773 blobs.v_obj4doc_no_data vo4d
774
775
776 union all -- document descriptions
777 select
778 vdd.pk_patient as pk_patient,
779 'o' as soap_cat,
780 vdd.description as narrative,
781 vdd.pk_encounter as pk_encounter,
782 vdd.pk_episode as pk_episode,
783 vdd.pk_health_issue as pk_health_issue,
784 vdd.pk_doc_desc as src_pk,
785 'blobs.doc_desc' as src_table
786 from
787 blobs.v_doc_desc vdd
788
789
790 union all -- reviewed documents
791 select
792 vrdo.pk_patient as pk_patient,
793 's' as soap_cat,
794 vrdo.comment as narrative,
795 null as pk_encounter,
796 vrdo.pk_episode as pk_episode,
797 vrdo.pk_health_issue as pk_health_issue,
798 vrdo.pk_review_root as src_pk,
799 'blobs.v_reviewed_doc_objects' as src_table
800 from
801 blobs.v_reviewed_doc_objects vrdo
802
803
804 union all -- patient tags
805 select
806 d_vit.pk_identity
807 as pk_patient,
808 's' as soap_cat,
809 d_vit.l10n_description
810 || coalesce(' ' || d_vit.comment, '')
811 as narrative,
812 null
813 as pk_encounter,
814 null
815 as pk_episode,
816 null
817 as pk_health_issue,
818 d_vit.pk_identity_tag
819 as src_pk,
820 'dem.v_identity_tags'
821 as src_table
822 from
823 dem.v_identity_tags d_vit
824
825
826 union all -- external care
827 select
828 c_vec.pk_identity
829 as pk_patient,
830 's' as soap_cat,
831 case
832 when c_vec.pk_health_issue is null then
833 coalesce(c_vec.issue, '')
834 || coalesce(' / ' || c_vec.provider, '')
835 || coalesce(' / ' || c_vec.comment, '')
836 else
837 coalesce(c_vec.provider, '')
838 || coalesce(' / ' || c_vec.comment, '')
839 end as narrative,
840 c_vec.pk_encounter
841 as pk_encounter,
842 null
843 as pk_episode,
844 c_vec.pk_health_issue
845 as pk_health_issue,
846 c_vec.pk_external_care
847 as src_pk,
848 'clin.v_external_care'
849 as src_table
850 from
851 clin.v_external_care c_vec
852
853
854 union all -- export items
855 select
856 c_vei.pk_identity
857 as pk_patient,
858 's' as soap_cat,
859 case
860 when c_vei.pk_doc_obj is null then
861 coalesce(c_vei.description, '')
862 || coalesce(' / ' || c_vei.filename, '')
863 else
864 coalesce(c_vei.description, '')
865 end as narrative,
866 null
867 as pk_encounter,
868 null
869 as pk_episode,
870 null
871 as pk_health_issue,
872 c_vei.pk_export_item
873 as src_pk,
874 'clin.v_export_items'
875 as src_table
876 from
877 clin.v_export_items c_vei
878
879
880 union all -- hint suppression rationale
881 select
882 (select fk_patient from clin.encounter where pk = c_sh.fk_encounter)
883 as pk_patient,
884 'p' as soap_cat,
885 c_sh.rationale
886 as narrative,
887 c_sh.fk_encounter
888 as pk_encounter,
889 null
890 as pk_episode,
891 null
892 as pk_health_issue,
893 c_sh.pk
894 as src_pk,
895 'clin.suppressed_hint'
896 as src_table
897 from
898 clin.suppressed_hint c_sh
899
900 ) as union_table
901
902 where
903 trim(coalesce(union_table.narrative, '')) != ''
904 ;
905 """
906
907 -def search_text_across_emrs(search_term=None):
908
909 if search_term is None:
910 return []
911
912 if search_term.strip() == '':
913 return []
914
915
916
917 cmd = u'select * from v_narrative4search where narrative ~* %(term)s order by pk_patient limit 1000'
918 queries = [
919 {'cmd': _VIEW_clin_v_narrative4search},
920 {'cmd': cmd, 'args': {'term': search_term}}
921 ]
922 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
923 return rows
924
925
926
927
928 if __name__ == '__main__':
929
930 if len(sys.argv) < 2:
931 sys.exit()
932
933 if sys.argv[1] != 'test':
934 sys.exit()
935
936 from Gnumed.pycommon import gmI18N
937 gmI18N.activate_locale()
938 gmI18N.install_domain(domain = 'gnumed')
939
940
942 print("\nnarrative test")
943 print("--------------")
944 narrative = cNarrative(aPK_obj=7)
945 fields = narrative.get_fields()
946 for field in fields:
947 print(field, ':', narrative[field])
948 print("updatable:", narrative.get_updatable_fields())
949 print("codes:", narrative.generic_codes)
950
951
952
953
954
955
956
957
958
960 results = search_text_across_emrs('cut')
961 for r in results:
962 print(r)
963
964
965
966 test_narrative()
967