1 """GNUmed clinical narrative business object."""
2
3 __version__ = "$Revision: 1.45 $"
4 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = 'GPL (for details see http://gnu.org)'
6
7 import sys, logging
8
9
10 if __name__ == '__main__':
11 sys.path.insert(0, '../../')
12 from Gnumed.pycommon import gmPG2, gmExceptions, gmBusinessDBObject, gmTools, gmDispatcher, gmHooks
13
14
15 try:
16 _('dummy-no-need-to-translate-but-make-epydoc-happy')
17 except NameError:
18 _ = lambda x:x
19
20
21 _log = logging.getLogger('gm.emr')
22 _log.info(__version__)
23
24
25 soap_cat2l10n = {
26 's': _('soap_S').replace(u'soap_', u''),
27 'o': _('soap_O').replace(u'soap_', u''),
28 'a': _('soap_A').replace(u'soap_', u''),
29 'p': _('soap_P').replace(u'soap_', u''),
30
31 None: gmTools.u_ellipsis,
32 u'': gmTools.u_ellipsis
33 }
34
35 soap_cat2l10n_str = {
36 's': _('soap_Subjective').replace(u'soap_', u''),
37 'o': _('soap_Objective').replace(u'soap_', u''),
38 'a': _('soap_Assessment').replace(u'soap_', u''),
39 'p': _('soap_Plan').replace(u'soap_', u''),
40 None: _('soap_Administrative').replace(u'soap_', u'')
41 }
42
43 l10n2soap_cat = {
44 _('soap_S').replace(u'soap_', u''): 's',
45 _('soap_O').replace(u'soap_', u''): 'o',
46 _('soap_A').replace(u'soap_', u''): 'a',
47 _('soap_P').replace(u'soap_', u''): 'p',
48
49 gmTools.u_ellipsis: None
50 }
51
52
56
57 gmDispatcher.connect(_on_soap_modified, u'clin_narrative_mod_db')
58
59
60 -class cDiag(gmBusinessDBObject.cBusinessDBObject):
61 """Represents one real diagnosis.
62 """
63 _cmd_fetch_payload = u"select *, xmin_clin_diag, xmin_clin_narrative from clin.v_pat_diag where pk_diag=%s"
64 _cmds_store_payload = [
65 u"""update clin.clin_diag set
66 laterality=%()s,
67 laterality=%(laterality)s,
68 is_chronic=%(is_chronic)s::boolean,
69 is_active=%(is_active)s::boolean,
70 is_definite=%(is_definite)s::boolean,
71 clinically_relevant=%(clinically_relevant)s::boolean
72 where
73 pk=%(pk_diag)s and
74 xmin=%(xmin_clin_diag)s""",
75 u"""update clin.clin_narrative set
76 narrative=%(diagnosis)s
77 where
78 pk=%(pk_diag)s and
79 xmin=%(xmin_clin_narrative)s""",
80 u"""select xmin_clin_diag, xmin_clin_narrative from clin.v_pat_diag where pk_diag=%s(pk_diag)s"""
81 ]
82
83 _updatable_fields = [
84 'diagnosis',
85 'laterality',
86 'is_chronic',
87 'is_active',
88 'is_definite',
89 'clinically_relevant'
90 ]
91
93 """
94 Retrieves codes linked to this diagnosis
95 """
96 cmd = u"select code, coding_system from clin.v_codes4diag where diagnosis=%s"
97 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['diagnosis']]]}])
98 return rows
99
100 - def add_code(self, code=None, coding_system=None):
101 """
102 Associates a code (from coding system) with this diagnosis.
103 """
104
105 cmd = u"select clin.add_coded_phrase (%(diag)s, %(code)s, %(sys)s)"
106 args = {
107 'diag': self._payload[self._idx['diagnosis']],
108 'code': code,
109 'sys': coding_system
110 }
111 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
112 return True
113
114 -class cNarrative(gmBusinessDBObject.cBusinessDBObject):
115 """Represents one clinical free text entry.
116 """
117 _cmd_fetch_payload = u"select *, xmin_clin_narrative from clin.v_pat_narrative where pk_narrative = %s"
118 _cmds_store_payload = [
119 u"""update clin.clin_narrative set
120 narrative = %(narrative)s,
121 clin_when = %(date)s,
122 soap_cat = lower(%(soap_cat)s),
123 fk_encounter = %(pk_encounter)s
124 where
125 pk=%(pk_narrative)s and
126 xmin=%(xmin_clin_narrative)s""",
127 u"""select xmin_clin_narrative from clin.v_pat_narrative where pk_narrative=%(pk_narrative)s"""
128 ]
129
130 _updatable_fields = [
131 'narrative',
132 'date',
133 'soap_cat',
134 'pk_episode',
135 'pk_encounter'
136 ]
137
138
139
140
167
168
169
171 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
172 cmd = u"INSERT INTO clin.lnk_code2narrative (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
173 args = {
174 'item': self._payload[self._idx['pk_procedure']],
175 'code': pk_code
176 }
177 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
178 return True
179
181 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
182 cmd = u"DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
183 args = {
184 'item': self._payload[self._idx['pk_procedure']],
185 'code': pk_code
186 }
187 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
188 return True
189
190
191
193 cmd = u"""
194 SELECT * FROM clin.v_linked_codes WHERE
195 item_table = 'clin.lnk_code2narrative'::regclass
196 AND
197 pk_item = %(narr)s
198 """
199 args = {'narr': self._payload[self._idx['pk_narrative']]}
200 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
201 return rows
202
203 codes = property(_get_codes, lambda x:x)
204
205
206
207 -def search_text_across_emrs(search_term=None):
208
209 if search_term is None:
210 return []
211
212 if search_term.strip() == u'':
213 return []
214
215 cmd = u'select * from clin.v_narrative4search where narrative ~* %(term)s order by pk_patient limit 1000'
216 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'term': search_term}}], get_col_idx = False)
217
218 return rows
219
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
229
230
231
232
233 narrative = narrative.strip()
234 if narrative == u'':
235 return (True, None)
236
237
238
239
240 cmd = u"""
241 SELECT
242 *, xmin_clin_narrative
243 FROM clin.v_pat_narrative
244 WHERE
245 pk_encounter = %(enc)s
246 AND
247 pk_episode = %(epi)s
248 AND
249 soap_cat = %(soap)s
250 AND
251 narrative = %(narr)s
252 """
253 args = {
254 'enc': encounter_id,
255 'epi': episode_id,
256 'soap': soap_cat,
257 'narr': narrative
258 }
259 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
260 if len(rows) == 1:
261 narrative = cNarrative(row = {'pk_field': 'pk_narrative', 'data': rows[0], 'idx': idx})
262 return (True, narrative)
263
264
265 queries = [
266 {'cmd': u"""
267 INSERT INTO clin.clin_narrative
268 (fk_encounter, fk_episode, narrative, soap_cat)
269 VALUES
270 (%s, %s, %s, lower(%s))""",
271 'args': [encounter_id, episode_id, narrative, soap_cat]
272 },
273 {'cmd': u"""
274 SELECT *, xmin_clin_narrative
275 FROM clin.v_pat_narrative
276 WHERE
277 pk_narrative = currval(pg_get_serial_sequence('clin.clin_narrative', 'pk'))"""
278 }
279 ]
280 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = True)
281
282 narrative = cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': rows[0]})
283 return (True, narrative)
284
286 """Deletes a clin.clin_narrative row by it's PK."""
287 cmd = u"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 -def get_narrative(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, patient=None, order_by=None):
292 """Get SOAP notes pertinent to this encounter.
293
294 since
295 - initial date for narrative items
296 until
297 - final date for narrative items
298 encounters
299 - list of encounters whose narrative are to be retrieved
300 episodes
301 - list of episodes whose narrative are to be retrieved
302 issues
303 - list of health issues whose narrative are to be retrieved
304 soap_cats
305 - list of SOAP categories of the narrative to be retrieved
306 """
307 where_parts = [u'TRUE']
308 args = {}
309
310 if encounters is not None:
311 where_parts.append(u'pk_encounter IN %(encs)s')
312 args['encs'] = tuple(encounters)
313
314 if episodes is not None:
315 where_parts.append(u'pk_episode IN %(epis)s')
316 args['epis'] = tuple(episodes)
317
318 if issues is not None:
319 where_parts.append(u'pk_health_issue IN %(issues)s')
320 args['issues'] = tuple(issues)
321
322 if patient is not None:
323 where_parts.append(u'pk_patient = %(pat)s')
324 args['pat'] = patient
325
326 if soap_cats is not None:
327 where_parts.append(u'soap_cat IN %(soap_cats)s')
328 args['soap_cats'] = tuple(soap_cats)
329
330 if order_by is None:
331 order_by = u'ORDER BY date, soap_rank'
332 else:
333 order_by = u'ORDER BY %s' % order_by
334
335 cmd = u"""
336 SELECT
337 cvpn.*,
338 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat)
339 AS soap_rank
340 FROM
341 clin.v_pat_narrative cvpn
342 WHERE
343 %s
344 %s
345 """ % (
346 u' AND '.join(where_parts),
347 order_by
348 )
349
350 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
351
352 filtered_narrative = [ cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
353
354 if since is not None:
355 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
356
357 if until is not None:
358 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
359
360 if providers is not None:
361 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
362
363 return filtered_narrative
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379 -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):
380
381 if (patient is None) and (episodes is None) and (issues is None) and (encounters is None):
382 raise ValueError('at least one of <patient>, <episodes>, <issues>, <encounters> must not be None')
383
384 if order_by is None:
385 order_by = u'ORDER BY vemrj.clin_when, vemrj.pk_episode, scr, vemrj.src_table'
386 else:
387 order_by = u'ORDER BY %s' % order_by
388
389 where_parts = []
390 args = {}
391
392 if patient is not None:
393 where_parts.append(u'pk_patient = %(pat)s')
394 args['pat'] = patient
395
396 if soap_cats is not None:
397
398
399 if None in soap_cats:
400 where_parts.append(u'((vemrj.soap_cat IN %(soap_cat)s) OR (vemrj.soap_cat IS NULL))')
401 soap_cats.remove(None)
402 else:
403 where_parts.append(u'vemrj.soap_cat IN %(soap_cat)s')
404 args['soap_cat'] = tuple(soap_cats)
405
406 if time_range is not None:
407 where_parts.append(u"vemrj.clin_when > (now() - '%s days'::interval)" % time_range)
408
409 if episodes is not None:
410 where_parts.append(u"vemrj.pk_episode IN %(epis)s")
411 args['epis'] = tuple(episodes)
412
413 if issues is not None:
414 where_parts.append(u"vemrj.pk_health_issue IN %(issues)s")
415 args['issues'] = tuple(issues)
416
417
418
419 cmd = u"""
420 SELECT
421 to_char(vemrj.clin_when, 'YYYY-MM-DD') AS date,
422 vemrj.clin_when,
423 coalesce(vemrj.soap_cat, '') as soap_cat,
424 vemrj.narrative,
425 vemrj.src_table,
426
427 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = vemrj.soap_cat) AS scr,
428
429 vemrj.modified_when,
430 to_char(vemrj.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified,
431 vemrj.modified_by,
432 vemrj.row_version,
433 vemrj.pk_episode,
434 vemrj.pk_encounter,
435 vemrj.soap_cat as real_soap_cat
436 FROM clin.v_emr_journal vemrj
437 WHERE
438 %s
439 %s""" % (
440 u'\n\t\t\t\t\tAND\n\t\t\t\t'.join(where_parts),
441 order_by
442 )
443
444 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
445 return rows
446
447
448
449 if __name__ == '__main__':
450
451 if len(sys.argv) < 2:
452 sys.exit()
453
454 if sys.argv[1] != 'test':
455 sys.exit()
456
457 from Gnumed.pycommon import gmI18N
458 gmI18N.activate_locale()
459 gmI18N.install_domain(domain = 'gnumed')
460
462 print "\nDiagnose test"
463 print "-------------"
464 diagnose = cDiag(aPK_obj=2)
465 fields = diagnose.get_fields()
466 for field in fields:
467 print field, ':', diagnose[field]
468 print "updatable:", diagnose.get_updatable_fields()
469 print "codes:", diagnose.get_codes()
470
471
472
473
475 print "\nnarrative test"
476 print "--------------"
477 narrative = cNarrative(aPK_obj=7)
478 fields = narrative.get_fields()
479 for field in fields:
480 print field, ':', narrative[field]
481 print "updatable:", narrative.get_updatable_fields()
482 print "codes:", narrative.get_codes()
483
484
485
486
487
488
489
490
491
493 results = search_text_across_emrs('cut')
494 for r in results:
495 print r
496
497
498
499 test_diag()
500 test_narrative()
501
502
503