From 213e8e7cde52b1503555ccf6954245b735826702 Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 08:56:02 -0500
Subject: [PATCH 01/17] Added elements page and removed from edit.html
---
src/soa_builder/web/routers/elements.py | 431 +++++++++++++++-----
src/soa_builder/web/routers/visits.py | 48 +--
src/soa_builder/web/templates/base.html | 1 +
src/soa_builder/web/templates/edit.html | 72 +---
src/soa_builder/web/templates/elements.html | 138 ++++---
tests/test_element_audit_endpoint.py | 16 +-
tests/test_ui_add_element.py | 2 +-
7 files changed, 433 insertions(+), 275 deletions(-)
diff --git a/src/soa_builder/web/routers/elements.py b/src/soa_builder/web/routers/elements.py
index 786ffad..be24ada 100644
--- a/src/soa_builder/web/routers/elements.py
+++ b/src/soa_builder/web/routers/elements.py
@@ -1,18 +1,26 @@
import json
import logging
-from datetime import datetime, timezone
-from typing import List, Optional
+import os
-from fastapi import APIRouter, HTTPException
-from fastapi.responses import JSONResponse
+from typing import Optional
+
+from fastapi import APIRouter, HTTPException, Request, Form
+from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse
+from fastapi.templating import Jinja2Templates
from ..audit import _record_element_audit
from ..db import _connect
-from ..utils import soa_exists
+from ..utils import (
+ soa_exists,
+ get_study_transition_rules,
+)
from ..schemas import ElementCreate, ElementUpdate
-router = APIRouter(prefix="/soa/{soa_id}")
+router = APIRouter()
logger = logging.getLogger("soa_builder.web.routers.elements")
+templates = Jinja2Templates(
+ directory=os.path.join(os.path.dirname(__file__), "..", "templates")
+)
def _nz(s: Optional[str]) -> Optional[str]:
@@ -20,111 +28,333 @@ def _nz(s: Optional[str]) -> Optional[str]:
return s or None
-def _next_element_identifier(soa_id: int) -> str:
- """Compute next monotonically increasing StudyElement_N for an SoA.
- Scans current element rows and element_audit snapshots to avoid reusing numbers after deletes.
- """
+# APi endpoint for listing elements
+@router.get("/soa/{soa_id}/elements", response_class=JSONResponse, response_model=None)
+def list_elements(soa_id: int):
+ if not soa_exists(soa_id):
+ raise HTTPException(404, "SOA not found")
+
conn = _connect()
cur = conn.cursor()
- max_n = 0
- try:
- cur.execute("SELECT element_id FROM element WHERE soa_id=?", (soa_id,))
- for (eid,) in cur.fetchall():
- if isinstance(eid, str) and eid.startswith("StudyElement_"):
- tail = eid.split("StudyElement_")[-1]
- if tail.isdigit():
- max_n = max(max_n, int(tail))
- except Exception as e:
- logger.exception(
- "_next_element_identifier scan elements failed for soa_id=%s: %s",
- soa_id,
- e,
- )
- try:
- cur.execute(
- "SELECT before_json, after_json FROM element_audit WHERE soa_id=?",
- (soa_id,),
- )
- for bjson, ajson in cur.fetchall():
- for js in (bjson, ajson):
- if not js:
- continue
- try:
- obj = json.loads(js)
- except Exception as e:
- logger.debug(
- "_next_element_identifier JSON parse failed soa_id=%s: %s",
- soa_id,
- e,
- )
- obj = None
- if isinstance(obj, dict):
- val = obj.get("element_id")
- if isinstance(val, str) and val.startswith("StudyElement_"):
- tail = val.split("StudyElement_")[-1]
- if tail.isdigit():
- max_n = max(max_n, int(tail))
- except Exception as e:
- logger.exception(
- "_next_element_identifier scan element_audit failed for soa_id=%s: %s",
- soa_id,
- e,
- )
+ cur.execute(
+ "SELECT id,element_id,name,label,description,order_index,testrl,teenrl FROM element WHERE soa_id=? ORDER BY order_index",
+ (soa_id,),
+ )
+ rows = [
+ {
+ "id": r[0],
+ "element_id": r[1],
+ "name": r[2],
+ "label": r[3],
+ "description": r[4],
+ "order_index": r[5],
+ "testrl": r[6],
+ "teenrl": r[7],
+ }
+ for r in cur.fetchall()
+ ]
conn.close()
- return f"StudyElement_{max_n + 1}"
+ return rows
-def _get_element_uid(soa_id: int, row_id: int) -> Optional[str]:
- """Return element.element_id (StudyElement_N) for row id if column exists, else None."""
- try:
- conn = _connect()
- cur = conn.cursor()
- cur.execute("PRAGMA table_info(element)")
- cols = {r[1] for r in cur.fetchall()}
- if "element_id" not in cols:
- conn.close()
- return None
- cur.execute(
- "SELECT element_id FROM element WHERE id=? AND soa_id=?",
- (row_id, soa_id),
- )
- r = cur.fetchone()
- conn.close()
- return r[0] if r else None
- except Exception as e:
- logger.exception(
- "_get_element_uid failed for soa_id=%s row_id=%s: %s", soa_id, row_id, e
- )
- return None
+# UI code for listing elements
+@router.get("/ui/soa/{soa_id}/elements", response_class=HTMLResponse)
+def ui_list_elements(request: Request, soa_id: int):
+ if not soa_exists(soa_id):
+ raise HTTPException(404, "SOA not found")
+ elements = list_elements(soa_id)
+ transition_rule_options = get_study_transition_rules(soa_id)
-@router.get("/elements", response_class=JSONResponse)
-def list_elements(soa_id: int):
+ return templates.TemplateResponse(
+ request,
+ "elements.html",
+ {
+ "request": request,
+ "soa_id": soa_id,
+ "elements": elements,
+ "transition_rule_options": transition_rule_options,
+ },
+ )
+
+
+# API endpoint to create an element
+@router.post(
+ "/soa/{soa_id}/elements",
+ response_class=JSONResponse,
+ status_code=201,
+ response_model=None,
+)
+def add_element(soa_id: int, payload: ElementCreate):
if not soa_exists(soa_id):
raise HTTPException(404, "SOA not found")
+
+ name = (payload.name or "").strip()
+ if not name:
+ raise HTTPException(404, "Element name required")
+
conn = _connect()
cur = conn.cursor()
+
+ # order index
cur.execute(
- "SELECT id,name,label,description,order_index,created_at,element_id FROM element WHERE soa_id=? ORDER BY order_index",
+ "SELECT COALESCE(MAX(order_index),0) FROM element WHERE soa_id=?",
(soa_id,),
)
- rows = [
- {
- "id": r[0],
- "name": r[1],
- "label": r[2],
- "description": r[3],
- "order_index": r[4],
- "created_at": r[5],
- "element_id": r[6],
- }
- for r in cur.fetchall()
+ next_ord = (cur.fetchone() or [0])[0] + 1
+
+ # Create element_uid and incremenet order_index
+ cur.execute(
+ "SELECT element_id FROM element WHERE soa_id=? AND element_id LIKE 'StudyElement_%'",
+ (soa_id,),
+ )
+
+ existing_uids = [r[0] for r in cur.fetchall() if r[0]]
+ used_nums = set()
+ for uid in existing_uids:
+ if uid.startswith("StudyElement_"):
+ tail = uid[len("StudyElement_") :]
+ if tail.isdigit():
+ used_nums.add(int(tail))
+ else:
+ logger.warning(
+ "Invalid element_id format encountered (ignored): %s", uid
+ )
+
+ # Always pick MAX(existing) + 1, do not fill gaps
+ next_n = (max(used_nums) if used_nums else 0) + 1
+ new_uid = f"StudyElement_{next_n}"
+
+ cur.execute(
+ """
+ INSERT into element (soa_id,element_id,name,label,description,order_index,testrl,teenrl)
+ VALUES (?,?,?,?,?,?,?,?)
+ """,
+ (
+ soa_id,
+ new_uid,
+ name,
+ _nz(payload.label),
+ _nz(payload.description),
+ next_ord,
+ _nz(payload.testrl),
+ _nz(payload.teenrl),
+ ),
+ )
+ id = cur.lastrowid
+ conn.commit()
+ conn.close()
+ after = {
+ "id": id,
+ "encounter_uid": new_uid,
+ "name": payload.name,
+ "label": (payload.label or "").strip() or None,
+ "description": (payload.description or "").strip() or None,
+ "testrl": (payload.testrl or "").strip() or None,
+ "teenrl": (payload.teenrl or "").strip() or None,
+ }
+ _record_element_audit(soa_id, "create", id, before=None, after=after)
+ return after
+
+
+# UI code to create element
+@router.post("/ui/soa/{soa_id}/elements/create")
+def ui_create_element(
+ request: Request,
+ soa_id: int,
+ name: str = Form(...),
+ label: Optional[str] = Form(None),
+ description: Optional[str] = Form(None),
+ testrl: Optional[str] = Form(None),
+ teenrl: Optional[str] = Form(None),
+):
+ if not soa_exists(soa_id):
+ raise HTTPException("SOA not found")
+
+ payload = ElementCreate(
+ name=name,
+ label=label,
+ description=description,
+ testrl=testrl,
+ teenrl=teenrl,
+ )
+ add_element(soa_id, payload)
+ return RedirectResponse(url=f"/ui/soa/{int(soa_id)}/elements", status_code=303)
+
+
+# API endpoint to update an element
+@router.patch("/soa/{soa_id}/elements/{element_id}", response_class=JSONResponse)
+def update_element(soa_id: int, element_id: int, payload: ElementUpdate):
+ if not soa_exists(soa_id):
+ raise HTTPException(404, "SOA not found")
+
+ conn = _connect()
+ cur = conn.cursor()
+ cur.execute(
+ """
+ SELECT id,element_id,name,label,description,testrl,teenrl
+ FROM element WHERE id=? AND soa_id=?
+ """,
+ (element_id, soa_id),
+ )
+ row = cur.fetchone()
+ if not row:
+ raise HTTPException(404, f"Element id={int(element_id)} not found")
+
+ before = {
+ "id": row[0],
+ "element_uid": row[1],
+ "name": row[2],
+ "label": row[3],
+ "description": row[4],
+ "testrl": row[5],
+ "teenrl": row[6],
+ }
+
+ new_name = (payload.name if payload.name is not None else before["name"]) or ""
+ 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_testrl = payload.testrl if payload.testrl is not None else before["testrl"]
+ new_teenrl = payload.teenrl if payload.teenrl is not None else before["teenrl"]
+
+ cur.execute(
+ """
+ UPDATE element SET name=?,label=?,description=?,testrl=?,teenrl=?
+ WHERE id=? AND soa_id=?
+ """,
+ (
+ _nz(new_name),
+ _nz(new_label),
+ _nz(new_description),
+ _nz(new_testrl),
+ _nz(new_teenrl),
+ element_id,
+ soa_id,
+ ),
+ )
+ conn.commit()
+ cur.execute(
+ """
+ SELECT id,element_id,name,label,description,testrl,teenrl
+ FROM element WHERE id=? AND soa_id=?
+ """,
+ (element_id, soa_id),
+ )
+ r = cur.fetchone()
+ conn.close()
+ after = {
+ "id": r[0],
+ "element_uid": r[1],
+ "name": r[2],
+ "label": r[3],
+ "description": r[4],
+ "testrl": r[5],
+ "teenrl": r[6],
+ }
+ mutable = [
+ "name",
+ "label",
+ "description",
+ "testrl",
+ "teenrl",
]
+ updated_fields = [
+ f for f in mutable if (before.get(f) or None) != (after.get(f) or None)
+ ]
+ _record_element_audit(
+ soa_id,
+ "update",
+ element_id,
+ before=before,
+ after={**after, "updated_fields": updated_fields},
+ )
+ return {**after, "updated_fields": updated_fields}
+
+
+# UI code to update an element
+@router.post("/ui/soa/{soa_id}/elements/{element_id}/update")
+def ui_update_element(
+ request: Request,
+ soa_id: int,
+ element_id: int,
+ name: Optional[str] = Form(None),
+ label: Optional[str] = Form(None),
+ description: Optional[str] = Form(None),
+ testrl: Optional[str] = Form(None),
+ teenrl: Optional[str] = Form(None),
+):
+ payload = ElementUpdate(
+ name=name,
+ label=label,
+ description=description,
+ testrl=testrl,
+ teenrl=teenrl,
+ )
+ update_element(soa_id, element_id, payload)
+ return RedirectResponse(url=f"/ui/soa/{int(soa_id)}/elements", status_code=303)
+
+
+# API endpoint to delete an element
+@router.delete(
+ "/soa/{soa_id}/elements/{element_id}",
+ response_class=JSONResponse,
+ response_model=None,
+)
+def delete_element(soa_id: int, element_id: int):
+ if not soa_exists(soa_id):
+ raise HTTPException(404, "SOA not found")
+
+ conn = _connect()
+ cur = conn.cursor()
+ cur.execute(
+ "SELECT id,element_id,name,label FROM element WHERE soa_id=? AND id=?",
+ (soa_id, element_id),
+ )
+ row = cur.fetchone()
+ if not row:
+ raise HTTPException(404, f"Element id={int(element_id)} not found")
+
+ before = {
+ "id": row[0],
+ "element_uid": row[1],
+ "name": row[2],
+ "label": row[3],
+ }
+ cur.execute(
+ "DELETE FROM element WHERE soa_id=? AND id=?",
+ (soa_id, element_id),
+ )
+ conn.commit()
+ # reindex remaining elements' order_index sequentially
+ cur.execute(
+ "SELECT id FROM element WHERE soa_id=? ORDER BY order_index",
+ (soa_id,),
+ )
+ remaining = [r[0] for r in cur.fetchall()]
+ for idx, eid in enumerate(remaining, start=1):
+ cur.execute(
+ "UPDATE element SET order_index=? WHERE id=?",
+ (idx, eid),
+ )
+ conn.commit()
conn.close()
- return JSONResponse(rows)
+ _record_element_audit(soa_id, "delete", element_id, before=before, after=None)
+ return {"deleted": True, "id": element_id}
+
+
+# UI code to delete an element
+@router.post("/ui/soa/{soa_id}/elements/{element_id}/delete")
+def ui_delete_element(request: Request, soa_id: int, element_id: int):
+ delete_element(soa_id, element_id)
+ return RedirectResponse(url=f"/ui/soa/{int(soa_id)}/elements", status_code=303)
-@router.get("/elements/{element_id}", response_class=JSONResponse)
+# Deprecated
+@router.get("/soa/{soa_id}/elements/{element_id}", response_class=HTMLResponse)
def get_element(soa_id: int, element_id: int):
if not soa_exists(soa_id):
raise HTTPException(404, "SOA not found")
@@ -150,10 +380,11 @@ def get_element(soa_id: int, element_id: int):
}
-@router.get("/element_audit", response_class=JSONResponse)
+@router.get("/soa/{soa_id}/element_audit", response_class=JSONResponse)
def list_element_audit(soa_id: int):
if not soa_exists(soa_id):
raise HTTPException(404, "SOA not found")
+
conn = _connect()
cur = conn.cursor()
cur.execute(
@@ -190,6 +421,8 @@ def list_element_audit(soa_id: int):
return JSONResponse(rows)
+# Deprecated
+"""
@router.post("/elements", response_class=JSONResponse, status_code=201)
def create_element(soa_id: int, payload: ElementCreate):
if not soa_exists(soa_id):
@@ -285,8 +518,10 @@ def create_element(soa_id: int, payload: ElementCreate):
# Audit with logical StudyElement_N when available
_record_element_audit(soa_id, "create", eid, before=None, after=after)
return {**after, "element_id": eid}
+"""
-
+# Deprecated
+"""
@router.patch("/elements/{element_id}", response_class=JSONResponse)
def update_element(soa_id: int, element_id: int, payload: ElementUpdate):
if not soa_exists(soa_id):
@@ -392,8 +627,11 @@ def update_element(soa_id: int, element_id: int, payload: ElementUpdate):
after={**after, "updated_fields": updated_fields},
)
return JSONResponse({**after, "updated_fields": updated_fields})
+"""
+# Deprecated
+"""
@router.delete("/elements/{element_id}", response_class=JSONResponse)
def delete_element(soa_id: int, element_id: int):
if not soa_exists(soa_id):
@@ -445,9 +683,11 @@ def delete_element(soa_id: int, element_id: int):
after=None,
)
return JSONResponse({"deleted": True, "id": element_id})
+"""
# Deprecated - no need to reorder elements
+"""
@router.post("/elements/reorder", response_class=JSONResponse)
def reorder_elements_api(soa_id: int, order: List[int]):
if not soa_exists(soa_id):
@@ -475,3 +715,4 @@ def reorder_elements_api(soa_id: int, order: List[int]):
after={"new_order": order},
)
return JSONResponse({"ok": True, "old_order": old_order, "new_order": order})
+"""
diff --git a/src/soa_builder/web/routers/visits.py b/src/soa_builder/web/routers/visits.py
index fa1288e..82b834b 100644
--- a/src/soa_builder/web/routers/visits.py
+++ b/src/soa_builder/web/routers/visits.py
@@ -120,7 +120,7 @@ def ui_list_visits(request: Request, soa_id: int):
transition_rule_options = get_study_transition_rules(soa_id)
timing_options = get_timing_id(soa_id)
- logger.info(environmental_setting_options)
+ # logger.info(environmental_setting_options)
return templates.TemplateResponse(
request,
@@ -225,29 +225,6 @@ def add_visit(soa_id: int, payload: VisitCreate):
)
# 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
- 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"
- )
-
- if environmentalSettings:
- 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,
- ),
- )
- """
env_code_value = (payload.environmentalSettings or "").strip()
environmentalSettings = None
if env_code_value:
@@ -271,29 +248,6 @@ def add_visit(soa_id: int, payload: VisitCreate):
)
# 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:
diff --git a/src/soa_builder/web/templates/base.html b/src/soa_builder/web/templates/base.html
index b78c5ce..09147ec 100644
--- a/src/soa_builder/web/templates/base.html
+++ b/src/soa_builder/web/templates/base.html
@@ -16,6 +16,7 @@
Epochs
Arms
Encounters
+ Elements
{% endif %}
Biomedical Concept Categories
Biomedical Concepts
diff --git a/src/soa_builder/web/templates/edit.html b/src/soa_builder/web/templates/edit.html
index e09f56f..60584e6 100644
--- a/src/soa_builder/web/templates/edit.html
+++ b/src/soa_builder/web/templates/edit.html
@@ -114,59 +114,6 @@
Editing SoA {{ soa_id }}
-
- Elements ({{ elements|length }}) (drag to reorder)
-
- {% for el in elements %}
- -
- {{ el.order_index }}. {% if el.label %}{{ el.label }}{% else %}{{ el.name }}{% endif %}
-
- {% if transition_rules %}
-
-
- {% endif %}
-
-
-
-
- {% endfor %}
-
-
-
-
Study Cells ({{ study_cells|length }})
-
- Transition Rules ({{ transition_rules|length }})
-
-
-
diff --git a/src/soa_builder/web/templates/rules.html b/src/soa_builder/web/templates/rules.html
new file mode 100644
index 0000000..92f7d01
--- /dev/null
+++ b/src/soa_builder/web/templates/rules.html
@@ -0,0 +1,63 @@
+{% extends 'base.html' %}
+{% block content %}
+Transition Rules for SoA {{ soa_id }}
+
+
+
+
+{% endfor %}
+{% endblock %}
\ No newline at end of file
From b2354d25abafccdb1d9a13bdcd7c504883e6490e Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:02:22 -0500
Subject: [PATCH 08/17] Update src/soa_builder/web/templates/rules.html
adjusted colspan
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/templates/rules.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/templates/rules.html b/src/soa_builder/web/templates/rules.html
index 92f7d01..fab5cb5 100644
--- a/src/soa_builder/web/templates/rules.html
+++ b/src/soa_builder/web/templates/rules.html
@@ -56,7 +56,7 @@ Transition Rules for SoA {{ soa_id }}
{% else %}
- | No rules yet. |
+ No rules yet. |
{% endfor %}
From 756df9c84e8701f914e50f01f8e39eb451decb1b Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:02:39 -0500
Subject: [PATCH 09/17] Update src/soa_builder/web/templates/elements.html
spelling
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/templates/elements.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/templates/elements.html b/src/soa_builder/web/templates/elements.html
index 327381b..a4c99d9 100644
--- a/src/soa_builder/web/templates/elements.html
+++ b/src/soa_builder/web/templates/elements.html
@@ -34,7 +34,7 @@ Elements for SoA {{ soa_id }}
{% endfor %}
-
+
From 02d3132d435eb8733cbd2a7f68d268e36f3b4730 Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:02:58 -0500
Subject: [PATCH 10/17] Update src/soa_builder/web/templates/elements.html
adjusted colspan
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/templates/elements.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/templates/elements.html b/src/soa_builder/web/templates/elements.html
index a4c99d9..2eda4d4 100644
--- a/src/soa_builder/web/templates/elements.html
+++ b/src/soa_builder/web/templates/elements.html
@@ -86,7 +86,7 @@ Elements for SoA {{ soa_id }}
{% else %}
- | No elements yet. |
+ No elements yet. |
{% endfor %}
From 67d86367202bb7fa131be5c37b4faec02e847c5b Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:03:15 -0500
Subject: [PATCH 11/17] Update src/soa_builder/web/routers/elements.py
spelling
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/routers/elements.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/routers/elements.py b/src/soa_builder/web/routers/elements.py
index be24ada..fe173bb 100644
--- a/src/soa_builder/web/routers/elements.py
+++ b/src/soa_builder/web/routers/elements.py
@@ -146,7 +146,7 @@ def add_element(soa_id: int, payload: ElementCreate):
conn.close()
after = {
"id": id,
- "encounter_uid": new_uid,
+ "element_uid": new_uid,
"name": payload.name,
"label": (payload.label or "").strip() or None,
"description": (payload.description or "").strip() or None,
From f079ed1e8d259a125ac15fd19c88d1e2b589bc1d Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:03:30 -0500
Subject: [PATCH 12/17] Update src/soa_builder/web/routers/elements.py
included status_code
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/routers/elements.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/routers/elements.py b/src/soa_builder/web/routers/elements.py
index fe173bb..91fab3b 100644
--- a/src/soa_builder/web/routers/elements.py
+++ b/src/soa_builder/web/routers/elements.py
@@ -169,7 +169,7 @@ def ui_create_element(
teenrl: Optional[str] = Form(None),
):
if not soa_exists(soa_id):
- raise HTTPException("SOA not found")
+ raise HTTPException(404, "SOA not found")
payload = ElementCreate(
name=name,
From 9cd18a5a9fb330ed341d34ee32bb5a37c7b830fc Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:03:49 -0500
Subject: [PATCH 13/17] Update src/soa_builder/web/templates/rules.html
spelling
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/templates/rules.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/templates/rules.html b/src/soa_builder/web/templates/rules.html
index fab5cb5..21e2568 100644
--- a/src/soa_builder/web/templates/rules.html
+++ b/src/soa_builder/web/templates/rules.html
@@ -24,7 +24,7 @@ Transition Rules for SoA {{ soa_id }}
-
+
| UID |
Name |
From 6a910e58c37acfc9ff0623e04d58d9c89bfe3332 Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:04:04 -0500
Subject: [PATCH 14/17] Update src/soa_builder/web/templates/elements.html
spelling
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/templates/elements.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/templates/elements.html b/src/soa_builder/web/templates/elements.html
index 2eda4d4..a3bc383 100644
--- a/src/soa_builder/web/templates/elements.html
+++ b/src/soa_builder/web/templates/elements.html
@@ -38,7 +38,7 @@ Elements for SoA {{ soa_id }}
-
+
| UID |
Name |
From 597db17b8917cada92b45c8de9c891941d230007 Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:04:17 -0500
Subject: [PATCH 15/17] Update src/soa_builder/web/routers/rules.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/routers/rules.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/routers/rules.py b/src/soa_builder/web/routers/rules.py
index d0d4860..3e9fad2 100644
--- a/src/soa_builder/web/routers/rules.py
+++ b/src/soa_builder/web/routers/rules.py
@@ -89,7 +89,7 @@ def add_rule(soa_id: int, payload: RuleCreate):
name = (payload.name or "").strip()
if not name:
- raise HTTPException(404, "Transition Rule name required")
+ raise HTTPException(400, "Transition Rule name required")
conn = _connect()
cur = conn.cursor()
From e18ada020d0bdf99fed6bf73cdf0862a1512c12a Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:04:27 -0500
Subject: [PATCH 16/17] Update src/soa_builder/web/routers/elements.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/routers/elements.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/routers/elements.py b/src/soa_builder/web/routers/elements.py
index 91fab3b..ec47597 100644
--- a/src/soa_builder/web/routers/elements.py
+++ b/src/soa_builder/web/routers/elements.py
@@ -91,7 +91,7 @@ def add_element(soa_id: int, payload: ElementCreate):
name = (payload.name or "").strip()
if not name:
- raise HTTPException(404, "Element name required")
+ raise HTTPException(400, "Element name required")
conn = _connect()
cur = conn.cursor()
From 83c79ae607b400371d7812f8bf20887c52fe3764 Mon Sep 17 00:00:00 2001
From: Darren <3921919+pendingintent@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:05:05 -0500
Subject: [PATCH 17/17] Update src/soa_builder/web/routers/rules.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/soa_builder/web/routers/rules.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/soa_builder/web/routers/rules.py b/src/soa_builder/web/routers/rules.py
index 3e9fad2..b33457e 100644
--- a/src/soa_builder/web/routers/rules.py
+++ b/src/soa_builder/web/routers/rules.py
@@ -196,7 +196,7 @@ def update_rule(soa_id: int, rule_id: int, payload: RuleUpdate):
before = {
"id": row[0],
- "transition_rule_id": row[1],
+ "transition_rule_uid": row[1],
"name": row[2],
"label": row[3],
"description": row[4],