| Home | Trees | Indices | Help |
|
|---|
|
|
1 """This module encapsulates a document stored in a GNUmed database."""
2 #============================================================
3 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL v2 or later"
5
6 import sys, os, shutil, os.path, types, time, logging
7
8
9 if __name__ == '__main__':
10 sys.path.insert(0, '../../')
11 from Gnumed.pycommon import gmExceptions
12 from Gnumed.pycommon import gmBusinessDBObject
13 from Gnumed.pycommon import gmPG2
14 from Gnumed.pycommon import gmTools
15 from Gnumed.pycommon import gmMimeLib
16 from Gnumed.pycommon import gmDateTime
17 from Gnumed.pycommon import gmWorkerThread
18
19 from Gnumed.business import gmOrganization
20
21
22 _log = logging.getLogger('gm.docs')
23
24 MUGSHOT=26
25 DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE = 'visual progress note'
26 DOCUMENT_TYPE_PRESCRIPTION = 'prescription'
27
28 #============================================================
30 """Represents a folder with medical documents for a single patient."""
31
33 """Fails if
34
35 - patient referenced by aPKey does not exist
36 """
37 self.pk_patient = aPKey # == identity.pk == primary key
38 if not self._pkey_exists():
39 raise gmExceptions.ConstructorError("No patient with PK [%s] in database." % aPKey)
40
41 # register backend notification interests
42 # (keep this last so we won't hang on threads when
43 # failing this constructor for other reasons ...)
44 # if not self._register_interests():
45 # raise gmExceptions.ConstructorError, "cannot register signal interests"
46
47 _log.debug('instantiated document folder for patient [%s]' % self.pk_patient)
48 #--------------------------------------------------------
51 #--------------------------------------------------------
52 # internal helper
53 #--------------------------------------------------------
55 """Does this primary key exist ?
56
57 - true/false/None
58 """
59 # patient in demographic database ?
60 rows, idx = gmPG2.run_ro_queries(queries = [
61 {'cmd': "select exists(select pk from dem.identity where pk = %s)", 'args': [self.pk_patient]}
62 ])
63 if not rows[0][0]:
64 _log.error("patient [%s] not in demographic database" % self.pk_patient)
65 return None
66 return True
67 #--------------------------------------------------------
68 # API
69 #--------------------------------------------------------
71 cmd = """
72 SELECT pk_doc
73 FROM blobs.v_doc_med
74 WHERE
75 pk_patient = %(pat)s
76 AND
77 type = %(typ)s
78 AND
79 ext_ref = %(ref)s
80 ORDER BY
81 clin_when DESC
82 LIMIT 1
83 """
84 args = {
85 'pat': self.pk_patient,
86 'typ': DOCUMENT_TYPE_PRESCRIPTION,
87 'ref': 'FreeDiams'
88 }
89 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
90 if len(rows) == 0:
91 _log.info('no FreeDiams prescription available for patient [%s]' % self.pk_patient)
92 return None
93 prescription = cDocument(aPK_obj = rows[0][0])
94 return prescription
95
96 #--------------------------------------------------------
98 cmd = "SELECT pk_obj FROM blobs.v_latest_mugshot WHERE pk_patient = %s"
99 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
100 if len(rows) == 0:
101 _log.info('no mugshots available for patient [%s]' % self.pk_patient)
102 return None
103 return cDocumentPart(aPK_obj = rows[0][0])
104
105 latest_mugshot = property(get_latest_mugshot, lambda x:x)
106
107 #--------------------------------------------------------
109 if latest_only:
110 cmd = "select pk_doc, pk_obj from blobs.v_latest_mugshot where pk_patient=%s"
111 else:
112 cmd = """
113 select
114 vdm.pk_doc as pk_doc,
115 dobj.pk as pk_obj
116 from
117 blobs.v_doc_med vdm
118 blobs.doc_obj dobj
119 where
120 vdm.pk_type = (select pk from blobs.doc_type where name = 'patient photograph')
121 and vdm.pk_patient = %s
122 and dobj.fk_doc = vdm.pk_doc
123 """
124 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
125 return rows
126
127 #--------------------------------------------------------
129 """return flat list of document IDs"""
130
131 args = {
132 'ID': self.pk_patient,
133 'TYP': doc_type
134 }
135
136 cmd = """
137 select vdm.pk_doc
138 from blobs.v_doc_med vdm
139 where
140 vdm.pk_patient = %%(ID)s
141 %s
142 order by vdm.clin_when"""
143
144 if doc_type is None:
145 cmd = cmd % ''
146 else:
147 try:
148 int(doc_type)
149 cmd = cmd % 'and vdm.pk_type = %(TYP)s'
150 except (TypeError, ValueError):
151 cmd = cmd % 'and vdm.pk_type = (select pk from blobs.doc_type where name = %(TYP)s)'
152
153 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
154 doc_ids = []
155 for row in rows:
156 doc_ids.append(row[0])
157 return doc_ids
158
159 #--------------------------------------------------------
161 return self.get_documents (
162 doc_type = DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
163 pk_episodes = episodes,
164 encounter = encounter
165 )
166
167 #--------------------------------------------------------
169 args = {'pat': self.pk_patient}
170 cmd = _SQL_get_document_fields % """
171 pk_doc IN (
172 SELECT DISTINCT ON (b_vo.pk_doc) b_vo.pk_doc
173 FROM blobs.v_obj4doc_no_data b_vo
174 WHERE
175 pk_patient = %(pat)s
176 AND
177 reviewed IS FALSE
178 )
179 ORDER BY clin_when DESC"""
180 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
181 return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
182
183 #--------------------------------------------------------
184 - def get_documents(self, doc_type=None, pk_episodes=None, encounter=None, order_by=None, exclude_unsigned=False, pk_types=None):
185 """Return list of documents."""
186
187 args = {
188 'pat': self.pk_patient,
189 'type': doc_type,
190 'enc': encounter
191 }
192 where_parts = ['pk_patient = %(pat)s']
193
194 if doc_type is not None:
195 try:
196 int(doc_type)
197 where_parts.append('pk_type = %(type)s')
198 except (TypeError, ValueError):
199 where_parts.append('pk_type = (SELECT pk FROM blobs.doc_type WHERE name = %(type)s)')
200
201 if pk_types is not None:
202 where_parts.append('pk_type IN %(pk_types)s')
203 args['pk_types'] = tuple(pk_types)
204
205 if (pk_episodes is not None) and (len(pk_episodes) > 0):
206 where_parts.append('pk_episode IN %(epis)s')
207 args['epis'] = tuple(pk_episodes)
208
209 if encounter is not None:
210 where_parts.append('pk_encounter = %(enc)s')
211
212 if exclude_unsigned:
213 where_parts.append('pk_doc IN (SELECT b_vo.pk_doc FROM blobs.v_obj4doc_no_data b_vo WHERE b_vo.pk_patient = %(pat)s AND b_vo.reviewed IS TRUE)')
214
215 if order_by is None:
216 order_by = 'ORDER BY clin_when'
217
218 cmd = "%s\n%s" % (_SQL_get_document_fields % ' AND '.join(where_parts), order_by)
219 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
220
221 return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
222
223 documents = property(get_documents, lambda x:x)
224
225 #--------------------------------------------------------
227 return create_document(link_obj = link_obj, document_type = document_type, encounter = encounter, episode = episode)
228
229 #--------------------------------------------------------
231 return self.add_document (
232 link_obj = link_obj,
233 document_type = create_document_type (
234 document_type = DOCUMENT_TYPE_PRESCRIPTION
235 )['pk_doc_type'],
236 encounter = encounter,
237 episode = episode
238 )
239
240 #--------------------------------------------------------
242 cmd = gmOrganization._SQL_get_org_unit % (
243 'pk_org_unit IN (SELECT DISTINCT ON (pk_org_unit) pk_org_unit FROM blobs.v_doc_med WHERE pk_patient = %(pat)s)'
244 )
245 args = {'pat': self.pk_patient}
246 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
247 return [ gmOrganization.cOrgUnit(row = {'data': r, 'idx': idx, 'pk_field': 'pk_org_unit'}) for r in rows ]
248
249 all_document_org_units = property(_get_all_document_org_units, lambda x:x)
250
251 #============================================================
252 _SQL_get_document_part_fields = "select * from blobs.v_obj4doc_no_data where %s"
253
255 """Represents one part of a medical document."""
256
257 _cmd_fetch_payload = _SQL_get_document_part_fields % "pk_obj = %s"
258 _cmds_store_payload = [
259 """UPDATE blobs.doc_obj SET
260 seq_idx = %(seq_idx)s,
261 comment = gm.nullify_empty_string(%(obj_comment)s),
262 filename = gm.nullify_empty_string(%(filename)s),
263 fk_intended_reviewer = %(pk_intended_reviewer)s
264 WHERE
265 pk = %(pk_obj)s
266 AND
267 xmin = %(xmin_doc_obj)s
268 RETURNING
269 xmin AS xmin_doc_obj"""
270 ]
271 _updatable_fields = [
272 'seq_idx',
273 'obj_comment',
274 'pk_intended_reviewer',
275 'filename'
276 ]
277 #--------------------------------------------------------
278 # retrieve data
279 #--------------------------------------------------------
280 - def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, adjust_extension=False, conn=None):
281
282 if filename is None:
283 filename = self.get_useful_filename(make_unique = True, directory = directory)
284
285 filename = self.__download_to_file(filename = filename)
286 if filename is None:
287 return None
288
289 if target_mime is None:
290 if filename.endswith('.dat'):
291 if adjust_extension:
292 return gmMimeLib.adjust_extension_by_mimetype(filename)
293 return filename
294
295 if target_extension is None:
296 target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
297
298 target_path, name = os.path.split(filename)
299 name, tmp = os.path.splitext(name)
300 target_fname = gmTools.get_unique_filename (
301 prefix = '%s-conv-' % name,
302 suffix = target_extension
303 )
304 _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname)
305 converted_fname = gmMimeLib.convert_file (
306 filename = filename,
307 target_mime = target_mime,
308 target_filename = target_fname
309 )
310 if converted_fname is not None:
311 return converted_fname
312
313 _log.warning('conversion failed')
314 if not ignore_conversion_problems:
315 return None
316
317 if filename.endswith('.dat'):
318 if adjust_extension:
319 filename = gmMimeLib.adjust_extension_by_mimetype(filename)
320 _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
321 return filename
322
323 #--------------------------------------------------------
325 cmd = """
326 SELECT
327 reviewer,
328 reviewed_when,
329 is_technically_abnormal,
330 clinically_relevant,
331 is_review_by_responsible_reviewer,
332 is_your_review,
333 coalesce(comment, '')
334 FROM blobs.v_reviewed_doc_objects
335 WHERE pk_doc_obj = %s
336 ORDER BY
337 is_your_review desc,
338 is_review_by_responsible_reviewer desc,
339 reviewed_when desc
340 """
341 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
342 return rows
343
344 #--------------------------------------------------------
346 return cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
347
348 containing_document = property(__get_containing_document)
349
350 #--------------------------------------------------------
351 # store data
352 #--------------------------------------------------------
354 # sanity check
355 if not (os.access(fname, os.R_OK) and os.path.isfile(fname)):
356 _log.error('[%s] is not a readable file' % fname)
357 return False
358
359 if not gmPG2.file2bytea (
360 conn = link_obj,
361 query = "UPDATE blobs.doc_obj SET data = %(data)s::bytea WHERE pk = %(pk)s RETURNING md5(data) AS md5",
362 filename = fname,
363 args = {'pk': self.pk_obj},
364 file_md5 = gmTools.file2md5(filename = fname, return_hex = True)
365 ):
366 return False
367
368 # must update XMIN now ...
369 self.refetch_payload(link_obj = link_obj)
370 return True
371
372 #--------------------------------------------------------
374 # row already there ?
375 cmd = """
376 select pk
377 from blobs.reviewed_doc_objs
378 where
379 fk_reviewed_row = %s and
380 fk_reviewer = (select pk from dem.staff where db_user = current_user)"""
381 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
382
383 # INSERT needed
384 if len(rows) == 0:
385 cols = [
386 "fk_reviewer",
387 "fk_reviewed_row",
388 "is_technically_abnormal",
389 "clinically_relevant"
390 ]
391 vals = [
392 '%(fk_row)s',
393 '%(abnormal)s',
394 '%(relevant)s'
395 ]
396 args = {
397 'fk_row': self.pk_obj,
398 'abnormal': technically_abnormal,
399 'relevant': clinically_relevant
400 }
401 cmd = """
402 insert into blobs.reviewed_doc_objs (
403 %s
404 ) values (
405 (select pk from dem.staff where db_user=current_user),
406 %s
407 )""" % (', '.join(cols), ', '.join(vals))
408
409 # UPDATE needed
410 if len(rows) == 1:
411 pk_review = rows[0][0]
412 args = {
413 'abnormal': technically_abnormal,
414 'relevant': clinically_relevant,
415 'pk_review': pk_review
416 }
417 cmd = """
418 UPDATE blobs.reviewed_doc_objs SET
419 is_technically_abnormal = %(abnormal)s,
420 clinically_relevant = %(relevant)s
421 WHERE
422 pk = %(pk_review)s
423 """
424 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
425
426 return True
427
428 #--------------------------------------------------------
430 if self._payload[self._idx['type']] != 'patient photograph':
431 return False
432 # set seq_idx to current max + 1
433 cmd = 'SELECT coalesce(max(seq_idx)+1, 1) FROM blobs.doc_obj WHERE fk_doc = %(doc_id)s'
434 rows, idx = gmPG2.run_ro_queries (
435 queries = [{
436 'cmd': cmd,
437 'args': {'doc_id': self._payload[self._idx['pk_doc']]}
438 }]
439 )
440 self._payload[self._idx['seq_idx']] = rows[0][0]
441 self._is_modified = True
442 self.save_payload()
443
444 #--------------------------------------------------------
446 if pk_doc == self._payload[self._idx['pk_doc']]:
447 return True
448
449 cmd = """
450 UPDATE blobs.doc_obj SET
451 fk_doc = %(pk_doc_target)s,
452 -- coalesce needed for no-parts target docs
453 seq_idx = (SELECT coalesce(max(seq_idx) + 1, 1) FROM blobs.doc_obj WHERE fk_doc = %(pk_doc_target)s)
454 WHERE
455 EXISTS(SELECT 1 FROM blobs.doc_med WHERE pk = %(pk_doc_target)s)
456 AND
457 pk = %(pk_obj)s
458 AND
459 xmin = %(xmin_doc_obj)s
460 RETURNING fk_doc
461 """
462 args = {
463 'pk_doc_target': pk_doc,
464 'pk_obj': self.pk_obj,
465 'xmin_doc_obj': self._payload[self._idx['xmin_doc_obj']]
466 }
467 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
468 if len(rows) == 0:
469 return False
470 # The following should never hold true because the target
471 # fk_doc is returned from the query and it is checked for
472 # equality before the UPDATE already. Assuming the update
473 # failed to update a row because the target fk_doc did
474 # not exist we would not get *any* rows in return - for
475 # which condition we also already checked
476 if rows[0]['fk_doc'] == self._payload[self._idx['pk_doc']]:
477 return False
478
479 self.refetch_payload()
480 return True
481
482 #--------------------------------------------------------
484
485 fname = self.save_to_file(aChunkSize = chunksize)
486 if fname is None:
487 return False, ''
488
489 success, msg = gmMimeLib.call_viewer_on_file(fname, block = block)
490 if not success:
491 return False, msg
492
493 return True, ''
494
495 #--------------------------------------------------------
497 f_ext = ''
498 if self._payload[self._idx['filename']] is not None:
499 f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip()
500 if f_ext != '':
501 f_ext = ' .' + f_ext.upper()
502 txt = _('part %s, %s%s%s of document %s from %s%s') % (
503 self._payload[self._idx['seq_idx']],
504 gmTools.size2str(self._payload[self._idx['size']]),
505 f_ext,
506 gmTools.coalesce(self._payload[self._idx['obj_comment']], '', ' ("%s")'),
507 self._payload[self._idx['l10n_type']],
508 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y %b %d'),
509 gmTools.coalesce(self._payload[self._idx['doc_comment']], '', ' ("%s")')
510 )
511 return txt
512
513 #--------------------------------------------------------
515 if single_line:
516 return self.format_single_line()
517
518 txt = _('%s document part [#%s]\n') % (
519 gmTools.bool2str (
520 boolean = self._payload[self._idx['reviewed']],
521 true_str = _('Reviewed'),
522 false_str = _('Unreviewed')
523 ),
524 self._payload[self._idx['pk_obj']]
525 )
526 f_ext = ''
527 if self._payload[self._idx['filename']] is not None:
528 f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip()
529 if f_ext != '':
530 f_ext = '.' + f_ext.upper() + ' '
531 txt += _(' Part %s: %s %s(%s Bytes)\n') % (
532 self._payload[self._idx['seq_idx']],
533 gmTools.size2str(self._payload[self._idx['size']]),
534 f_ext,
535 self._payload[self._idx['size']]
536 )
537 if self._payload[self._idx['filename']] is not None:
538 path, fname = os.path.split(self._payload[self._idx['filename']])
539 if not path.endswith(os.path.sep):
540 if path != '':
541 path += os.path.sep
542 if path != '':
543 path = ' (%s)' % path
544 txt += _(' Filename: %s%s\n') % (fname, path)
545 if self._payload[self._idx['obj_comment']] is not None:
546 txt += '\n%s\n' % self._payload[self._idx['obj_comment']]
547 return txt
548
549 #--------------------------------------------------------
551 """If <callback> is not None it will receive a tuple (status, description, pk_obj)."""
552 if callback is None:
553 return self.__run_metainfo_formatter()
554
555 gmWorkerThread.execute_in_worker_thread (
556 payload_function = self.__run_metainfo_formatter,
557 completion_callback = callback,
558 worker_name = 'doc_part-metainfo_formatter-'
559 )
560
561 #--------------------------------------------------------
562 - def get_useful_filename(self, patient=None, make_unique=False, directory=None, include_gnumed_tag=True, date_before_type=False, name_first=True):
563 patient_part = ''
564 if patient is not None:
565 if name_first:
566 patient_part = '%s-' % patient.subdir_name
567 else:
568 patient_part = '-%s' % patient.subdir_name
569
570 # preserve original filename extension if available
571 suffix = '.dat'
572 if self._payload[self._idx['filename']] is not None:
573 tmp, suffix = os.path.splitext (
574 gmTools.fname_sanitize(self._payload[self._idx['filename']]).lower()
575 )
576 if suffix == '':
577 suffix = '.dat'
578
579 if include_gnumed_tag:
580 fname_template = 'gm_doc-part_%s-%%s' % self._payload[self._idx['seq_idx']]
581 else:
582 fname_template = '%%s-part_%s' % self._payload[self._idx['seq_idx']]
583
584 if date_before_type:
585 date_type_part = '%s-%s' % (
586 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y-%m-%d', 'utf-8', gmDateTime.acc_days),
587 self._payload[self._idx['l10n_type']].replace(' ', '_').replace('-', '_'),
588 )
589 else:
590 date_type_part = '%s-%s' % (
591 self._payload[self._idx['l10n_type']].replace(' ', '_').replace('-', '_'),
592 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y-%m-%d', 'utf-8', gmDateTime.acc_days)
593 )
594
595 if name_first:
596 date_type_name_part = patient_part + date_type_part
597 else:
598 date_type_name_part = date_type_part + patient_part
599
600 fname = fname_template % date_type_name_part
601
602 if make_unique:
603 fname = gmTools.get_unique_filename (
604 prefix = '%s-' % gmTools.fname_sanitize(fname),
605 suffix = suffix,
606 tmp_dir = directory
607 )
608 else:
609 fname = gmTools.fname_sanitize(os.path.join(gmTools.coalesce(directory, gmTools.gmPaths().tmp_dir), fname + suffix))
610
611 return fname
612
613 useful_filename = property(get_useful_filename)
614
615 #--------------------------------------------------------
616 # internal helpers
617 #--------------------------------------------------------
619 if self._payload[self._idx['size']] == 0:
620 _log.debug('part size 0, nothing to download')
621 return None
622
623 if filename is None:
624 filename = gmTools.get_unique_filename()
625 success = gmPG2.bytea2file (
626 data_query = {
627 'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM blobs.doc_obj WHERE pk=%(pk)s',
628 'args': {'pk': self.pk_obj}
629 },
630 filename = filename,
631 chunk_size = aChunkSize,
632 data_size = self._payload[self._idx['size']],
633 conn = conn
634 )
635 if not success:
636 return None
637
638 return filename
639
640 #--------------------------------------------------------
649
650 #------------------------------------------------------------
652 cmd = """
653 SELECT blobs.delete_document_part(%(pk)s, %(enc)s)
654 WHERE NOT EXISTS
655 (SELECT 1 FROM clin.export_item where fk_doc_obj = %(pk)s)
656 """
657 args = {'pk': part_pk, 'enc': encounter_pk}
658 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
659 return
660
661 #============================================================
662 _SQL_get_document_fields = "SELECT * FROM blobs.v_doc_med b_vdm WHERE %s"
663
665 """Represents one medical document."""
666
667 _cmd_fetch_payload = _SQL_get_document_fields % "pk_doc = %s"
668 _cmds_store_payload = [
669 """UPDATE blobs.doc_med SET
670 fk_type = %(pk_type)s,
671 fk_episode = %(pk_episode)s,
672 fk_encounter = %(pk_encounter)s,
673 fk_org_unit = %(pk_org_unit)s,
674 unit_is_receiver = %(unit_is_receiver)s,
675 clin_when = %(clin_when)s,
676 comment = gm.nullify_empty_string(%(comment)s),
677 ext_ref = gm.nullify_empty_string(%(ext_ref)s),
678 fk_hospital_stay = %(pk_hospital_stay)s
679 WHERE
680 pk = %(pk_doc)s and
681 xmin = %(xmin_doc_med)s
682 RETURNING
683 xmin AS xmin_doc_med"""
684 ]
685 _updatable_fields = [
686 'pk_type',
687 'comment',
688 'clin_when',
689 'ext_ref',
690 'pk_episode',
691 'pk_encounter', # mainly useful when moving visual progress notes to their respective encounters
692 'pk_org_unit',
693 'unit_is_receiver',
694 'pk_hospital_stay'
695 ]
696
697 #--------------------------------------------------------
699 try: del self.__has_unreviewed_parts
700 except AttributeError: pass
701
702 return super(cDocument, self).refetch_payload(ignore_changes = ignore_changes, link_obj = link_obj)
703
704 #--------------------------------------------------------
706 """Get document descriptions.
707
708 - will return a list of rows
709 """
710 if max_lng is None:
711 cmd = "SELECT pk, text FROM blobs.doc_desc WHERE fk_doc = %s"
712 else:
713 cmd = "SELECT pk, substring(text from 1 for %s) FROM blobs.doc_desc WHERE fk_doc=%%s" % max_lng
714 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
715 return rows
716
717 #--------------------------------------------------------
719 cmd = "insert into blobs.doc_desc (fk_doc, text) values (%s, %s)"
720 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj, description]}])
721 return True
722
723 #--------------------------------------------------------
725 cmd = "update blobs.doc_desc set text = %(desc)s where fk_doc = %(doc)s and pk = %(pk_desc)s"
726 gmPG2.run_rw_queries(queries = [
727 {'cmd': cmd, 'args': {'doc': self.pk_obj, 'pk_desc': pk, 'desc': description}}
728 ])
729 return True
730
731 #--------------------------------------------------------
733 cmd = "delete from blobs.doc_desc where fk_doc = %(doc)s and pk = %(desc)s"
734 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'doc': self.pk_obj, 'desc': pk}}])
735 return True
736
737 #--------------------------------------------------------
739 cmd = _SQL_get_document_part_fields % "pk_doc = %s ORDER BY seq_idx"
740 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
741 return [ cDocumentPart(row = {'pk_field': 'pk_obj', 'idx': idx, 'data': r}) for r in rows ]
742
743 parts = property(_get_parts, lambda x:x)
744
745 #--------------------------------------------------------
747 """Add a part to the document."""
748 # create dummy part
749 cmd = """
750 INSERT INTO blobs.doc_obj (
751 fk_doc, data, seq_idx
752 ) VALUES (
753 %(doc_id)s,
754 ''::bytea,
755 (SELECT coalesce(max(seq_idx)+1, 1) FROM blobs.doc_obj WHERE fk_doc = %(doc_id)s)
756 ) RETURNING pk"""
757 rows, idx = gmPG2.run_rw_queries (
758 link_obj = link_obj,
759 queries = [{'cmd': cmd, 'args': {'doc_id': self.pk_obj}}],
760 return_data = True
761 )
762 # init document part instance
763 pk_part = rows[0][0]
764 new_part = cDocumentPart(aPK_obj = pk_part, link_obj = link_obj)
765 if not new_part.update_data_from_file(link_obj = link_obj, fname = file):
766 _log.error('cannot import binary data from [%s] into document part' % file)
767 gmPG2.run_rw_queries (
768 link_obj = link_obj,
769 queries = [{'cmd': "DELETE FROM blobs.doc_obj WHERE pk = %s", 'args': [pk_part]}]
770 )
771 return None
772 new_part['filename'] = file
773 new_part.save_payload(conn = link_obj)
774
775 return new_part
776
777 #--------------------------------------------------------
779
780 new_parts = []
781
782 for filename in files:
783 new_part = self.add_part(file = filename)
784 if new_part is None:
785 msg = 'cannot instantiate document part object from [%s]' % filename
786 _log.error(msg)
787 return (False, msg, filename)
788 new_parts.append(new_part)
789
790 if reviewer is not None:
791 new_part['pk_intended_reviewer'] = reviewer # None == Null
792 success, data = new_part.save_payload()
793 if not success:
794 msg = 'cannot set reviewer to [%s] on [%s]' % (reviewer, filename)
795 _log.error(msg)
796 _log.error(str(data))
797 return (False, msg, filename)
798
799 return (True, '', new_parts)
800
801 #--------------------------------------------------------
803 fnames = []
804 for part in self.parts:
805 fname = part.save_to_file(aChunkSize = chunksize, directory = export_dir, conn = conn)
806 if fname is None:
807 _log.error('cannot export document part [%s]', part)
808 continue
809 fnames.append(fname)
810 return fnames
811
812 #--------------------------------------------------------
814 try:
815 return self.__has_unreviewed_parts
816 except AttributeError:
817 pass
818
819 cmd = "SELECT EXISTS(SELECT 1 FROM blobs.v_obj4doc_no_data WHERE pk_doc = %(pk)s AND reviewed IS FALSE)"
820 args = {'pk': self.pk_obj}
821 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
822 self.__has_unreviewed_parts = rows[0][0]
823
824 return self.__has_unreviewed_parts
825
826 has_unreviewed_parts = property(_get_has_unreviewed_parts, lambda x:x)
827
828 #--------------------------------------------------------
830 # FIXME: this is probably inefficient
831 for part in self.parts:
832 if not part.set_reviewed(technically_abnormal, clinically_relevant):
833 return False
834 return True
835
836 #--------------------------------------------------------
838 for part in self.parts:
839 part['pk_intended_reviewer'] = reviewer
840 success, data = part.save_payload()
841 if not success:
842 _log.error('cannot set reviewer to [%s]' % reviewer)
843 _log.error(str(data))
844 return False
845 return True
846
847 #--------------------------------------------------------
849
850 part_count = len(self._payload[self._idx['seq_idx_list']])
851 if part_count == 0:
852 parts = _('no parts')
853 elif part_count == 1:
854 parts = _('1 part')
855 else:
856 parts = _('%s parts') % part_count
857
858 detail = ''
859 if self._payload[self._idx['ext_ref']] is not None:
860 detail = self._payload[self._idx['ext_ref']]
861 if self._payload[self._idx['unit']] is not None:
862 template = _('%s of %s')
863 if detail == '':
864 detail = _('%s of %s') % (
865 self._payload[self._idx['unit']],
866 self._payload[self._idx['organization']]
867 )
868 else:
869 detail += (' @ ' + template % (
870 self._payload[self._idx['unit']],
871 self._payload[self._idx['organization']]
872 ))
873 if detail != '':
874 detail = ' (%s)' % detail
875
876 return '%s %s (%s):%s%s' % (
877 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d', accuracy = gmDateTime.acc_days),
878 self._payload[self._idx['l10n_type']],
879 parts,
880 gmTools.coalesce(self._payload[self._idx['comment']], '', ' "%s"'),
881 detail
882 )
883
884 #--------------------------------------------------------
886 if single_line:
887 return self.format_single_line()
888
889 part_count = len(self._payload[self._idx['seq_idx_list']])
890 if part_count == 0:
891 parts = _('no parts')
892 elif part_count == 1:
893 parts = _('1 part')
894 else:
895 parts = _('%s parts') % part_count
896 org = ''
897 if self._payload[self._idx['unit']] is not None:
898 if self._payload[self._idx['unit_is_receiver']]:
899 org = _(' Receiver: %s @ %s\n') % (
900 self._payload[self._idx['unit']],
901 self._payload[self._idx['organization']]
902 )
903 else:
904 org = _(' Sender: %s @ %s\n') % (
905 self._payload[self._idx['unit']],
906 self._payload[self._idx['organization']]
907 )
908 stay = ''
909 if self._payload[self._idx['pk_hospital_stay']] is not None:
910 stay = _('Hospital stay') + ': %s\n' % self.hospital_stay.format (
911 left_margin = 0,
912 include_procedures = False,
913 include_docs = False,
914 include_episode = False
915 )
916
917 txt = _(
918 '%s (%s) #%s\n'
919 ' Created: %s\n'
920 ' Episode: %s\n'
921 '%s'
922 '%s'
923 '%s'
924 '%s'
925 '%s'
926 ) % (
927 self._payload[self._idx['l10n_type']],
928 parts,
929 self._payload[self._idx['pk_doc']],
930 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], format = '%Y %b %d', accuracy = gmDateTime.acc_days),
931 self._payload[self._idx['episode']],
932 gmTools.coalesce(self._payload[self._idx['health_issue']], '', _(' Health issue: %s\n')),
933 gmTools.coalesce(self._payload[self._idx['ext_ref']], '', _(' External reference: %s\n')),
934 org,
935 stay,
936 gmTools.coalesce(self._payload[self._idx['comment']], '', ' %s')
937 )
938
939 return txt
940
941 #--------------------------------------------------------
943 if self._payload[self._idx['pk_hospital_stay']] is None:
944 return None
945 from Gnumed.business import gmEMRStructItems
946 return gmEMRStructItems.cHospitalStay(self._payload[self._idx['pk_hospital_stay']])
947
948 hospital_stay = property(_get_hospital_stay, lambda x:x)
949
950 #--------------------------------------------------------
952 if self._payload[self._idx['pk_org_unit']] is None:
953 return None
954 return gmOrganization.cOrgUnit(self._payload[self._idx['pk_org_unit']])
955
956 org_unit = property(_get_org_unit, lambda x:x)
957
958 #--------------------------------------------------------
960 from Gnumed.business.gmEMRStructItems import get_procedures4document
961 return get_procedures4document(pk_document = self.pk_obj)
962
963 procedures = property(_get_procedures, lambda x:x)
964
965 #--------------------------------------------------------
967 from Gnumed.business.gmBilling import get_bills4document
968 return get_bills4document(pk_document = self.pk_obj)
969
970 bills = property(_get_bills, lambda x:x)
971
972 #------------------------------------------------------------
974 """Returns new document instance or raises an exception."""
975 try:
976 int(document_type)
977 cmd = """INSERT INTO blobs.doc_med (fk_type, fk_encounter, fk_episode) VALUES (%(type)s, %(enc)s, %(epi)s) RETURNING pk"""
978 except ValueError:
979 create_document_type(document_type = document_type)
980 cmd = """
981 INSERT INTO blobs.doc_med (
982 fk_type,
983 fk_encounter,
984 fk_episode
985 ) VALUES (
986 coalesce (
987 (SELECT pk from blobs.doc_type bdt where bdt.name = %(type)s),
988 (SELECT pk from blobs.doc_type bdt where _(bdt.name) = %(type)s)
989 ),
990 %(enc)s,
991 %(epi)s
992 ) RETURNING pk"""
993 args = {'type': document_type, 'enc': encounter, 'epi': episode}
994 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True)
995 doc = cDocument(aPK_obj = rows[0][0], link_obj = link_obj)
996 return doc
997
998 #------------------------------------------------------------
999 -def search_for_documents(patient_id=None, type_id=None, external_reference=None, pk_episode=None, pk_types=None):
1000 """Searches for documents with the given patient and type ID."""
1001
1002 if (patient_id is None) and (pk_episode is None):
1003 raise ValueError('need patient_id or pk_episode to search for document')
1004
1005 where_parts = []
1006 args = {
1007 'pat_id': patient_id,
1008 'type_id': type_id,
1009 'ref': external_reference,
1010 'pk_epi': pk_episode
1011 }
1012
1013 if patient_id is not None:
1014 where_parts.append('pk_patient = %(pat_id)s')
1015
1016 if type_id is not None:
1017 where_parts.append('pk_type = %(type_id)s')
1018
1019 if external_reference is not None:
1020 where_parts.append('ext_ref = %(ref)s')
1021
1022 if pk_episode is not None:
1023 where_parts.append('pk_episode = %(pk_epi)s')
1024
1025 if pk_types is not None:
1026 where_parts.append('pk_type IN %(pk_types)s')
1027 args['pk_types'] = tuple(pk_types)
1028
1029 cmd = _SQL_get_document_fields % ' AND '.join(where_parts)
1030 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1031 return [ cDocument(row = {'data': r, 'idx': idx, 'pk_field': 'pk_doc'}) for r in rows ]
1032
1033 #------------------------------------------------------------
1035 # cascades to doc_obj and doc_desc but not bill.bill
1036 cmd = "SELECT blobs.delete_document(%(pk)s, %(enc)s)"
1037 args = {'pk': document_id, 'enc': encounter_id}
1038 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
1039 if not rows[0][0]:
1040 _log.error('cannot delete document [%s]', document_id)
1041 return False
1042 return True
1043
1044 #------------------------------------------------------------
1046
1047 _log.debug('reclassifying documents by type')
1048 _log.debug('original: %s', original_type)
1049 _log.debug('target: %s', target_type)
1050
1051 if target_type['pk_doc_type'] == original_type['pk_doc_type']:
1052 return True
1053
1054 cmd = """
1055 update blobs.doc_med set
1056 fk_type = %(new_type)s
1057 where
1058 fk_type = %(old_type)s
1059 """
1060 args = {'new_type': target_type['pk_doc_type'], 'old_type': original_type['pk_doc_type']}
1061
1062 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1063
1064 return True
1065
1066 #============================================================
1068 """Represents a document type."""
1069 _cmd_fetch_payload = """select * from blobs.v_doc_type where pk_doc_type=%s"""
1070 _cmds_store_payload = [
1071 """update blobs.doc_type set
1072 name = %(type)s
1073 where
1074 pk=%(pk_obj)s and
1075 xmin=%(xmin_doc_type)s""",
1076 """select xmin_doc_type from blobs.v_doc_type where pk_doc_type = %(pk_obj)s"""
1077 ]
1078 _updatable_fields = ['type']
1079 #--------------------------------------------------------
1081
1082 if translation.strip() == '':
1083 return False
1084
1085 if translation.strip() == self._payload[self._idx['l10n_type']].strip():
1086 return True
1087
1088 rows, idx = gmPG2.run_rw_queries (
1089 queries = [
1090 {'cmd': 'select i18n.i18n(%s)', 'args': [self._payload[self._idx['type']]]},
1091 {'cmd': 'select i18n.upd_tx((select i18n.get_curr_lang()), %(orig)s, %(tx)s)',
1092 'args': {
1093 'orig': self._payload[self._idx['type']],
1094 'tx': translation
1095 }
1096 }
1097 ],
1098 return_data = True
1099 )
1100 if not rows[0][0]:
1101 _log.error('cannot set translation to [%s]' % translation)
1102 return False
1103
1104 return self.refetch_payload()
1105
1106 #------------------------------------------------------------
1108 rows, idx = gmPG2.run_ro_queries (
1109 queries = [{'cmd': "SELECT * FROM blobs.v_doc_type"}],
1110 get_col_idx = True
1111 )
1112 doc_types = []
1113 for row in rows:
1114 row_def = {'pk_field': 'pk_doc_type', 'idx': idx, 'data': row}
1115 doc_types.append(cDocumentType(row = row_def))
1116 return doc_types
1117
1118 #------------------------------------------------------------
1120 args = {'typ': document_type.strip()}
1121
1122 cmd = 'SELECT pk FROM blobs.doc_type WHERE name = %(typ)s'
1123 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1124 if len(rows) == 0:
1125 cmd = 'SELECT pk FROM blobs.doc_type WHERE _(name) = %(typ)s'
1126 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1127
1128 if len(rows) == 0:
1129 return None
1130
1131 return rows[0]['pk']
1132
1133 #------------------------------------------------------------
1135 args = {'types': tuple(document_types)}
1136 cmd = 'SELECT pk_doc_type, coalesce(l10n_type, type) as desc FROM blobs.v_doc_type WHERE l10n_type IN %(types)s OR type IN %(types)s'
1137 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1138 return rows
1139
1140 #------------------------------------------------------------
1142 # check for potential dupes:
1143 cmd = 'SELECT pk FROM blobs.doc_type WHERE name = %s'
1144 rows, idx = gmPG2.run_ro_queries (
1145 queries = [{'cmd': cmd, 'args': [document_type]}]
1146 )
1147 if len(rows) == 0:
1148 _log.debug('creating document type [%s]', document_type)
1149 cmd1 = "INSERT INTO blobs.doc_type (name) VALUES (%s) RETURNING pk"
1150 rows, idx = gmPG2.run_rw_queries (
1151 queries = [{'cmd': cmd1, 'args': [document_type]}],
1152 return_data = True
1153 )
1154 return cDocumentType(aPK_obj = rows[0][0])
1155
1156 #------------------------------------------------------------
1158 if document_type['is_in_use']:
1159 return False
1160 gmPG2.run_rw_queries (
1161 queries = [{
1162 'cmd': 'delete from blobs.doc_type where pk=%s',
1163 'args': [document_type['pk_doc_type']]
1164 }]
1165 )
1166 return True
1167
1168 #------------------------------------------------------------
1170 """This needs *considerably* more smarts."""
1171 dirname = gmTools.get_unique_filename (
1172 prefix = '',
1173 suffix = time.strftime(".%Y%m%d-%H%M%S", time.localtime())
1174 )
1175 # extract name for dir
1176 path, doc_ID = os.path.split(dirname)
1177 return doc_ID
1178
1179 #============================================================
1180 # main
1181 #------------------------------------------------------------
1182 if __name__ == '__main__':
1183
1184 if len(sys.argv) < 2:
1185 sys.exit()
1186
1187 if sys.argv[1] != 'test':
1188 sys.exit()
1189
1190 #--------------------------------------------------------
1192
1193 print("----------------------")
1194 print("listing document types")
1195 print("----------------------")
1196
1197 for dt in get_document_types():
1198 print(dt)
1199
1200 print("------------------------------")
1201 print("testing document type handling")
1202 print("------------------------------")
1203
1204 dt = create_document_type(document_type = 'dummy doc type for unit test 1')
1205 print("created:", dt)
1206
1207 dt['type'] = 'dummy doc type for unit test 2'
1208 dt.save_payload()
1209 print("changed base name:", dt)
1210
1211 dt.set_translation(translation = 'Dummy-Dokumenten-Typ fuer Unit-Test')
1212 print("translated:", dt)
1213
1214 print("deleted:", delete_document_type(document_type = dt))
1215
1216 return
1217 #--------------------------------------------------------
1219
1220 print("-----------------------")
1221 print("testing document import")
1222 print("-----------------------")
1223
1224 docs = search_for_documents(patient_id=12)
1225 doc = docs[0]
1226 print("adding to doc:", doc)
1227
1228 fname = sys.argv[1]
1229 print("adding from file:", fname)
1230 part = doc.add_part(file=fname)
1231 print("new part:", part)
1232
1233 return
1234 #--------------------------------------------------------
1236
1237 doc_folder = cDocumentFolder(aPKey=12)
1238
1239 #photo = doc_folder.get_latest_mugshot()
1240 #print type(photo), photo
1241
1242 docs = doc_folder.get_documents()
1243 for doc in docs:
1244 #print type(doc), doc
1245 #print doc.parts
1246 #print doc.format_single_line()
1247 print('--------------------------')
1248 print(doc.format(single_line = True))
1249 print(doc.format())
1250
1251 #--------------------------------------------------------
1253 pk = 12
1254 from Gnumed.business.gmPerson import cPatient
1255 pat = cPatient(pk)
1256 doc_folder = cDocumentFolder(aPKey = pk)
1257 for doc in doc_folder.documents:
1258 for part in doc.parts:
1259 print(part.get_useful_filename (
1260 patient = pat,
1261 make_unique = True,
1262 directory = None,
1263 include_gnumed_tag = False,
1264 date_before_type = True,
1265 name_first = False
1266 ))
1267
1268 #--------------------------------------------------------
1279 #--------------------------------
1280
1281 pk = 12
1282 from Gnumed.business.gmPerson import cPatient
1283 pat = cPatient(pk)
1284 doc_folder = cDocumentFolder(aPKey = pk)
1285 for doc in doc_folder.documents:
1286 for part in doc.parts:
1287 part.format_metainfo(callback = desc_printer)
1288 input('waiting ...')
1289 # success, desc = part.format_metainfo()
1290 # print(success)
1291 # print(desc)
1292 # input('next')
1293
1294 # print(part.get_useful_filename (
1295 # patient = pat,
1296 # make_unique = True,
1297 # directory = None,
1298 # include_gnumed_tag = False,
1299 # date_before_type = True,
1300 # name_first = False
1301 # ))
1302
1303 #--------------------------------------------------------
1304 from Gnumed.pycommon import gmI18N
1305 gmI18N.activate_locale()
1306 gmI18N.install_domain()
1307
1308 #test_doc_types()
1309 #test_adding_doc_part()
1310 #test_get_documents()
1311 #test_get_useful_filename()
1312 test_part_metainfo_formatter()
1313
1314 # print get_ext_ref()
1315
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Nov 10 02:55:34 2019 | http://epydoc.sourceforge.net |