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