Package Gnumed :: Package business :: Module gmDocuments
[frames] | no frames]

Source Code for Module Gnumed.business.gmDocuments

  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   
 18   
 19  _log = logging.getLogger('gm.docs') 
 20   
 21  MUGSHOT=26 
 22  DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE = u'visual progress note' 
 23  DOCUMENT_TYPE_PRESCRIPTION = u'prescription' 
 24  #============================================================ 
25 -class cDocumentFolder:
26 """Represents a folder with medical documents for a single patient.""" 27
28 - def __init__(self, aPKey = None):
29 """Fails if 30 31 - patient referenced by aPKey does not exist 32 """ 33 self.pk_patient = aPKey # == identity.pk == primary key 34 if not self._pkey_exists(): 35 raise gmExceptions.ConstructorError, "No patient with PK [%s] in database." % aPKey 36 37 # register backend notification interests 38 # (keep this last so we won't hang on threads when 39 # failing this constructor for other reasons ...) 40 # if not self._register_interests(): 41 # raise gmExceptions.ConstructorError, "cannot register signal interests" 42 43 _log.debug('instantiated document folder for patient [%s]' % self.pk_patient)
44 #--------------------------------------------------------
45 - def cleanup(self):
46 pass
47 #-------------------------------------------------------- 48 # internal helper 49 #--------------------------------------------------------
50 - def _pkey_exists(self):
51 """Does this primary key exist ? 52 53 - true/false/None 54 """ 55 # patient in demographic database ? 56 rows, idx = gmPG2.run_ro_queries(queries = [ 57 {'cmd': u"select exists(select pk from dem.identity where pk = %s)", 'args': [self.pk_patient]} 58 ]) 59 if not rows[0][0]: 60 _log.error("patient [%s] not in demographic database" % self.pk_patient) 61 return None 62 return True
63 #-------------------------------------------------------- 64 # API 65 #--------------------------------------------------------
67 cmd = u""" 68 SELECT pk_doc 69 FROM blobs.v_doc_med 70 WHERE 71 pk_patient = %(pat)s 72 AND 73 type = %(typ)s 74 AND 75 ext_ref = %(ref)s 76 ORDER BY 77 clin_when DESC 78 LIMIT 1 79 """ 80 args = { 81 'pat': self.pk_patient, 82 'typ': DOCUMENT_TYPE_PRESCRIPTION, 83 'ref': u'FreeDiams' 84 } 85 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 86 if len(rows) == 0: 87 _log.info('no FreeDiams prescription available for patient [%s]' % self.pk_patient) 88 return None 89 prescription = cDocument(aPK_obj = rows[0][0]) 90 return prescription
91 #--------------------------------------------------------
92 - def get_latest_mugshot(self):
93 cmd = u"SELECT pk_obj FROM blobs.v_latest_mugshot WHERE pk_patient = %s" 94 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}]) 95 if len(rows) == 0: 96 _log.info('no mugshots available for patient [%s]' % self.pk_patient) 97 return None 98 return cDocumentPart(aPK_obj = rows[0][0])
99 100 latest_mugshot = property(get_latest_mugshot, lambda x:x) 101 #--------------------------------------------------------
102 - def get_mugshot_list(self, latest_only=True):
103 if latest_only: 104 cmd = u"select pk_doc, pk_obj from blobs.v_latest_mugshot where pk_patient=%s" 105 else: 106 cmd = u""" 107 select 108 vdm.pk_doc as pk_doc, 109 dobj.pk as pk_obj 110 from 111 blobs.v_doc_med vdm 112 blobs.doc_obj dobj 113 where 114 vdm.pk_type = (select pk from blobs.doc_type where name = 'patient photograph') 115 and vdm.pk_patient = %s 116 and dobj.fk_doc = vdm.pk_doc 117 """ 118 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}]) 119 return rows
120 #--------------------------------------------------------
121 - def get_doc_list(self, doc_type=None):
122 """return flat list of document IDs""" 123 124 args = { 125 'ID': self.pk_patient, 126 'TYP': doc_type 127 } 128 129 cmd = u""" 130 select vdm.pk_doc 131 from blobs.v_doc_med vdm 132 where 133 vdm.pk_patient = %%(ID)s 134 %s 135 order by vdm.clin_when""" 136 137 if doc_type is None: 138 cmd = cmd % u'' 139 else: 140 try: 141 int(doc_type) 142 cmd = cmd % u'and vdm.pk_type = %(TYP)s' 143 except (TypeError, ValueError): 144 cmd = cmd % u'and vdm.pk_type = (select pk from blobs.doc_type where name = %(TYP)s)' 145 146 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 147 doc_ids = [] 148 for row in rows: 149 doc_ids.append(row[0]) 150 return doc_ids
151 #--------------------------------------------------------
152 - def get_visual_progress_notes(self, episodes=None, encounter=None):
153 return self.get_documents ( 154 doc_type = DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE, 155 episodes = episodes, 156 encounter = encounter 157 )
158 #--------------------------------------------------------
159 - def get_unsigned_documents(self):
160 args = {'pat': self.pk_patient} 161 cmd = _sql_fetch_document_fields % u""" 162 pk_doc IN ( 163 SELECT DISTINCT ON (b_vo.pk_doc) b_vo.pk_doc 164 FROM blobs.v_obj4doc_no_data b_vo 165 WHERE 166 pk_patient = %(pat)s 167 AND 168 reviewed IS FALSE 169 ) 170 ORDER BY clin_when DESC""" 171 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 172 return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
173 #--------------------------------------------------------
174 - def get_documents(self, doc_type=None, episodes=None, encounter=None, order_by=None, exclude_unsigned=False):
175 """Return list of documents.""" 176 177 args = { 178 'pat': self.pk_patient, 179 'type': doc_type, 180 'enc': encounter 181 } 182 where_parts = [u'pk_patient = %(pat)s'] 183 184 if doc_type is not None: 185 try: 186 int(doc_type) 187 where_parts.append(u'pk_type = %(type)s') 188 except (TypeError, ValueError): 189 where_parts.append(u'pk_type = (SELECT pk FROM blobs.doc_type WHERE name = %(type)s)') 190 191 if (episodes is not None) and (len(episodes) > 0): 192 where_parts.append(u'pk_episode IN %(epi)s') 193 args['epi'] = tuple(episodes) 194 195 if encounter is not None: 196 where_parts.append(u'pk_encounter = %(enc)s') 197 198 if exclude_unsigned: 199 where_parts.append(u'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)') 200 201 if order_by is None: 202 order_by = u'ORDER BY clin_when' 203 204 cmd = u"%s\n%s" % (_sql_fetch_document_fields % u' AND '.join(where_parts), order_by) 205 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 206 207 return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
208 209 documents = property(get_documents, lambda x:x) 210 #--------------------------------------------------------
211 - def add_document(self, document_type=None, encounter=None, episode=None):
212 return create_document(document_type = document_type, encounter = encounter, episode = episode)
213 #============================================================ 214 _sql_fetch_document_part_fields = u"select * from blobs.v_obj4doc_no_data where %s" 215
216 -class cDocumentPart(gmBusinessDBObject.cBusinessDBObject):
217 """Represents one part of a medical document.""" 218 219 _cmd_fetch_payload = _sql_fetch_document_part_fields % u"pk_obj = %s" 220 _cmds_store_payload = [ 221 u"""UPDATE blobs.doc_obj SET 222 seq_idx = %(seq_idx)s, 223 comment = gm.nullify_empty_string(%(obj_comment)s), 224 filename = gm.nullify_empty_string(%(filename)s), 225 fk_intended_reviewer = %(pk_intended_reviewer)s, 226 fk_doc = %(pk_doc)s 227 WHERE 228 pk = %(pk_obj)s 229 AND 230 xmin = %(xmin_doc_obj)s 231 RETURNING 232 xmin AS xmin_doc_obj""" 233 ] 234 _updatable_fields = [ 235 'seq_idx', 236 'obj_comment', 237 'pk_intended_reviewer', 238 'filename', 239 'pk_doc' 240 ] 241 #-------------------------------------------------------- 242 # retrieve data 243 #--------------------------------------------------------
244 - def export_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False):
245 246 if self._payload[self._idx['size']] == 0: 247 return None 248 249 if filename is None: 250 suffix = None 251 # preserve original filename extension if available 252 if self._payload[self._idx['filename']] is not None: 253 name, suffix = os.path.splitext(self._payload[self._idx['filename']]) 254 suffix = suffix.strip() 255 if suffix == u'': 256 suffix = None 257 # get unique filename 258 filename = gmTools.get_unique_filename ( 259 prefix = 'gm-doc_obj-page_%s-' % self._payload[self._idx['seq_idx']], 260 suffix = suffix 261 ) 262 263 success = gmPG2.bytea2file ( 264 data_query = { 265 'cmd': u'SELECT substring(data from %(start)s for %(size)s) FROM blobs.doc_obj WHERE pk=%(pk)s', 266 'args': {'pk': self.pk_obj} 267 }, 268 filename = filename, 269 chunk_size = aChunkSize, 270 data_size = self._payload[self._idx['size']] 271 ) 272 273 if not success: 274 return None 275 276 if target_mime is None: 277 return filename 278 279 if target_extension is None: 280 target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime) 281 282 target_fname = gmTools.get_unique_filename ( 283 prefix = 'gm-doc_obj-page_%s-converted-' % self._payload[self._idx['seq_idx']], 284 suffix = target_extension 285 ) 286 _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname) 287 if gmMimeLib.convert_file ( 288 filename = filename, 289 target_mime = target_mime, 290 target_filename = target_fname 291 ): 292 return target_fname 293 294 _log.warning('conversion failed') 295 if not ignore_conversion_problems: 296 return None 297 298 _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename) 299 return filename
300 #--------------------------------------------------------
301 - def get_reviews(self):
302 cmd = u""" 303 select 304 reviewer, 305 reviewed_when, 306 is_technically_abnormal, 307 clinically_relevant, 308 is_review_by_responsible_reviewer, 309 is_your_review, 310 coalesce(comment, '') 311 from blobs.v_reviewed_doc_objects 312 where pk_doc_obj = %s 313 order by 314 is_your_review desc, 315 is_review_by_responsible_reviewer desc, 316 reviewed_when desc 317 """ 318 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 319 return rows
320 #--------------------------------------------------------
321 - def get_containing_document(self):
322 return cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
323 #-------------------------------------------------------- 324 # store data 325 #--------------------------------------------------------
326 - def update_data_from_file(self, fname=None):
327 # sanity check 328 if not (os.access(fname, os.R_OK) and os.path.isfile(fname)): 329 _log.error('[%s] is not a readable file' % fname) 330 return False 331 332 gmPG2.file2bytea ( 333 query = u"UPDATE blobs.doc_obj SET data = %(data)s::bytea WHERE pk = %(pk)s", 334 filename = fname, 335 args = {'pk': self.pk_obj} 336 ) 337 338 # must update XMIN now ... 339 self.refetch_payload() 340 return True
341 #--------------------------------------------------------
342 - def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
343 # row already there ? 344 cmd = u""" 345 select pk 346 from blobs.reviewed_doc_objs 347 where 348 fk_reviewed_row = %s and 349 fk_reviewer = (select pk from dem.staff where db_user = current_user)""" 350 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 351 352 # INSERT needed 353 if len(rows) == 0: 354 cols = [ 355 u"fk_reviewer", 356 u"fk_reviewed_row", 357 u"is_technically_abnormal", 358 u"clinically_relevant" 359 ] 360 vals = [ 361 u'%(fk_row)s', 362 u'%(abnormal)s', 363 u'%(relevant)s' 364 ] 365 args = { 366 'fk_row': self.pk_obj, 367 'abnormal': technically_abnormal, 368 'relevant': clinically_relevant 369 } 370 cmd = u""" 371 insert into blobs.reviewed_doc_objs ( 372 %s 373 ) values ( 374 (select pk from dem.staff where db_user=current_user), 375 %s 376 )""" % (', '.join(cols), ', '.join(vals)) 377 378 # UPDATE needed 379 if len(rows) == 1: 380 pk_row = rows[0][0] 381 args = { 382 'abnormal': technically_abnormal, 383 'relevant': clinically_relevant, 384 'pk_row': pk_row 385 } 386 cmd = u""" 387 update blobs.reviewed_doc_objs set 388 is_technically_abnormal = %(abnormal)s, 389 clinically_relevant = %(relevant)s 390 where 391 pk=%(pk_row)s""" 392 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 393 394 return True
395 #--------------------------------------------------------
396 - def set_as_active_photograph(self):
397 if self._payload[self._idx['type']] != u'patient photograph': 398 return False 399 # set seq_idx to current max + 1 400 rows, idx = gmPG2.run_ro_queries ( 401 queries = [{ 402 'cmd': u'select coalesce(max(seq_idx)+1, 1) from blobs.doc_obj where fk_doc=%(doc_id)s', 403 'args': {'doc_id': self._payload[self._idx['pk_doc']]} 404 }] 405 ) 406 self._payload[self._idx['seq_idx']] = rows[0][0] 407 self._is_modified = True 408 self.save_payload()
409 #--------------------------------------------------------
410 - def display_via_mime(self, chunksize=0, block=None):
411 412 fname = self.export_to_file(aChunkSize = chunksize) 413 if fname is None: 414 return False, '' 415 416 success, msg = gmMimeLib.call_viewer_on_file(fname, block = block) 417 if not success: 418 return False, msg 419 420 return True, ''
421 #--------------------------------------------------------
422 - def format_single_line(self):
423 f_ext = u'' 424 if self._payload[self._idx['filename']] is not None: 425 f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip() 426 if f_ext != u'': 427 f_ext = u' .' + f_ext.upper() 428 txt = _('part %s, %s%s%s of document %s from %s%s') % ( 429 self._payload[self._idx['seq_idx']], 430 gmTools.size2str(self._payload[self._idx['size']]), 431 f_ext, 432 gmTools.coalesce(self._payload[self._idx['obj_comment']], u'', u' ("%s")'), 433 self._payload[self._idx['l10n_type']], 434 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y %b %d'), 435 gmTools.coalesce(self._payload[self._idx['doc_comment']], u'', u' ("%s")') 436 ) 437 return txt
438 #--------------------------------------------------------
439 - def format(self, single_line=False):
440 if single_line: 441 return self.format_single_line() 442 443 txt = _('%s document part [#%s]\n') % ( 444 gmTools.bool2str ( 445 boolean = self._payload[self._idx['reviewed']], 446 true_str = _('Reviewed'), 447 false_str = _('Unreviewed') 448 ), 449 self._payload[self._idx['pk_obj']] 450 ) 451 452 f_ext = u'' 453 if self._payload[self._idx['filename']] is not None: 454 f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip() 455 if f_ext != u'': 456 f_ext = u'.' + f_ext.upper() + u' ' 457 txt += _(' Part %s: %s %s(%s Bytes)\n') % ( 458 self._payload[self._idx['seq_idx']], 459 gmTools.size2str(self._payload[self._idx['size']]), 460 f_ext, 461 self._payload[self._idx['size']] 462 ) 463 464 if self._payload[self._idx['filename']] is not None: 465 txt += _(' Filename: %s\n') % self._payload[self._idx['filename']] 466 467 if self._payload[self._idx['obj_comment']] is not None: 468 txt += u'\n%s\n' % self._payload[self._idx['obj_comment']] 469 470 return txt
471 #------------------------------------------------------------
472 -def delete_document_part(part_pk=None, encounter_pk=None):
473 cmd = u"select blobs.delete_document_part(%(pk)s, %(enc)s)" 474 args = {'pk': part_pk, 'enc': encounter_pk} 475 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 476 return
477 #============================================================ 478 _sql_fetch_document_fields = u""" 479 SELECT 480 *, 481 COALESCE ( 482 (SELECT array_agg(seq_idx) FROM blobs.doc_obj b_do WHERE b_do.fk_doc = b_vdm.pk_doc), 483 ARRAY[]::integer[] 484 ) 485 AS seq_idx_list 486 FROM 487 blobs.v_doc_med b_vdm 488 WHERE 489 %s 490 """ 491
492 -class cDocument(gmBusinessDBObject.cBusinessDBObject):
493 """Represents one medical document.""" 494 495 _cmd_fetch_payload = _sql_fetch_document_fields % u"pk_doc = %s" 496 _cmds_store_payload = [ 497 u"""update blobs.doc_med set 498 fk_type = %(pk_type)s, 499 fk_episode = %(pk_episode)s, 500 fk_encounter = %(pk_encounter)s, 501 clin_when = %(clin_when)s, 502 comment = gm.nullify_empty_string(%(comment)s), 503 ext_ref = gm.nullify_empty_string(%(ext_ref)s) 504 where 505 pk = %(pk_doc)s and 506 xmin = %(xmin_doc_med)s""", 507 u"""select xmin_doc_med from blobs.v_doc_med where pk_doc = %(pk_doc)s""" 508 ] 509 510 _updatable_fields = [ 511 'pk_type', 512 'comment', 513 'clin_when', 514 'ext_ref', 515 'pk_episode', 516 'pk_encounter' # mainly useful when moving visual progress notes to their respective encounters 517 ] 518 #--------------------------------------------------------
519 - def refetch_payload(self, ignore_changes=False):
520 try: del self.__has_unreviewed_parts 521 except AttributeError: pass 522 523 return super(cDocument, self).refetch_payload(ignore_changes = ignore_changes)
524 #--------------------------------------------------------
525 - def get_descriptions(self, max_lng=250):
526 """Get document descriptions. 527 528 - will return a list of rows 529 """ 530 if max_lng is None: 531 cmd = u"SELECT pk, text FROM blobs.doc_desc WHERE fk_doc = %s" 532 else: 533 cmd = u"SELECT pk, substring(text from 1 for %s) FROM blobs.doc_desc WHERE fk_doc=%%s" % max_lng 534 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 535 return rows
536 #--------------------------------------------------------
537 - def add_description(self, description=None):
538 cmd = u"insert into blobs.doc_desc (fk_doc, text) values (%s, %s)" 539 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj, description]}]) 540 return True
541 #--------------------------------------------------------
542 - def update_description(self, pk=None, description=None):
543 cmd = u"update blobs.doc_desc set text = %(desc)s where fk_doc = %(doc)s and pk = %(pk_desc)s" 544 gmPG2.run_rw_queries(queries = [ 545 {'cmd': cmd, 'args': {'doc': self.pk_obj, 'pk_desc': pk, 'desc': description}} 546 ]) 547 return True
548 #--------------------------------------------------------
549 - def delete_description(self, pk=None):
550 cmd = u"delete from blobs.doc_desc where fk_doc = %(doc)s and pk = %(desc)s" 551 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'doc': self.pk_obj, 'desc': pk}}]) 552 return True
553 #--------------------------------------------------------
554 - def _get_parts(self):
555 cmd = _sql_fetch_document_part_fields % u"pk_doc = %s ORDER BY seq_idx" 556 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True) 557 return [ cDocumentPart(row = {'pk_field': 'pk_obj', 'idx': idx, 'data': r}) for r in rows ]
558 559 parts = property(_get_parts, lambda x:x) 560 #--------------------------------------------------------
561 - def add_part(self, file=None):
562 """Add a part to the document.""" 563 # create dummy part 564 cmd = u""" 565 insert into blobs.doc_obj ( 566 fk_doc, data, seq_idx 567 ) VALUES ( 568 %(doc_id)s, 569 ''::bytea, 570 (select coalesce(max(seq_idx)+1, 1) from blobs.doc_obj where fk_doc=%(doc_id)s) 571 )""" 572 rows, idx = gmPG2.run_rw_queries ( 573 queries = [ 574 {'cmd': cmd, 'args': {'doc_id': self.pk_obj}}, 575 {'cmd': u"select currval('blobs.doc_obj_pk_seq')"} 576 ], 577 return_data = True 578 ) 579 # init document part instance 580 pk_part = rows[0][0] 581 new_part = cDocumentPart(aPK_obj = pk_part) 582 if not new_part.update_data_from_file(fname=file): 583 _log.error('cannot import binary data from [%s] into document part' % file) 584 gmPG2.run_rw_queries ( 585 queries = [ 586 {'cmd': u"delete from blobs.doc_obj where pk = %s", 'args': [pk_part]} 587 ] 588 ) 589 return None 590 new_part['filename'] = file 591 new_part.save_payload() 592 593 return new_part
594 #--------------------------------------------------------
595 - def add_parts_from_files(self, files=None, reviewer=None):
596 597 new_parts = [] 598 599 for filename in files: 600 new_part = self.add_part(file = filename) 601 if new_part is None: 602 msg = 'cannot instantiate document part object' 603 _log.error(msg) 604 return (False, msg, filename) 605 new_parts.append(new_part) 606 607 if reviewer is not None: 608 new_part['pk_intended_reviewer'] = reviewer # None == Null 609 success, data = new_part.save_payload() 610 if not success: 611 msg = 'cannot set reviewer to [%s]' % reviewer 612 _log.error(msg) 613 _log.error(str(data)) 614 return (False, msg, filename) 615 616 return (True, '', new_parts)
617 #--------------------------------------------------------
618 - def export_parts_to_files(self, export_dir=None, chunksize=0):
619 fnames = [] 620 for part in self.parts: 621 fname = part.export_to_file(aChunkSize = chunksize) 622 if export_dir is not None: 623 shutil.move(fname, export_dir) 624 fname = os.path.join(export_dir, os.path.split(fname)[1]) 625 fnames.append(fname) 626 return fnames
627 #--------------------------------------------------------
629 try: 630 return self.__has_unreviewed_parts 631 except AttributeError: 632 pass 633 634 cmd = u"SELECT EXISTS(SELECT 1 FROM blobs.v_obj4doc_no_data WHERE pk_doc = %(pk)s AND reviewed IS FALSE)" 635 args = {'pk': self.pk_obj} 636 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 637 self.__has_unreviewed_parts = rows[0][0] 638 639 return self.__has_unreviewed_parts
640 641 has_unreviewed_parts = property(_get_has_unreviewed_parts, lambda x:x) 642 #--------------------------------------------------------
643 - def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
644 # FIXME: this is probably inefficient 645 for part in self.parts: 646 if not part.set_reviewed(technically_abnormal, clinically_relevant): 647 return False 648 return True
649 #--------------------------------------------------------
650 - def set_primary_reviewer(self, reviewer=None):
651 for part in self.parts: 652 part['pk_intended_reviewer'] = reviewer 653 success, data = part.save_payload() 654 if not success: 655 _log.error('cannot set reviewer to [%s]' % reviewer) 656 _log.error(str(data)) 657 return False 658 return True
659 #--------------------------------------------------------
660 - def format(self):
661 part_count = len(self._payload[self._idx['seq_idx_list']]) 662 if part_count == 0: 663 parts = _('no parts') 664 elif part_count == 1: 665 parts = _('1 part') 666 else: 667 parts = _('%s parts') % part_count 668 txt = _( 669 '%s (%s) #%s\n' 670 '\n' 671 ' Created: %s\n' 672 ' Episode: %s\n' 673 '%s' 674 '%s' 675 '%s' 676 ) % ( 677 self._payload[self._idx['l10n_type']], 678 parts, 679 self._payload[self._idx['pk_doc']], 680 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], format = '%Y %b %d', accuracy = gmDateTime.acc_days), 681 self._payload[self._idx['episode']], 682 gmTools.coalesce(self._payload[self._idx['health_issue']], u'', _(' Health issue: %s\n')), 683 gmTools.coalesce(self._payload[self._idx['ext_ref']], u'', _(' External reference: %s\n')), 684 gmTools.coalesce(self._payload[self._idx['comment']], u'', u' %s') 685 ) 686 return txt
687 #------------------------------------------------------------
688 -def create_document(document_type=None, encounter=None, episode=None):
689 """Returns new document instance or raises an exception. 690 """ 691 cmd = u"""INSERT INTO blobs.doc_med (fk_type, fk_encounter, fk_episode) VALUES (%(type)s, %(enc)s, %(epi)s) RETURNING pk""" 692 try: 693 int(document_type) 694 except ValueError: 695 cmd = u""" 696 INSERT INTO blobs.doc_med ( 697 fk_type, 698 fk_encounter, 699 fk_episode 700 ) VALUES ( 701 coalesce ( 702 (SELECT pk from blobs.doc_type bdt where bdt.name = %(type)s), 703 (SELECT pk from blobs.doc_type bdt where _(bdt.name) = %(type)s) 704 ), 705 %(enc)s, 706 %(epi)s 707 ) RETURNING pk""" 708 709 args = {'type': document_type, 'enc': encounter, 'epi': episode} 710 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True) 711 doc = cDocument(aPK_obj = rows[0][0]) 712 return doc
713 #------------------------------------------------------------
714 -def search_for_documents(patient_id=None, type_id=None, external_reference=None):
715 """Searches for documents with the given patient and type ID.""" 716 if patient_id is None: 717 raise ValueError('need patient id to search for document') 718 719 args = {'pat_id': patient_id, 'type_id': type_id, 'ref': external_reference} 720 where_parts = [u'pk_patient = %(pat_id)s'] 721 722 if type_id is not None: 723 where_parts.append(u'pk_type = %(type_id)s') 724 725 if external_reference is not None: 726 where_parts.append(u'ext_ref = %(ref)s') 727 728 cmd = _sql_fetch_document_fields % u' AND '.join(where_parts) 729 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 730 return [ cDocument(row = {'data': r, 'idx': idx, 'pk_field': 'pk_doc'}) for r in rows ]
731 #------------------------------------------------------------
732 -def delete_document(document_id=None, encounter_id=None):
733 # cascades to doc_obj and doc_desc but not bill.bill 734 cmd = u"SELECT blobs.delete_document(%(pk)s, %(enc)s)" 735 args = {'pk': document_id, 'enc': encounter_id} 736 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True) 737 if not rows[0][0]: 738 _log.error('cannot delete document [%s]', document_id) 739 return False 740 return True
741 #------------------------------------------------------------
742 -def reclassify_documents_by_type(original_type=None, target_type=None):
743 744 _log.debug('reclassifying documents by type') 745 _log.debug('original: %s', original_type) 746 _log.debug('target: %s', target_type) 747 748 if target_type['pk_doc_type'] == original_type['pk_doc_type']: 749 return True 750 751 cmd = u""" 752 update blobs.doc_med set 753 fk_type = %(new_type)s 754 where 755 fk_type = %(old_type)s 756 """ 757 args = {u'new_type': target_type['pk_doc_type'], u'old_type': original_type['pk_doc_type']} 758 759 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 760 761 return True
762 763 #============================================================
764 -class cDocumentType(gmBusinessDBObject.cBusinessDBObject):
765 """Represents a document type.""" 766 _cmd_fetch_payload = u"""select * from blobs.v_doc_type where pk_doc_type=%s""" 767 _cmds_store_payload = [ 768 u"""update blobs.doc_type set 769 name = %(type)s 770 where 771 pk=%(pk_obj)s and 772 xmin=%(xmin_doc_type)s""", 773 u"""select xmin_doc_type from blobs.v_doc_type where pk_doc_type = %(pk_obj)s""" 774 ] 775 _updatable_fields = ['type'] 776 #--------------------------------------------------------
777 - def set_translation(self, translation=None):
778 779 if translation.strip() == '': 780 return False 781 782 if translation.strip() == self._payload[self._idx['l10n_type']].strip(): 783 return True 784 785 rows, idx = gmPG2.run_rw_queries ( 786 queries = [ 787 {'cmd': u'select i18n.i18n(%s)', 'args': [self._payload[self._idx['type']]]}, 788 {'cmd': u'select i18n.upd_tx((select i18n.get_curr_lang()), %(orig)s, %(tx)s)', 789 'args': { 790 'orig': self._payload[self._idx['type']], 791 'tx': translation 792 } 793 } 794 ], 795 return_data = True 796 ) 797 if not rows[0][0]: 798 _log.error('cannot set translation to [%s]' % translation) 799 return False 800 801 return self.refetch_payload()
802 #------------------------------------------------------------
803 -def get_document_types():
804 rows, idx = gmPG2.run_ro_queries ( 805 queries = [{'cmd': u"SELECT * FROM blobs.v_doc_type"}], 806 get_col_idx = True 807 ) 808 doc_types = [] 809 for row in rows: 810 row_def = {'pk_field': 'pk_doc_type', 'idx': idx, 'data': row} 811 doc_types.append(cDocumentType(row = row_def)) 812 return doc_types
813 #------------------------------------------------------------
814 -def get_document_type_pk(document_type=None):
815 args = {'typ': document_type.strip()} 816 817 cmd = u'SELECT pk FROM blobs.doc_type WHERE name = %(typ)s' 818 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 819 if len(rows) == 0: 820 cmd = u'SELECT pk FROM blobs.doc_type WHERE _(name) = %(typ)s' 821 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 822 823 if len(rows) == 0: 824 return None 825 826 return rows[0]['pk']
827 #------------------------------------------------------------
828 -def create_document_type(document_type=None):
829 # check for potential dupes: 830 cmd = u'select pk from blobs.doc_type where name = %s' 831 rows, idx = gmPG2.run_ro_queries ( 832 queries = [{'cmd': cmd, 'args': [document_type]}] 833 ) 834 if len(rows) == 0: 835 cmd1 = u"INSERT INTO blobs.doc_type (name) VALUES (%s) RETURNING pk" 836 rows, idx = gmPG2.run_rw_queries ( 837 queries = [{'cmd': cmd1, 'args': [document_type]}], 838 return_data = True 839 ) 840 return cDocumentType(aPK_obj = rows[0][0])
841 #------------------------------------------------------------
842 -def delete_document_type(document_type=None):
843 if document_type['is_in_use']: 844 return False 845 gmPG2.run_rw_queries ( 846 queries = [{ 847 'cmd': u'delete from blobs.doc_type where pk=%s', 848 'args': [document_type['pk_doc_type']] 849 }] 850 ) 851 return True
852 #------------------------------------------------------------
853 -def get_ext_ref():
854 """This needs *considerably* more smarts.""" 855 dirname = gmTools.get_unique_filename ( 856 prefix = '', 857 suffix = time.strftime(".%Y%m%d-%H%M%S", time.localtime()) 858 ) 859 # extract name for dir 860 path, doc_ID = os.path.split(dirname) 861 return doc_ID
862 #============================================================ 863 # main 864 #------------------------------------------------------------ 865 if __name__ == '__main__': 866 867 if len(sys.argv) < 2: 868 sys.exit() 869 870 if sys.argv[1] != u'test': 871 sys.exit() 872 873 #--------------------------------------------------------
874 - def test_doc_types():
875 876 print "----------------------" 877 print "listing document types" 878 print "----------------------" 879 880 for dt in get_document_types(): 881 print dt 882 883 print "------------------------------" 884 print "testing document type handling" 885 print "------------------------------" 886 887 dt = create_document_type(document_type = 'dummy doc type for unit test 1') 888 print "created:", dt 889 890 dt['type'] = 'dummy doc type for unit test 2' 891 dt.save_payload() 892 print "changed base name:", dt 893 894 dt.set_translation(translation = 'Dummy-Dokumenten-Typ fuer Unit-Test') 895 print "translated:", dt 896 897 print "deleted:", delete_document_type(document_type = dt) 898 899 return
900 #--------------------------------------------------------
901 - def test_adding_doc_part():
902 903 print "-----------------------" 904 print "testing document import" 905 print "-----------------------" 906 907 docs = search_for_documents(patient_id=12) 908 doc = docs[0] 909 print "adding to doc:", doc 910 911 fname = sys.argv[1] 912 print "adding from file:", fname 913 part = doc.add_part(file=fname) 914 print "new part:", part 915 916 return
917 #--------------------------------------------------------
918 - def test_get_documents():
919 doc_folder = cDocumentFolder(aPKey=12) 920 921 #photo = doc_folder.get_latest_mugshot() 922 #print type(photo), photo 923 924 docs = doc_folder.get_documents() 925 for doc in docs: 926 print type(doc), doc 927 print doc.parts
928 #pprint(gmBusinessDBObject.jsonclasshintify(docs)) 929 #-------------------------------------------------------- 930 from Gnumed.pycommon import gmI18N 931 gmI18N.activate_locale() 932 gmI18N.install_domain() 933 934 #test_doc_types() 935 #test_adding_doc_part() 936 test_get_documents() 937 938 # print get_ext_ref() 939 940 #============================================================ 941