| 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 if gmMimeLib.convert_file (
306 filename = filename,
307 target_mime = target_mime,
308 target_filename = target_fname
309 ):
310 return target_fname
311
312 _log.warning('conversion failed')
313 if not ignore_conversion_problems:
314 return None
315
316 if filename.endswith('.dat'):
317 if adjust_extension:
318 filename = gmMimeLib.adjust_extension_by_mimetype(filename)
319 _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
320 return filename
321
322 #--------------------------------------------------------
324 cmd = """
325 SELECT
326 reviewer,
327 reviewed_when,
328 is_technically_abnormal,
329 clinically_relevant,
330 is_review_by_responsible_reviewer,
331 is_your_review,
332 coalesce(comment, '')
333 FROM blobs.v_reviewed_doc_objects
334 WHERE pk_doc_obj = %s
335 ORDER BY
336 is_your_review desc,
337 is_review_by_responsible_reviewer desc,
338 reviewed_when desc
339 """
340 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
341 return rows
342
343 #--------------------------------------------------------
345 return cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
346
347 containing_document = property(__get_containing_document)
348
349 #--------------------------------------------------------
350 # store data
351 #--------------------------------------------------------
353 # sanity check
354 if not (os.access(fname, os.R_OK) and os.path.isfile(fname)):
355 _log.error('[%s] is not a readable file' % fname)
356 return False
357
358 if not gmPG2.file2bytea (
359 conn = link_obj,
360 query = "UPDATE blobs.doc_obj SET data = %(data)s::bytea WHERE pk = %(pk)s RETURNING md5(data) AS md5",
361 filename = fname,
362 args = {'pk': self.pk_obj},
363 file_md5 = gmTools.file2md5(filename = fname, return_hex = True)
364 ):
365 return False
366
367 # must update XMIN now ...
368 self.refetch_payload(link_obj = link_obj)
369 return True
370
371 #--------------------------------------------------------
373 # row already there ?
374 cmd = """
375 select pk
376 from blobs.reviewed_doc_objs
377 where
378 fk_reviewed_row = %s and
379 fk_reviewer = (select pk from dem.staff where db_user = current_user)"""
380 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
381
382 # INSERT needed
383 if len(rows) == 0:
384 cols = [
385 "fk_reviewer",
386 "fk_reviewed_row",
387 "is_technically_abnormal",
388 "clinically_relevant"
389 ]
390 vals = [
391 '%(fk_row)s',
392 '%(abnormal)s',
393 '%(relevant)s'
394 ]
395 args = {
396 'fk_row': self.pk_obj,
397 'abnormal': technically_abnormal,
398 'relevant': clinically_relevant
399 }
400 cmd = """
401 insert into blobs.reviewed_doc_objs (
402 %s
403 ) values (
404 (select pk from dem.staff where db_user=current_user),
405 %s
406 )""" % (', '.join(cols), ', '.join(vals))
407
408 # UPDATE needed
409 if len(rows) == 1:
410 pk_review = rows[0][0]
411 args = {
412 'abnormal': technically_abnormal,
413 'relevant': clinically_relevant,
414 'pk_review': pk_review
415 }
416 cmd = """
417 UPDATE blobs.reviewed_doc_objs SET
418 is_technically_abnormal = %(abnormal)s,
419 clinically_relevant = %(relevant)s
420 WHERE
421 pk = %(pk_review)s
422 """
423 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
424
425 return True
426
427 #--------------------------------------------------------
429 if self._payload[self._idx['type']] != 'patient photograph':
430 return False
431 # set seq_idx to current max + 1
432 cmd = 'SELECT coalesce(max(seq_idx)+1, 1) FROM blobs.doc_obj WHERE fk_doc = %(doc_id)s'
433 rows, idx = gmPG2.run_ro_queries (
434 queries = [{
435 'cmd': cmd,
436 'args': {'doc_id': self._payload[self._idx['pk_doc']]}
437 }]
438 )
439 self._payload[self._idx['seq_idx']] = rows[0][0]
440 self._is_modified = True
441 self.save_payload()
442
443 #--------------------------------------------------------
445 if pk_doc == self._payload[self._idx['pk_doc']]:
446 return True
447
448 cmd = """
449 UPDATE blobs.doc_obj SET
450 fk_doc = %(pk_doc_target)s,
451 -- coalesce needed for no-parts target docs
452 seq_idx = (SELECT coalesce(max(seq_idx) + 1, 1) FROM blobs.doc_obj WHERE fk_doc = %(pk_doc_target)s)
453 WHERE
454 EXISTS(SELECT 1 FROM blobs.doc_med WHERE pk = %(pk_doc_target)s)
455 AND
456 pk = %(pk_obj)s
457 AND
458 xmin = %(xmin_doc_obj)s
459 RETURNING fk_doc
460 """
461 args = {
462 'pk_doc_target': pk_doc,
463 'pk_obj': self.pk_obj,
464 'xmin_doc_obj': self._payload[self._idx['xmin_doc_obj']]
465 }
466 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
467 if len(rows) == 0:
468 return False
469 # The following should never hold true because the target
470 # fk_doc is returned from the query and it is checked for
471 # equality before the UPDATE already. Assuming the update
472 # failed to update a row because the target fk_doc did
473 # not exist we would not get *any* rows in return - for
474 # which condition we also already checked
475 if rows[0]['fk_doc'] == self._payload[self._idx['pk_doc']]:
476 return False
477
478 self.refetch_payload()
479 return True
480
481 #--------------------------------------------------------
483
484 fname = self.save_to_file(aChunkSize = chunksize)
485 if fname is None:
486 return False, ''
487
488 success, msg = gmMimeLib.call_viewer_on_file(fname, block = block)
489 if not success:
490 return False, msg
491
492 return True, ''
493
494 #--------------------------------------------------------
496 f_ext = ''
497 if self._payload[self._idx['filename']] is not None:
498 f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip()
499 if f_ext != '':
500 f_ext = ' .' + f_ext.upper()
501 txt = _('part %s, %s%s%s of document %s from %s%s') % (
502 self._payload[self._idx['seq_idx']],
503 gmTools.size2str(self._payload[self._idx['size']]),
504 f_ext,
505 gmTools.coalesce(self._payload[self._idx['obj_comment']], '', ' ("%s")'),
506 self._payload[self._idx['l10n_type']],
507 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y %b %d'),
508 gmTools.coalesce(self._payload[self._idx['doc_comment']], '', ' ("%s")')
509 )
510 return txt
511
512 #--------------------------------------------------------
514 if single_line:
515 return self.format_single_line()
516
517 txt = _('%s document part [#%s]\n') % (
518 gmTools.bool2str (
519 boolean = self._payload[self._idx['reviewed']],
520 true_str = _('Reviewed'),
521 false_str = _('Unreviewed')
522 ),
523 self._payload[self._idx['pk_obj']]
524 )
525 f_ext = ''
526 if self._payload[self._idx['filename']] is not None:
527 f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip()
528 if f_ext != '':
529 f_ext = '.' + f_ext.upper() + ' '
530 txt += _(' Part %s: %s %s(%s Bytes)\n') % (
531 self._payload[self._idx['seq_idx']],
532 gmTools.size2str(self._payload[self._idx['size']]),
533 f_ext,
534 self._payload[self._idx['size']]
535 )
536 if self._payload[self._idx['filename']] is not None:
537 path, fname = os.path.split(self._payload[self._idx['filename']])
538 if not path.endswith(os.path.sep):
539 if path != '':
540 path += os.path.sep
541 if path != '':
542 path = ' (%s)' % path
543 txt += _(' Filename: %s%s\n') % (fname, path)
544 if self._payload[self._idx['obj_comment']] is not None:
545 txt += '\n%s\n' % self._payload[self._idx['obj_comment']]
546 return txt
547
548 #--------------------------------------------------------
550 """If <callback> is not None it will receive a tuple (status, description, pk_obj)."""
551 if callback is None:
552 return self.__run_metainfo_formatter()
553
554 gmWorkerThread.execute_in_worker_thread (
555 payload_function = self.__run_metainfo_formatter,
556 completion_callback = callback,
557 worker_name = 'doc_part-metainfo_formatter-'
558 )
559
560 #--------------------------------------------------------
561 - def get_useful_filename(self, patient=None, make_unique=False, directory=None, include_gnumed_tag=True, date_before_type=False, name_first=True):
562 patient_part = ''
563 if patient is not None:
564 if name_first:
565 patient_part = '%s-' % patient.subdir_name
566 else:
567 patient_part = '-%s' % patient.subdir_name
568
569 # preserve original filename extension if available
570 suffix = '.dat'
571 if self._payload[self._idx['filename']] is not None:
572 tmp, suffix = os.path.splitext (
573 gmTools.fname_sanitize(self._payload[self._idx['filename']]).lower()
574 )
575 if suffix == '':
576 suffix = '.dat'
577
578 if include_gnumed_tag:
579 fname_template = 'gm_doc-part_%s-%%s' % self._payload[self._idx['seq_idx']]
580 else:
581 fname_template = '%%s-part_%s' % self._payload[self._idx['seq_idx']]
582
583 if date_before_type:
584 date_type_part = '%s-%s' % (
585 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y-%m-%d', 'utf-8', gmDateTime.acc_days),
586 self._payload[self._idx['l10n_type']].replace(' ', '_').replace('-', '_'),
587 )
588 else:
589 date_type_part = '%s-%s' % (
590 self._payload[self._idx['l10n_type']].replace(' ', '_').replace('-', '_'),
591 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y-%m-%d', 'utf-8', gmDateTime.acc_days)
592 )
593
594 if name_first:
595 date_type_name_part = patient_part + date_type_part
596 else:
597 date_type_name_part = date_type_part + patient_part
598
599 fname = fname_template % date_type_name_part
600
601 if make_unique:
602 fname = gmTools.get_unique_filename (
603 prefix = '%s-' % gmTools.fname_sanitize(fname),
604 suffix = suffix,
605 tmp_dir = directory
606 )
607 else:
608 fname = gmTools.fname_sanitize(os.path.join(gmTools.coalesce(directory, gmTools.gmPaths().tmp_dir), fname + suffix))
609
610 return fname
611
612 useful_filename = property(get_useful_filename)
613
614 #--------------------------------------------------------
615 # internal helpers
616 #--------------------------------------------------------
618 if self._payload[self._idx['size']] == 0:
619 _log.debug('part size 0, nothing to download')
620 return None
621
622 if filename is None:
623 filename = gmTools.get_unique_filename()
624 success = gmPG2.bytea2file (
625 data_query = {
626 'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM blobs.doc_obj WHERE pk=%(pk)s',
627 'args': {'pk': self.pk_obj}
628 },
629 filename = filename,
630 chunk_size = aChunkSize,
631 data_size = self._payload[self._idx['size']],
632 conn = conn
633 )
634 if not success:
635 return None
636
637 return filename
638
639 #--------------------------------------------------------
648
649 #------------------------------------------------------------
651 cmd = """
652 SELECT blobs.delete_document_part(%(pk)s, %(enc)s)
653 WHERE NOT EXISTS
654 (SELECT 1 FROM clin.export_item where fk_doc_obj = %(pk)s)
655 """
656 args = {'pk': part_pk, 'enc': encounter_pk}
657 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
658 return
659
660 #============================================================
661 _SQL_get_document_fields = "SELECT * FROM blobs.v_doc_med b_vdm WHERE %s"
662
664 """Represents one medical document."""
665
666 _cmd_fetch_payload = _SQL_get_document_fields % "pk_doc = %s"
667 _cmds_store_payload = [
668 """UPDATE blobs.doc_med SET
669 fk_type = %(pk_type)s,
670 fk_episode = %(pk_episode)s,
671 fk_encounter = %(pk_encounter)s,
672 fk_org_unit = %(pk_org_unit)s,
673 unit_is_receiver = %(unit_is_receiver)s,
674 clin_when = %(clin_when)s,
675 comment = gm.nullify_empty_string(%(comment)s),
676 ext_ref = gm.nullify_empty_string(%(ext_ref)s),
677 fk_hospital_stay = %(pk_hospital_stay)s
678 WHERE
679 pk = %(pk_doc)s and
680 xmin = %(xmin_doc_med)s
681 RETURNING
682 xmin AS xmin_doc_med"""
683 ]
684 _updatable_fields = [
685 'pk_type',
686 'comment',
687 'clin_when',
688 'ext_ref',
689 'pk_episode',
690 'pk_encounter', # mainly useful when moving visual progress notes to their respective encounters
691 'pk_org_unit',
692 'unit_is_receiver',
693 'pk_hospital_stay'
694 ]
695
696 #--------------------------------------------------------
698 try: del self.__has_unreviewed_parts
699 except AttributeError: pass
700
701 return super(cDocument, self).refetch_payload(ignore_changes = ignore_changes, link_obj = link_obj)
702
703 #--------------------------------------------------------
705 """Get document descriptions.
706
707 - will return a list of rows
708 """
709 if max_lng is None:
710 cmd = "SELECT pk, text FROM blobs.doc_desc WHERE fk_doc = %s"
711 else:
712 cmd = "SELECT pk, substring(text from 1 for %s) FROM blobs.doc_desc WHERE fk_doc=%%s" % max_lng
713 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
714 return rows
715
716 #--------------------------------------------------------
718 cmd = "insert into blobs.doc_desc (fk_doc, text) values (%s, %s)"
719 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj, description]}])
720 return True
721
722 #--------------------------------------------------------
724 cmd = "update blobs.doc_desc set text = %(desc)s where fk_doc = %(doc)s and pk = %(pk_desc)s"
725 gmPG2.run_rw_queries(queries = [
726 {'cmd': cmd, 'args': {'doc': self.pk_obj, 'pk_desc': pk, 'desc': description}}
727 ])
728 return True
729
730 #--------------------------------------------------------
732 cmd = "delete from blobs.doc_desc where fk_doc = %(doc)s and pk = %(desc)s"
733 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'doc': self.pk_obj, 'desc': pk}}])
734 return True
735
736 #--------------------------------------------------------
738 cmd = _SQL_get_document_part_fields % "pk_doc = %s ORDER BY seq_idx"
739 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
740 return [ cDocumentPart(row = {'pk_field': 'pk_obj', 'idx': idx, 'data': r}) for r in rows ]
741
742 parts = property(_get_parts, lambda x:x)
743
744 #--------------------------------------------------------
746 """Add a part to the document."""
747 # create dummy part
748 cmd = """
749 INSERT INTO blobs.doc_obj (
750 fk_doc, data, seq_idx
751 ) VALUES (
752 %(doc_id)s,
753 ''::bytea,
754 (SELECT coalesce(max(seq_idx)+1, 1) FROM blobs.doc_obj WHERE fk_doc = %(doc_id)s)
755 ) RETURNING pk"""
756 rows, idx = gmPG2.run_rw_queries (
757 link_obj = link_obj,
758 queries = [{'cmd': cmd, 'args': {'doc_id': self.pk_obj}}],
759 return_data = True
760 )
761 # init document part instance
762 pk_part = rows[0][0]
763 new_part = cDocumentPart(aPK_obj = pk_part, link_obj = link_obj)
764 if not new_part.update_data_from_file(link_obj = link_obj, fname = file):
765 _log.error('cannot import binary data from [%s] into document part' % file)
766 gmPG2.run_rw_queries (
767 link_obj = link_obj,
768 queries = [{'cmd': "DELETE FROM blobs.doc_obj WHERE pk = %s", 'args': [pk_part]}]
769 )
770 return None
771 new_part['filename'] = file
772 new_part.save_payload(conn = link_obj)
773
774 return new_part
775
776 #--------------------------------------------------------
778
779 new_parts = []
780
781 for filename in files:
782 new_part = self.add_part(file = filename)
783 if new_part is None:
784 msg = 'cannot instantiate document part object from [%s]' % filename
785 _log.error(msg)
786 return (False, msg, filename)
787 new_parts.append(new_part)
788
789 if reviewer is not None:
790 new_part['pk_intended_reviewer'] = reviewer # None == Null
791 success, data = new_part.save_payload()
792 if not success:
793 msg = 'cannot set reviewer to [%s] on [%s]' % (reviewer, filename)
794 _log.error(msg)
795 _log.error(str(data))
796 return (False, msg, filename)
797
798 return (True, '', new_parts)
799
800 #--------------------------------------------------------
802 fnames = []
803 for part in self.parts:
804 fname = part.save_to_file(aChunkSize = chunksize, directory = export_dir, conn = conn)
805 if fname is None:
806 _log.error('cannot export document part [%s]', part)
807 continue
808 fnames.append(fname)
809 return fnames
810
811 #--------------------------------------------------------
813 try:
814 return self.__has_unreviewed_parts
815 except AttributeError:
816 pass
817
818 cmd = "SELECT EXISTS(SELECT 1 FROM blobs.v_obj4doc_no_data WHERE pk_doc = %(pk)s AND reviewed IS FALSE)"
819 args = {'pk': self.pk_obj}
820 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
821 self.__has_unreviewed_parts = rows[0][0]
822
823 return self.__has_unreviewed_parts
824
825 has_unreviewed_parts = property(_get_has_unreviewed_parts, lambda x:x)
826
827 #--------------------------------------------------------
829 # FIXME: this is probably inefficient
830 for part in self.parts:
831 if not part.set_reviewed(technically_abnormal, clinically_relevant):
832 return False
833 return True
834
835 #--------------------------------------------------------
837 for part in self.parts:
838 part['pk_intended_reviewer'] = reviewer
839 success, data = part.save_payload()
840 if not success:
841 _log.error('cannot set reviewer to [%s]' % reviewer)
842 _log.error(str(data))
843 return False
844 return True
845
846 #--------------------------------------------------------
848
849 part_count = len(self._payload[self._idx['seq_idx_list']])
850 if part_count == 0:
851 parts = _('no parts')
852 elif part_count == 1:
853 parts = _('1 part')
854 else:
855 parts = _('%s parts') % part_count
856
857 detail = ''
858 if self._payload[self._idx['ext_ref']] is not None:
859 detail = self._payload[self._idx['ext_ref']]
860 if self._payload[self._idx['unit']] is not None:
861 template = _('%s of %s')
862 if detail == '':
863 detail = _('%s of %s') % (
864 self._payload[self._idx['unit']],
865 self._payload[self._idx['organization']]
866 )
867 else:
868 detail += (' @ ' + template % (
869 self._payload[self._idx['unit']],
870 self._payload[self._idx['organization']]
871 ))
872 if detail != '':
873 detail = ' (%s)' % detail
874
875 return '%s %s (%s):%s%s' % (
876 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d', accuracy = gmDateTime.acc_days),
877 self._payload[self._idx['l10n_type']],
878 parts,
879 gmTools.coalesce(self._payload[self._idx['comment']], '', ' "%s"'),
880 detail
881 )
882
883 #--------------------------------------------------------
885 if single_line:
886 return self.format_single_line()
887
888 part_count = len(self._payload[self._idx['seq_idx_list']])
889 if part_count == 0:
890 parts = _('no parts')
891 elif part_count == 1:
892 parts = _('1 part')
893 else:
894 parts = _('%s parts') % part_count
895 org = ''
896 if self._payload[self._idx['unit']] is not None:
897 if self._payload[self._idx['unit_is_receiver']]:
898 org = _(' Receiver: %s @ %s\n') % (
899 self._payload[self._idx['unit']],
900 self._payload[self._idx['organization']]
901 )
902 else:
903 org = _(' Sender: %s @ %s\n') % (
904 self._payload[self._idx['unit']],
905 self._payload[self._idx['organization']]
906 )
907 stay = ''
908 if self._payload[self._idx['pk_hospital_stay']] is not None:
909 stay = _('Hospital stay') + ': %s\n' % self.hospital_stay.format (
910 left_margin = 0,
911 include_procedures = False,
912 include_docs = False,
913 include_episode = False
914 )
915
916 txt = _(
917 '%s (%s) #%s\n'
918 ' Created: %s\n'
919 ' Episode: %s\n'
920 '%s'
921 '%s'
922 '%s'
923 '%s'
924 '%s'
925 ) % (
926 self._payload[self._idx['l10n_type']],
927 parts,
928 self._payload[self._idx['pk_doc']],
929 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], format = '%Y %b %d', accuracy = gmDateTime.acc_days),
930 self._payload[self._idx['episode']],
931 gmTools.coalesce(self._payload[self._idx['health_issue']], '', _(' Health issue: %s\n')),
932 gmTools.coalesce(self._payload[self._idx['ext_ref']], '', _(' External reference: %s\n')),
933 org,
934 stay,
935 gmTools.coalesce(self._payload[self._idx['comment']], '', ' %s')
936 )
937
938 return txt
939
940 #--------------------------------------------------------
942 if self._payload[self._idx['pk_hospital_stay']] is None:
943 return None
944 from Gnumed.business import gmEMRStructItems
945 return gmEMRStructItems.cHospitalStay(self._payload[self._idx['pk_hospital_stay']])
946
947 hospital_stay = property(_get_hospital_stay, lambda x:x)
948
949 #--------------------------------------------------------
951 if self._payload[self._idx['pk_org_unit']] is None:
952 return None
953 return gmOrganization.cOrgUnit(self._payload[self._idx['pk_org_unit']])
954
955 org_unit = property(_get_org_unit, lambda x:x)
956
957 #--------------------------------------------------------
959 from Gnumed.business.gmEMRStructItems import get_procedures4document
960 return get_procedures4document(pk_document = self.pk_obj)
961
962 procedures = property(_get_procedures, lambda x:x)
963
964 #--------------------------------------------------------
966 from Gnumed.business.gmBilling import get_bills4document
967 return get_bills4document(pk_document = self.pk_obj)
968
969 bills = property(_get_bills, lambda x:x)
970
971 #------------------------------------------------------------
973 """Returns new document instance or raises an exception."""
974 try:
975 int(document_type)
976 cmd = """INSERT INTO blobs.doc_med (fk_type, fk_encounter, fk_episode) VALUES (%(type)s, %(enc)s, %(epi)s) RETURNING pk"""
977 except ValueError:
978 create_document_type(document_type = document_type)
979 cmd = """
980 INSERT INTO blobs.doc_med (
981 fk_type,
982 fk_encounter,
983 fk_episode
984 ) VALUES (
985 coalesce (
986 (SELECT pk from blobs.doc_type bdt where bdt.name = %(type)s),
987 (SELECT pk from blobs.doc_type bdt where _(bdt.name) = %(type)s)
988 ),
989 %(enc)s,
990 %(epi)s
991 ) RETURNING pk"""
992 args = {'type': document_type, 'enc': encounter, 'epi': episode}
993 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True)
994 doc = cDocument(aPK_obj = rows[0][0], link_obj = link_obj)
995 return doc
996
997 #------------------------------------------------------------
998 -def search_for_documents(patient_id=None, type_id=None, external_reference=None, pk_episode=None, pk_types=None):
999 """Searches for documents with the given patient and type ID."""
1000
1001 if (patient_id is None) and (pk_episode is None):
1002 raise ValueError('need patient_id or pk_episode to search for document')
1003
1004 where_parts = []
1005 args = {
1006 'pat_id': patient_id,
1007 'type_id': type_id,
1008 'ref': external_reference,
1009 'pk_epi': pk_episode
1010 }
1011
1012 if patient_id is not None:
1013 where_parts.append('pk_patient = %(pat_id)s')
1014
1015 if type_id is not None:
1016 where_parts.append('pk_type = %(type_id)s')
1017
1018 if external_reference is not None:
1019 where_parts.append('ext_ref = %(ref)s')
1020
1021 if pk_episode is not None:
1022 where_parts.append('pk_episode = %(pk_epi)s')
1023
1024 if pk_types is not None:
1025 where_parts.append('pk_type IN %(pk_types)s')
1026 args['pk_types'] = tuple(pk_types)
1027
1028 cmd = _SQL_get_document_fields % ' AND '.join(where_parts)
1029 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1030 return [ cDocument(row = {'data': r, 'idx': idx, 'pk_field': 'pk_doc'}) for r in rows ]
1031
1032 #------------------------------------------------------------
1034 # cascades to doc_obj and doc_desc but not bill.bill
1035 cmd = "SELECT blobs.delete_document(%(pk)s, %(enc)s)"
1036 args = {'pk': document_id, 'enc': encounter_id}
1037 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
1038 if not rows[0][0]:
1039 _log.error('cannot delete document [%s]', document_id)
1040 return False
1041 return True
1042
1043 #------------------------------------------------------------
1045
1046 _log.debug('reclassifying documents by type')
1047 _log.debug('original: %s', original_type)
1048 _log.debug('target: %s', target_type)
1049
1050 if target_type['pk_doc_type'] == original_type['pk_doc_type']:
1051 return True
1052
1053 cmd = """
1054 update blobs.doc_med set
1055 fk_type = %(new_type)s
1056 where
1057 fk_type = %(old_type)s
1058 """
1059 args = {'new_type': target_type['pk_doc_type'], 'old_type': original_type['pk_doc_type']}
1060
1061 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1062
1063 return True
1064
1065 #============================================================
1067 """Represents a document type."""
1068 _cmd_fetch_payload = """select * from blobs.v_doc_type where pk_doc_type=%s"""
1069 _cmds_store_payload = [
1070 """update blobs.doc_type set
1071 name = %(type)s
1072 where
1073 pk=%(pk_obj)s and
1074 xmin=%(xmin_doc_type)s""",
1075 """select xmin_doc_type from blobs.v_doc_type where pk_doc_type = %(pk_obj)s"""
1076 ]
1077 _updatable_fields = ['type']
1078 #--------------------------------------------------------
1080
1081 if translation.strip() == '':
1082 return False
1083
1084 if translation.strip() == self._payload[self._idx['l10n_type']].strip():
1085 return True
1086
1087 rows, idx = gmPG2.run_rw_queries (
1088 queries = [
1089 {'cmd': 'select i18n.i18n(%s)', 'args': [self._payload[self._idx['type']]]},
1090 {'cmd': 'select i18n.upd_tx((select i18n.get_curr_lang()), %(orig)s, %(tx)s)',
1091 'args': {
1092 'orig': self._payload[self._idx['type']],
1093 'tx': translation
1094 }
1095 }
1096 ],
1097 return_data = True
1098 )
1099 if not rows[0][0]:
1100 _log.error('cannot set translation to [%s]' % translation)
1101 return False
1102
1103 return self.refetch_payload()
1104
1105 #------------------------------------------------------------
1107 rows, idx = gmPG2.run_ro_queries (
1108 queries = [{'cmd': "SELECT * FROM blobs.v_doc_type"}],
1109 get_col_idx = True
1110 )
1111 doc_types = []
1112 for row in rows:
1113 row_def = {'pk_field': 'pk_doc_type', 'idx': idx, 'data': row}
1114 doc_types.append(cDocumentType(row = row_def))
1115 return doc_types
1116
1117 #------------------------------------------------------------
1119 args = {'typ': document_type.strip()}
1120
1121 cmd = 'SELECT pk FROM blobs.doc_type WHERE name = %(typ)s'
1122 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1123 if len(rows) == 0:
1124 cmd = 'SELECT pk FROM blobs.doc_type WHERE _(name) = %(typ)s'
1125 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1126
1127 if len(rows) == 0:
1128 return None
1129
1130 return rows[0]['pk']
1131
1132 #------------------------------------------------------------
1134 args = {'types': tuple(document_types)}
1135 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'
1136 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1137 return rows
1138
1139 #------------------------------------------------------------
1141 # check for potential dupes:
1142 cmd = 'SELECT pk FROM blobs.doc_type WHERE name = %s'
1143 rows, idx = gmPG2.run_ro_queries (
1144 queries = [{'cmd': cmd, 'args': [document_type]}]
1145 )
1146 if len(rows) == 0:
1147 _log.debug('creating document type [%s]', document_type)
1148 cmd1 = "INSERT INTO blobs.doc_type (name) VALUES (%s) RETURNING pk"
1149 rows, idx = gmPG2.run_rw_queries (
1150 queries = [{'cmd': cmd1, 'args': [document_type]}],
1151 return_data = True
1152 )
1153 return cDocumentType(aPK_obj = rows[0][0])
1154
1155 #------------------------------------------------------------
1157 if document_type['is_in_use']:
1158 return False
1159 gmPG2.run_rw_queries (
1160 queries = [{
1161 'cmd': 'delete from blobs.doc_type where pk=%s',
1162 'args': [document_type['pk_doc_type']]
1163 }]
1164 )
1165 return True
1166
1167 #------------------------------------------------------------
1169 """This needs *considerably* more smarts."""
1170 dirname = gmTools.get_unique_filename (
1171 prefix = '',
1172 suffix = time.strftime(".%Y%m%d-%H%M%S", time.localtime())
1173 )
1174 # extract name for dir
1175 path, doc_ID = os.path.split(dirname)
1176 return doc_ID
1177
1178 #============================================================
1179 # main
1180 #------------------------------------------------------------
1181 if __name__ == '__main__':
1182
1183 if len(sys.argv) < 2:
1184 sys.exit()
1185
1186 if sys.argv[1] != 'test':
1187 sys.exit()
1188
1189 #--------------------------------------------------------
1191
1192 print("----------------------")
1193 print("listing document types")
1194 print("----------------------")
1195
1196 for dt in get_document_types():
1197 print(dt)
1198
1199 print("------------------------------")
1200 print("testing document type handling")
1201 print("------------------------------")
1202
1203 dt = create_document_type(document_type = 'dummy doc type for unit test 1')
1204 print("created:", dt)
1205
1206 dt['type'] = 'dummy doc type for unit test 2'
1207 dt.save_payload()
1208 print("changed base name:", dt)
1209
1210 dt.set_translation(translation = 'Dummy-Dokumenten-Typ fuer Unit-Test')
1211 print("translated:", dt)
1212
1213 print("deleted:", delete_document_type(document_type = dt))
1214
1215 return
1216 #--------------------------------------------------------
1218
1219 print("-----------------------")
1220 print("testing document import")
1221 print("-----------------------")
1222
1223 docs = search_for_documents(patient_id=12)
1224 doc = docs[0]
1225 print("adding to doc:", doc)
1226
1227 fname = sys.argv[1]
1228 print("adding from file:", fname)
1229 part = doc.add_part(file=fname)
1230 print("new part:", part)
1231
1232 return
1233 #--------------------------------------------------------
1235
1236 doc_folder = cDocumentFolder(aPKey=12)
1237
1238 #photo = doc_folder.get_latest_mugshot()
1239 #print type(photo), photo
1240
1241 docs = doc_folder.get_documents()
1242 for doc in docs:
1243 #print type(doc), doc
1244 #print doc.parts
1245 #print doc.format_single_line()
1246 print('--------------------------')
1247 print(doc.format(single_line = True))
1248 print(doc.format())
1249
1250 #--------------------------------------------------------
1252 pk = 12
1253 from Gnumed.business.gmPerson import cPatient
1254 pat = cPatient(pk)
1255 doc_folder = cDocumentFolder(aPKey = pk)
1256 for doc in doc_folder.documents:
1257 for part in doc.parts:
1258 print(part.get_useful_filename (
1259 patient = pat,
1260 make_unique = True,
1261 directory = None,
1262 include_gnumed_tag = False,
1263 date_before_type = True,
1264 name_first = False
1265 ))
1266
1267 #--------------------------------------------------------
1278 #--------------------------------
1279
1280 pk = 12
1281 from Gnumed.business.gmPerson import cPatient
1282 pat = cPatient(pk)
1283 doc_folder = cDocumentFolder(aPKey = pk)
1284 for doc in doc_folder.documents:
1285 for part in doc.parts:
1286 part.format_metainfo(callback = desc_printer)
1287 input('waiting ...')
1288 # success, desc = part.format_metainfo()
1289 # print(success)
1290 # print(desc)
1291 # input('next')
1292
1293 # print(part.get_useful_filename (
1294 # patient = pat,
1295 # make_unique = True,
1296 # directory = None,
1297 # include_gnumed_tag = False,
1298 # date_before_type = True,
1299 # name_first = False
1300 # ))
1301
1302 #--------------------------------------------------------
1303 from Gnumed.pycommon import gmI18N
1304 gmI18N.activate_locale()
1305 gmI18N.install_domain()
1306
1307 #test_doc_types()
1308 #test_adding_doc_part()
1309 #test_get_documents()
1310 #test_get_useful_filename()
1311 test_part_metainfo_formatter()
1312
1313 # print get_ext_ref()
1314
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Jul 28 01:55:29 2019 | http://epydoc.sourceforge.net |