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