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, logging
7
8
9 if __name__ == '__main__':
10 sys.path.insert(0, '../../')
11 from Gnumed.pycommon import gmPG2, gmExceptions, gmBusinessDBObject, gmTools, gmDispatcher, gmHooks
12 from Gnumed.business import gmCoding
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
23
24 soap_cat2l10n = {
25 u's': _('soap_S').replace(u'soap_', u''),
26 u'o': _('soap_O').replace(u'soap_', u''),
27 u'a': _('soap_A').replace(u'soap_', u''),
28 u'p': _('soap_P').replace(u'soap_', u''),
29 u'u': _('soap_U').replace(u'soap_', u''),
30
31 None: gmTools.u_ellipsis,
32 u'': gmTools.u_ellipsis
33 }
34
35 soap_cat2l10n_str = {
36 u's': _('soap_Subjective').replace(u'soap_', u''),
37 u'o': _('soap_Objective').replace(u'soap_', u''),
38 u'a': _('soap_Assessment').replace(u'soap_', u''),
39 u'p': _('soap_Plan').replace(u'soap_', u''),
40 u'u': _('soap_Unspecified').replace(u'soap_', u''),
41 None: _('soap_Administrative').replace(u'soap_', u'')
42 }
43
44 l10n2soap_cat = {
45 _('soap_S').replace(u'soap_', u''): u's',
46 _('soap_O').replace(u'soap_', u''): u'o',
47 _('soap_A').replace(u'soap_', u''): u'a',
48 _('soap_P').replace(u'soap_', u''): u'p',
49 _('soap_U').replace(u'soap_', u''): u'u',
50
51 gmTools.u_ellipsis: None
52 }
53
54
58
59 gmDispatcher.connect(_on_soap_modified, u'clin.clin_narrative_mod_db')
60
61
62 -class cNarrative(gmBusinessDBObject.cBusinessDBObject):
63 """Represents one clinical free text entry.
64 """
65 _cmd_fetch_payload = u"select *, xmin_clin_narrative from clin.v_pat_narrative where pk_narrative = %s"
66 _cmds_store_payload = [
67 u"""update clin.clin_narrative set
68 narrative = %(narrative)s,
69 clin_when = %(date)s,
70 soap_cat = lower(%(soap_cat)s),
71 fk_encounter = %(pk_encounter)s
72 where
73 pk=%(pk_narrative)s and
74 xmin=%(xmin_clin_narrative)s""",
75 u"""select xmin_clin_narrative from clin.v_pat_narrative where pk_narrative=%(pk_narrative)s"""
76 ]
77
78 _updatable_fields = [
79 'narrative',
80 'date',
81 'soap_cat',
82 'pk_episode',
83 'pk_encounter'
84 ]
85
86
114
116 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
117
118 if pk_code in self._payload[self._idx['pk_generic_codes']]:
119 return
120
121 cmd = u"""
122 INSERT INTO clin.lnk_code2narrative
123 (fk_item, fk_generic_code)
124 SELECT
125 %(item)s,
126 %(code)s
127 WHERE NOT EXISTS (
128 SELECT 1 FROM clin.lnk_code2narrative
129 WHERE
130 fk_item = %(item)s
131 AND
132 fk_generic_code = %(code)s
133 )"""
134 args = {
135 'item': self._payload[self._idx['pk_narrative']],
136 'code': pk_code
137 }
138 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
139 return
140
142 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
143 cmd = u"DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
144 args = {
145 'item': self._payload[self._idx['pk_narrative']],
146 'code': pk_code
147 }
148 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
149 return True
150
151
152
154 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
155 return []
156
157 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
158 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
159 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
160 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
161
163 queries = []
164
165 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
166 queries.append ({
167 'cmd': u'DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(narr)s AND fk_generic_code IN %(codes)s',
168 'args': {
169 'narr': self._payload[self._idx['pk_narrative']],
170 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
171 }
172 })
173
174 for pk_code in pk_codes:
175 queries.append ({
176 'cmd': u'INSERT INTO clin.lnk_code2narrative (fk_item, fk_generic_code) VALUES (%(narr)s, %(pk_code)s)',
177 'args': {
178 'narr': self._payload[self._idx['pk_narrative']],
179 'pk_code': pk_code
180 }
181 })
182 if len(queries) == 0:
183 return
184
185 rows, idx = gmPG2.run_rw_queries(queries = queries)
186 return
187
188 generic_codes = property(_get_generic_codes, _set_generic_codes)
189
190
191
192 -def search_text_across_emrs(search_term=None):
193
194 if search_term is None:
195 return []
196
197 if search_term.strip() == u'':
198 return []
199
200 cmd = u'select * from clin.v_narrative4search where narrative ~* %(term)s order by pk_patient limit 1000'
201 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'term': search_term}}], get_col_idx = False)
202
203 return rows
204
206 """Creates a new clinical narrative entry
207
208 narrative - free text clinical narrative
209 soap_cat - soap category
210 episode_id - episodes's primary key
211 encounter_id - encounter's primary key
212 """
213
214
215
216
217
218 narrative = narrative.strip()
219 if narrative == u'':
220 return (True, None)
221
222
223
224
225 cmd = u"""
226 SELECT
227 *, xmin_clin_narrative
228 FROM clin.v_pat_narrative
229 WHERE
230 pk_encounter = %(enc)s
231 AND
232 pk_episode = %(epi)s
233 AND
234 soap_cat = %(soap)s
235 AND
236 narrative = %(narr)s
237 """
238 args = {
239 'enc': encounter_id,
240 'epi': episode_id,
241 'soap': soap_cat,
242 'narr': narrative
243 }
244 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
245 if len(rows) == 1:
246 narrative = cNarrative(row = {'pk_field': 'pk_narrative', 'data': rows[0], 'idx': idx})
247 return (True, narrative)
248
249
250 queries = [
251 {'cmd': u"""
252 INSERT INTO clin.clin_narrative
253 (fk_encounter, fk_episode, narrative, soap_cat)
254 VALUES
255 (%s, %s, %s, lower(%s))""",
256 'args': [encounter_id, episode_id, narrative, soap_cat]
257 },
258 {'cmd': u"""
259 SELECT *, xmin_clin_narrative
260 FROM clin.v_pat_narrative
261 WHERE
262 pk_narrative = currval(pg_get_serial_sequence('clin.clin_narrative', 'pk'))"""
263 }
264 ]
265 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = True)
266
267 narrative = cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': rows[0]})
268 return (True, narrative)
269
271 """Deletes a clin.clin_narrative row by it's PK."""
272 cmd = u"delete from clin.clin_narrative where pk=%s"
273 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [narrative]}])
274 return True
275
276 -def get_narrative(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, patient=None, order_by=None):
277 """Get SOAP notes pertinent to this encounter.
278
279 since
280 - initial date for narrative items
281 until
282 - final date for narrative items
283 encounters
284 - list of encounters whose narrative are to be retrieved
285 episodes
286 - list of episodes whose narrative are to be retrieved
287 issues
288 - list of health issues whose narrative are to be retrieved
289 soap_cats
290 - list of SOAP categories of the narrative to be retrieved
291 """
292 where_parts = [u'TRUE']
293 args = {}
294
295 if encounters is not None:
296 where_parts.append(u'pk_encounter IN %(encs)s')
297 args['encs'] = tuple(encounters)
298
299 if episodes is not None:
300 where_parts.append(u'pk_episode IN %(epis)s')
301 args['epis'] = tuple(episodes)
302
303 if issues is not None:
304 where_parts.append(u'pk_health_issue IN %(issues)s')
305 args['issues'] = tuple(issues)
306
307 if patient is not None:
308 where_parts.append(u'pk_patient = %(pat)s')
309 args['pat'] = patient
310
311 if soap_cats is not None:
312 where_parts.append(u'soap_cat IN %(soap_cats)s')
313 args['soap_cats'] = tuple(soap_cats)
314
315 if order_by is None:
316 order_by = u'ORDER BY date, soap_rank'
317 else:
318 order_by = u'ORDER BY %s' % order_by
319
320 cmd = u"""
321 SELECT
322 cvpn.*,
323 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat)
324 AS soap_rank
325 FROM
326 clin.v_pat_narrative cvpn
327 WHERE
328 %s
329 %s
330 """ % (
331 u' AND '.join(where_parts),
332 order_by
333 )
334
335 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
336
337 filtered_narrative = [ cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
338
339 if since is not None:
340 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
341
342 if until is not None:
343 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
344
345 if providers is not None:
346 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
347
348 return filtered_narrative
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364 -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):
365
366 if (patient is None) and (episodes is None) and (issues is None) and (encounters is None):
367 raise ValueError('at least one of <patient>, <episodes>, <issues>, <encounters> must not be None')
368
369 if order_by is None:
370 order_by = u'ORDER BY vemrj.clin_when, vemrj.pk_episode, scr, vemrj.src_table'
371 else:
372 order_by = u'ORDER BY %s' % order_by
373
374 where_parts = []
375 args = {}
376
377 if patient is not None:
378 where_parts.append(u'pk_patient = %(pat)s')
379 args['pat'] = patient
380
381 if soap_cats is not None:
382
383
384 if None in soap_cats:
385 where_parts.append(u'((vemrj.soap_cat IN %(soap_cat)s) OR (vemrj.soap_cat IS NULL))')
386 soap_cats.remove(None)
387 else:
388 where_parts.append(u'vemrj.soap_cat IN %(soap_cat)s')
389 args['soap_cat'] = tuple(soap_cats)
390
391 if time_range is not None:
392 where_parts.append(u"vemrj.clin_when > (now() - '%s days'::interval)" % time_range)
393
394 if episodes is not None:
395 where_parts.append(u"vemrj.pk_episode IN %(epis)s")
396 args['epis'] = tuple(episodes)
397
398 if issues is not None:
399 where_parts.append(u"vemrj.pk_health_issue IN %(issues)s")
400 args['issues'] = tuple(issues)
401
402
403
404 cmd = u"""
405 SELECT
406 to_char(vemrj.clin_when, 'YYYY-MM-DD') AS date,
407 vemrj.clin_when,
408 coalesce(vemrj.soap_cat, '') as soap_cat,
409 vemrj.narrative,
410 vemrj.src_table,
411
412 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = vemrj.soap_cat) AS scr,
413
414 vemrj.modified_when,
415 to_char(vemrj.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified,
416 vemrj.modified_by,
417 vemrj.row_version,
418 vemrj.pk_episode,
419 vemrj.pk_encounter,
420 vemrj.soap_cat as real_soap_cat
421 FROM clin.v_emr_journal vemrj
422 WHERE
423 %s
424 %s""" % (
425 u'\n\t\t\t\t\tAND\n\t\t\t\t'.join(where_parts),
426 order_by
427 )
428
429 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
430 return rows
431
432
433
434 if __name__ == '__main__':
435
436 if len(sys.argv) < 2:
437 sys.exit()
438
439 if sys.argv[1] != 'test':
440 sys.exit()
441
442 from Gnumed.pycommon import gmI18N
443 gmI18N.activate_locale()
444 gmI18N.install_domain(domain = 'gnumed')
445
446
448 print "\nnarrative test"
449 print "--------------"
450 narrative = cNarrative(aPK_obj=7)
451 fields = narrative.get_fields()
452 for field in fields:
453 print field, ':', narrative[field]
454 print "updatable:", narrative.get_updatable_fields()
455 print "codes:", narrative.generic_codes
456
457
458
459
460
461
462
463
464
466 results = search_text_across_emrs('cut')
467 for r in results:
468 print r
469
470
471
472 test_narrative()
473
474
475