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
38 if not self._pkey_exists():
39 raise gmExceptions.ConstructorError("No patient with PK [%s] in database." % aPKey)
40
41
42
43
44
45
46
47 _log.debug('instantiated document folder for patient [%s]' % self.pk_patient)
48
51
52
53
55 """Does this primary key exist ?
56
57 - true/false/None
58 """
59
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
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
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
226 - def add_document(self, document_type=None, encounter=None, episode=None, link_obj=None):
227 return create_document(link_obj = link_obj, document_type = document_type, encounter = encounter, episode = episode)
228
229
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
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
352
354
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
369 self.refetch_payload(link_obj = link_obj)
370 return True
371
372
373 - def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
374
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
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
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
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
471
472
473
474
475
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
512
513
548
549
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
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
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
664 -class cDocument(gmBusinessDBObject.cBusinessDBObject):
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',
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
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
742
743 parts = property(_get_parts, lambda x:x)
744
745
746 - def add_part(self, file=None, link_obj=None):
747 """Add a part to the document."""
748
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
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
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
829 - def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
830
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
883
884
940
941
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
962
963 procedures = property(_get_procedures, lambda x:x)
964
965
969
970 bills = property(_get_bills, lambda x:x)
971
972
973 -def create_document(document_type=None, encounter=None, episode=None, link_obj=None):
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
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
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
1176 path, doc_ID = os.path.split(dirname)
1177 return doc_ID
1178
1179
1180
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
1240
1241
1242 docs = doc_folder.get_documents()
1243 for doc in docs:
1244
1245
1246
1247 print('--------------------------')
1248 print(doc.format(single_line = True))
1249 print(doc.format())
1250
1251
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
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304 from Gnumed.pycommon import gmI18N
1305 gmI18N.activate_locale()
1306 gmI18N.install_domain()
1307
1308
1309
1310
1311
1312 test_part_metainfo_formatter()
1313
1314
1315