diff --git a/src/soa_builder/web/routers/visits.py b/src/soa_builder/web/routers/visits.py index 81b507a..45059a9 100644 --- a/src/soa_builder/web/routers/visits.py +++ b/src/soa_builder/web/routers/visits.py @@ -11,11 +11,11 @@ soa_exists, get_next_code_uid as _get_next_code_uid, get_study_transition_rules, - get_epoch_id, get_timing_id, get_encounter_type_sv, load_environmental_setting_options, get_latest_sdtm_ct_href, + load_contact_mode_options, ) from ..schemas import VisitCreate, VisitUpdate from fastapi.templating import Jinja2Templates @@ -43,7 +43,7 @@ def list_visits(soa_id: int): cur.execute( """ SELECT id,encounter_uid,name,label,description,type,environmentalSettings,transitionStartRule, - transitionEndRule,epoch_id,scheduledAtId,order_index FROM visit WHERE soa_id=? ORDER BY order_index, id + transitionEndRule,scheduledAtId,order_index,contactModes FROM visit WHERE soa_id=? ORDER BY order_index, id """, (soa_id,), ) @@ -58,9 +58,9 @@ def list_visits(soa_id: int): "environmentalSettings": r[6], "transitionStartRule": r[7], "transitionEndRule": r[8], - "epoch_id": r[9], - "scheduledAtId": r[10], - "order_index": r[11], + "scheduledAtId": r[9], + "order_index": r[10], + "contactModes": r[11], } for r in cur.fetchall() ] @@ -93,6 +93,12 @@ def ui_list_visits(request: Request, soa_id: int): for opt in environmental_setting_options } + contact_mode_options = load_contact_mode_options() + con_mode_lookup = { + str(opt["conceptId"]).strip(): str(opt["submissionValue"]).strip() + for opt in contact_mode_options + } + encounters = list_visits(soa_id) for e in encounters: tsv = get_encounter_type_sv(soa_id, e.get("type") or "") @@ -104,8 +110,14 @@ def ui_list_visits(request: Request, soa_id: int): e["environmental_concept_id"] = concept_id e["environmental_submission_value"] = env_option_lookup.get(concept_id) + contact_code_uid = e.get("contactModes") or "" + contact_concept_id = ( + code_map.get(contact_code_uid, "") if contact_code_uid else "" + ) + e["contact_mode_concept_id"] = contact_concept_id + e["contact_mode_submission_value"] = con_mode_lookup.get(contact_concept_id) + transition_rule_options = get_study_transition_rules(soa_id) - epoch_options = get_epoch_id(soa_id) timing_options = get_timing_id(soa_id) logger.info(environmental_setting_options) @@ -118,9 +130,9 @@ def ui_list_visits(request: Request, soa_id: int): "soa_id": soa_id, "encounters": encounters, "transition_rule_options": transition_rule_options, - "epoch_options": epoch_options, "timing_options": timing_options, "environmental_setting_options": environmental_setting_options, + "contact_mode_options": contact_mode_options, }, ) @@ -133,7 +145,7 @@ def get_visit(soa_id: int, visit_id: int): conn = _connect() cur = conn.cursor() cur.execute( - "SELECT id,name,label,order_index,epoch_id,encounter_uid,description FROM visit WHERE id=? AND soa_id=?", + "SELECT id,name,label,order_index,encounter_uid,description FROM visit WHERE id=? AND soa_id=?", (visit_id, soa_id), ) row = cur.fetchone() @@ -146,9 +158,8 @@ def get_visit(soa_id: int, visit_id: int): "name": row[1], "label": row[2], "order_index": row[3], - "epoch_id": row[4], - "encounter_uid": row[5], - "description": row[6], + "encounter_uid": row[4], + "description": row[5], } @@ -197,14 +208,6 @@ def add_visit(soa_id: int, payload: VisitCreate): next_n = (max(used_nums) if used_nums else 0) + 1 new_uid = f"Encounter_{next_n}" - if payload.epoch_id is not None: - cur.execute( - "SELECT 1 FROM epoch WHERE id=? AND soa_id=?", (payload.epoch_id, soa_id) - ) - if not cur.fetchone(): - conn.close() - raise HTTPException(400, "Invalid epoch_id for this SOA") - # Generate Code_{N} for encounter.type type = _get_next_code_uid(cur, soa_id) logger.info("type=%s", type) @@ -221,7 +224,8 @@ def add_visit(soa_id: int, payload: VisitCreate): ), ) - # Generate Code_{N} for environmentalSettings.type + # Generate Code_{N} for environmentalSettings **only if value selected + """ environmentalSettings = _get_next_code_uid(cur, soa_id) logger.info("environmentalSettings=%s", environmentalSettings) env_code_value = (payload.environmentalSettings or "").strip() or None @@ -243,11 +247,79 @@ def add_visit(soa_id: int, payload: VisitCreate): env_code_value, ), ) + """ + env_code_value = (payload.environmentalSettings or "").strip() + environmentalSettings = None + if env_code_value: + environmentalSettings = _get_next_code_uid(cur, soa_id) + logger.info("environmentalSettings=%s", environmentalSettings) + env_package_slug = get_latest_sdtm_ct_href() or "" + env_codelist_table = ( + f"/mdr/ct/packages/{env_package_slug}" + if env_package_slug + else "/mdr/ct/packages" + ) + cur.execute( + "INSERT INTO code (soa_id, code_uid, codelist_table, codelist_code, code) VALUES (?,?,?,?,?)", + ( + soa_id, + environmentalSettings, + env_codelist_table, + "C127262", + env_code_value, + ), + ) + + # Generate Code_{N} for contactModes **only if value selected + """ + contactModes = _get_next_code_uid(cur, soa_id) + logger.info("contactModes=%s", contactModes) + contact_mode_value = (payload.contactModes or "").strip() or None + contact_mode_slug = get_latest_sdtm_ct_href() or "" + contact_mode_codelist_table = ( + f"/mdr/ct/packages/{contact_mode_slug}" + if contact_mode_slug + else "/mdr/ct/packages" + ) + + if contactModes: + cur.execute( + "INSERT INTO code (soa_id, code_uid, codelist_table, codelist_code, code) VALUES (?,?,?,?,?)", + ( + soa_id, + contactModes, + contact_mode_codelist_table, + "C171445", + contact_mode_value, + ), + ) + """ + contact_mode_value = (payload.contactModes or "").strip() + contactModes = None + if contact_mode_value: + contactModes = _get_next_code_uid(cur, soa_id) + logger.info("contactModes=%s", contactModes) + contact_mode_slug = get_latest_sdtm_ct_href() or "" + contact_mode_codelist_table = ( + f"/mdr/ct/packages/{contact_mode_slug}" + if contact_mode_slug + else "/mdr/ct/packages" + ) + cur.execute( + "INSERT INTO code (soa_id, code_uid, codelist_table, codelist_code, code) VALUES (?,?,?,?,?)", + ( + soa_id, + contactModes, + contact_mode_codelist_table, + "C171445", + contact_mode_value, + ), + ) cur.execute( """ - INSERT INTO visit (soa_id,name,label,order_index,epoch_id,encounter_uid, - description,type,environmentalSettings,transitionStartRule,transitionEndRule,scheduledAtId) + INSERT INTO visit (soa_id,name,label,order_index,encounter_uid, + description,type,environmentalSettings,transitionStartRule,transitionEndRule,scheduledAtId,contactModes) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) """, ( @@ -255,14 +327,14 @@ def add_visit(soa_id: int, payload: VisitCreate): name, _nz(payload.label), next_ord, - payload.epoch_id, new_uid, _nz(payload.description), type, - environmentalSettings, + environmentalSettings, # can be None _nz(payload.transitionStartRule), _nz(payload.transitionEndRule), _nz(payload.scheduledAtId), + contactModes, # can be None ), ) encounter_id = cur.lastrowid @@ -275,6 +347,7 @@ def add_visit(soa_id: int, payload: VisitCreate): "description": (payload.description or "").strip() or None, "type": (payload.type or "").strip() or None, "environmental_settings": (payload.environmentalSettings or "").strip() or None, + "contactModes": (payload.contactModes or "").strip() or None, "transitionStartRule": (payload.transitionStartRule or "").strip() or None, "transitionEndRule": (payload.transitionEndRule or "").strip() or None, "scheduledAtId": (payload.scheduledAtId or "").strip() or None, @@ -292,34 +365,24 @@ def ui_create_visit( name: str = Form(...), label: Optional[str] = Form(None), description: Optional[str] = Form(None), - epoch_id: Optional[str] = Form(None), transitionStartRule: Optional[str] = Form(None), transitionEndRule: Optional[str] = Form(None), scheduledAtId: Optional[str] = Form(None), environmentalSettings: Optional[str] = Form(None), + contactModes: Optional[str] = Form(None), ): if not soa_exists(soa_id): raise HTTPException(404, "SOA not found") - # Coerce empty epoch_id from form to None, otherwise to int - parsed_epoch_id: Optional[int] = None - if epoch_id is not None: - eid = str(epoch_id).strip() - if eid: - try: - parsed_epoch_id = int(eid) - except ValueError: - parsed_epoch_id = None - payload = VisitCreate( name=name, label=label, description=description, - epoch_id=parsed_epoch_id, transitionStartRule=transitionStartRule, transitionEndRule=transitionEndRule, scheduledAtId=scheduledAtId, environmentalSettings=environmentalSettings, + contactModes=contactModes, ) add_visit(soa_id, payload) @@ -336,8 +399,8 @@ def update_visit(soa_id: int, visit_id: int, payload: VisitUpdate): cur = conn.cursor() cur.execute( """ - SELECT id,encounter_uid,name,label,description,type,environmentalSettings,transitionStartRule, - transitionEndRule,epoch_id,scheduledAtId,order_index FROM visit WHERE id=? AND soa_id=? ORDER BY order_index, id + SELECT id,encounter_uid,name,label,description,type,environmentalSettings,contactModes,transitionStartRule, + transitionEndRule,scheduledAtId,order_index FROM visit WHERE id=? AND soa_id=? ORDER BY order_index, id """, (visit_id, soa_id), ) @@ -354,31 +417,20 @@ def update_visit(soa_id: int, visit_id: int, payload: VisitUpdate): "description": row[4], "type": row[5], "environmentalSettings": row[6], - "transitionStartRule": row[7], - "transitionEndRule": row[8], - "epoch_id": row[9], + "contactModes": row[7], + "transitionStartRule": row[8], + "transitionEndRule": row[9], "scheduledAtId": row[10], "order_index": row[11], } new_name = (payload.name if payload.name is not None else before["name"]) or "" - if payload.epoch_id is not None: - cur.execute( - "SELECT 1 FROM epoch WHERE id=? AND soa_id=?", (payload.epoch_id, soa_id) - ) - if not cur.fetchone(): - conn.close() - raise HTTPException(400, "Invalid epoch_id for this SOA") - new_label = payload.label if payload.label is not None else before["label"] new_description = ( payload.description if payload.description is not None else before["description"] ) - new_epoch_id = ( - payload.epoch_id if payload.epoch_id is not None else before["epoch_id"] - ) new_transition_start_rule = ( payload.transitionStartRule if payload.transitionStartRule is not None @@ -407,12 +459,24 @@ def update_visit(soa_id: int, visit_id: int, payload: VisitUpdate): else "/mdr/ct/packages" ) + new_contact_mode = ( + (payload.contactModes or "").strip() + if payload.contactModes is not None + else None + ) + contact_mode_code_uid = before["contactModes"] + contact_mode_package_slug = get_latest_sdtm_ct_href() or "" + contact_mode_codelist_table = ( + f"/mdr/ct/packages/{contact_mode_package_slug}" + if contact_mode_package_slug + else "/mdr/ct/packages" + ) + cur.execute( - "UPDATE visit SET name=?, label=?, epoch_id=?, description=?,transitionStartRule=?,transitionEndRule=?,scheduledAtId=? WHERE id=? AND soa_id=?", + "UPDATE visit SET name=?, label=?, description=?,transitionStartRule=?,transitionEndRule=?,scheduledAtId=? WHERE id=? AND soa_id=?", ( _nz(new_name), _nz(new_label), - new_epoch_id, _nz(new_description), _nz(new_transition_start_rule), _nz(new_transition_end_rule), @@ -464,10 +528,51 @@ def update_visit(soa_id: int, visit_id: int, payload: VisitUpdate): conn.commit() + if new_contact_mode is not None: + if not contact_mode_code_uid: + contact_mode_code_uid = _get_next_code_uid(cur, soa_id) + cur.execute( + "INSERT INTO code (soa_id, code_uid, codelist_table, codelist_code, code) VALUES (?,?,?,?,?)", + ( + soa_id, + contact_mode_code_uid, + contact_mode_codelist_table, + "C171445", + new_contact_mode, + ), + ) + cur.execute( + "UPDATE visit SET contactModes=? WHERE id=? AND soa_id=?", + (contact_mode_code_uid, visit_id, soa_id), + ) + else: + cur.execute( + "UPDATE code SET code=? WHERE soa_id=? AND code_uid=?", + (new_contact_mode, soa_id, contact_mode_code_uid), + ) + if cur.rowcount == 0: + contact_mode_code_uid = _get_next_code_uid(cur, soa_id) + cur.execute( + "INSERT INTO code (soa_id, code_uid, codelist_table, codelist_code, code) VALUES (?,?,?,?,?)", + ( + soa_id, + contact_mode_code_uid, + contact_mode_codelist_table, + "C171445", + new_contact_mode, + ), + ) + cur.execute( + "UPDATE visit SET contactModes=? WHERE id=? AND soa_id=?", + (contact_mode_code_uid, visit_id, soa_id), + ) + + conn.commit() + cur.execute( """ - SELECT id,encounter_uid,name,label,description,type,environmentalSettings,transitionStartRule, - transitionEndRule,epoch_id,scheduledAtId,order_index FROM visit WHERE id=? AND soa_id=? ORDER BY order_index, id + SELECT id,encounter_uid,name,label,description,type,environmentalSettings,contactModes,transitionStartRule, + transitionEndRule,scheduledAtId,order_index FROM visit WHERE id=? AND soa_id=? ORDER BY order_index, id """, ( visit_id, @@ -484,9 +589,9 @@ def update_visit(soa_id: int, visit_id: int, payload: VisitUpdate): "description": r[4], "type": r[5], "environmentalSettings": r[6], - "transitionStartRule": r[7], - "transitionEndRule": r[8], - "epoch_id": r[9], + "contactModes": r[7], + "transitionStartRule": r[8], + "transitionEndRule": r[9], "scheduledAtId": r[10], "order_index": r[11], } @@ -494,8 +599,9 @@ def update_visit(soa_id: int, visit_id: int, payload: VisitUpdate): mutable = [ "name", "label", - "epoch_id", "description", + "environmentalSettings", + "contactModes", "transitionStartRule", "transitionEndRule", "scheduledAtId", @@ -524,21 +630,21 @@ def ui_update_visit( name: Optional[str] = Form(None), label: Optional[str] = Form(None), description: Optional[str] = Form(None), - epoch_id: Optional[int] = Form(None), transitionStartRule: Optional[str] = Form(None), transitionEndRule: Optional[str] = Form(None), scheduledAtId: Optional[str] = Form(None), environmentalSettings: Optional[str] = Form(None), + contactModes: Optional[str] = Form(None), ): payload = VisitUpdate( name=name, label=label, description=description, - epoch_id=epoch_id, transitionStartRule=transitionStartRule, transitionEndRule=transitionEndRule, scheduledAtId=scheduledAtId, environmentalSettings=environmentalSettings, + contactModes=contactModes, ) update_visit(soa_id, visit_id, payload) return RedirectResponse(url=f"/ui/soa/{int(soa_id)}/visits", status_code=303) diff --git a/src/soa_builder/web/schemas.py b/src/soa_builder/web/schemas.py index 77c9aeb..32ce262 100644 --- a/src/soa_builder/web/schemas.py +++ b/src/soa_builder/web/schemas.py @@ -133,6 +133,7 @@ class VisitCreate(BaseModel): transitionEndRule: Optional[str] = None scheduledAtId: Optional[str] = None environmentalSettings: Optional[str] = None + contactModes: Optional[str] = None class VisitUpdate(BaseModel): @@ -145,6 +146,7 @@ class VisitUpdate(BaseModel): transitionEndRule: Optional[str] = None scheduledAtId: Optional[str] = None environmentalSettings: Optional[str] = None + contactModes: Optional[str] = None class ArmCreate(BaseModel): diff --git a/src/soa_builder/web/templates/encounters.html b/src/soa_builder/web/templates/encounters.html index 9e8c1fa..5229fbd 100644 --- a/src/soa_builder/web/templates/encounters.html +++ b/src/soa_builder/web/templates/encounters.html @@ -16,15 +16,6 @@