Release v 1.1#187
Conversation
| _record_bc_surrogate_audit( | ||
| soa_id, "update", surrogate_id, before=before, after=after | ||
| ) | ||
| return RedirectResponse(f"/ui/soa/{soa_id}/activities", status_code=303) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
Copilot Autofix
AI 2 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| _record_bc_surrogate_audit( | ||
| soa_id, "delete", surrogate_id, before=before, after=None | ||
| ) | ||
| return RedirectResponse(f"/ui/soa/{soa_id}/activities", status_code=303) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
Copilot Autofix
AI 2 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
There was a problem hiding this comment.
Pull request overview
Release-oriented update for the SoA Workbench adding USDM v4.0 support pieces (schema validation submodule + USDM output extensions), plus new UI/API capabilities for biomedical concept surrogates and matrix footnotes/superscripts.
Changes:
- Add Biomedical Concept Surrogates (DB tables, API/UI router, USDM export, tests, and UI integration in concepts cells / XLSX export).
- Add footnotes + matrix cell superscripts (DB migrations, UI endpoints, edit-page rendering).
- USDM generator tweaks and performance improvements (prefetching mappings, safer timing code objects, extra indexes).
Reviewed changes
Copilot reviewed 30 out of 46 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_routers_bc_surrogates.py | Adds API + USDM integration tests for BC surrogates. |
| tests/test_routers_activities.py | Updates reorder tests to new request shape. |
| src/usdm/usdm_utils.py | Adjusts BC property output fields and AliasCode typing. |
| src/usdm/generate_usdm.py | Adds bcSurrogates to the full USDM StudyVersion output; improves logging. |
| src/usdm/generate_study_timings.py | Makes timing type / relativeToFrom optional code objects. |
| src/usdm/generate_encounters.py | Prefetches code/rule/timing lookups to reduce per-row queries; tweaks transition rule nullability. |
| src/usdm/generate_biomedical_concepts.py | Provides a default reference URL when missing. |
| src/usdm/generate_bc_surrogates.py | New USDM generator for BC surrogates. |
| src/usdm/generate_activities.py | Prefetches concept/surrogate mappings; emits bcSurrogateIds. |
| src/soa_builder/web/templates/edit.html | Renders superscripts + inline edit controls; adds footnote display and management UI. |
| src/soa_builder/web/templates/concepts_cell.html | Shows surrogate chips alongside concept chips and updates counts/actions. |
| src/soa_builder/web/templates/activities.html | Adds UI for defining/editing BC surrogates and linking them to activities. |
| src/soa_builder/web/schemas.py | Adds Pydantic schemas for BC surrogate create/update payloads. |
| src/soa_builder/web/routers/visits.py | Adjusts transaction commit placement; fixes connection close on 404. |
| src/soa_builder/web/routers/rollback.py | Removes unused DB_PATH constant. |
| src/soa_builder/web/routers/freezes.py | Uses shared _connect(); removes local SQLite wiring. |
| src/soa_builder/web/routers/footnotes.py | New footnotes API/UI router with audit logging. |
| src/soa_builder/web/routers/bc_surrogates.py | New BC surrogate API/UI router + activity linking endpoints + HTMX partial rendering. |
| src/soa_builder/web/routers/activities.py | Makes activity_uid immutable; changes reorder body shape; adds surrogate data for templates. |
| src/soa_builder/web/migrate_database.py | Adds indexes + new tables/columns for footnotes, superscripts, and surrogates. |
| src/soa_builder/web/initialize_database.py | Creates footnote table during fresh DB init. |
| src/soa_builder/web/db.py | Improves PRAGMA failure logging. |
| src/soa_builder/web/audit.py | Adds audit recorders for footnotes and BC surrogates; removes defensive CREATE TABLE blocks. |
| src/soa_builder/web/app.py | Integrates new migrations/routers; adds superscript helpers and surrogate export/XLSX support. |
| requirements.txt | Loosens requests dependency constraint. |
| README.md | Adds submodule clone instructions; updates USDM export section. |
| output/report/NCT01797120_validation_report.xlsx | Adds a generated validation report artifact. |
| images/image-7.png | Documentation screenshot (surrogate selection UI). |
| images/image-5.png | Documentation screenshot. |
| images/image-4.png | Documentation screenshot. |
| files/NCT01797120_Footnote_2.html | Example footnote HTML content. |
| files/NCT01797120_Footnote_1.html | Example footnote HTML content (currently malformed). |
| docs/BC_WORKFLOW.md | New/expanded documentation for BC/DSS workflow and surrogates. |
| .gitmodules | Adds cdisc-json-validation submodule reference. |
| .gitignore | Adjusts ignored output patterns and adds additional local artifact ignores. |
| resp = client.post( | ||
| f"/soa/{soa_id}/activities/reorder", json={"order": [a3, a1, a2]} | ||
| ) |
There was a problem hiding this comment.
The reorder tests now POST a JSON object {order: [...]} to /soa/{soa_id}/activities/reorder, but the current handler (src/soa_builder/web/app.py at the same route) expects the request body to be a bare JSON array (List[int]). This will result in a 422 and make the test fail.
Align the test payload shape with the actual endpoint contract (or update the endpoint to Body(..., embed=True) consistently).
| if request.headers.get("HX-Request") == "true": | ||
| return HTMLResponse("", headers={"HX-Redirect": redirect_url}) | ||
|
|
||
| return RedirectResponse(redirect_url, status_code=303) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
Copilot Autofix
AI 2 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| if request.headers.get("HX-Request") == "true": | ||
| return HTMLResponse("", headers={"HX-Redirect": redirect_url}) | ||
|
|
||
| return RedirectResponse(redirect_url, status_code=303) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
Copilot Autofix
AI 2 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| if request.headers.get("HX-Request") == "true": | ||
| return HTMLResponse("", headers={"HX-Redirect": redirect_url}) | ||
|
|
||
| return RedirectResponse(redirect_url, status_code=303) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
Copilot Autofix
AI 2 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
… so deleted UIDs are never recycled
| hx_vals_attr = _html.escape(hx_vals_json, quote=True) | ||
|
|
||
| return ( | ||
| f'<td hx-post="/ui/soa/{soa_id_safe}/toggle_cell"' |
There was a problem hiding this comment.
_render_cell_td() returns a <td> without data-visit-id / data-instance-id. The matrix reorder JS in edit.html relies on td[data-visit-id] to reorder columns; after an HTMX swap from toggle/superscript-save, those cells will no longer participate in reordering. Include the same data-* attributes in the returned <td> so swapped cells keep working with the existing client-side reorder logic.
| f'<td hx-post="/ui/soa/{soa_id_safe}/toggle_cell"' | |
| f'<td data-instance-id="{instance_id_safe}" data-activity-id="{activity_id_safe}"' | |
| f' hx-post="/ui/soa/{soa_id_safe}/toggle_cell"' |
| conn.close() | ||
| _record_bc_surrogate_audit( | ||
| soa_id, "delete", surrogate_id, before=before, after=None | ||
| ) | ||
| return RedirectResponse(f"/ui/soa/{soa_id}/activities", status_code=303) |
There was a problem hiding this comment.
_render_concepts_cell() queries activity_concept with ... WHERE activity_id=? AND soa_id=?. Elsewhere the codebase defensively checks whether activity_concept has an soa_id column for backward-compatible databases. To avoid breaking upgraded deployments with legacy schemas, add the same column-existence guard here (or join via activity when soa_id is absent).
| """Add standalone soa_id indexes on high-traffic tables. | ||
|
|
||
| The existing UNIQUE constraints cover (soa_id, uid) lookups, but bare | ||
| WHERE soa_id=? list queries do full table scans without a leading index. | ||
| These indexes cover the ~259 soa_id filter sites in the codebase. | ||
| """ | ||
| try: | ||
| conn = _connect() | ||
| cur = conn.cursor() | ||
| indexes = [ | ||
| ("idx_activity_soa", "activity", "soa_id"), | ||
| ("idx_visit_soa", "visit", "soa_id"), | ||
| ("idx_matrix_cells_soa", "matrix_cells", "soa_id"), | ||
| ("idx_activity_concept_soa", "activity_concept", "soa_id"), | ||
| ("idx_instances_soa", "instances", "soa_id"), | ||
| ("idx_timing_soa", "timing", "soa_id"), |
There was a problem hiding this comment.
Most of these new indexes appear redundant because the tables already have UNIQUE constraints whose backing indexes start with soa_id (e.g., UNIQUE(soa_id, activity_uid) on activity, UNIQUE(soa_id, encounter_uid) on visit, etc.), which SQLite can use for WHERE soa_id=? queries. Adding additional single-column soa_id indexes increases write cost and DB size without improving those queries. Consider limiting this migration to tables that don’t already have a leading (soa_id, ...) index (e.g., matrix_cells, activity_concept).
| """Add standalone soa_id indexes on high-traffic tables. | |
| The existing UNIQUE constraints cover (soa_id, uid) lookups, but bare | |
| WHERE soa_id=? list queries do full table scans without a leading index. | |
| These indexes cover the ~259 soa_id filter sites in the codebase. | |
| """ | |
| try: | |
| conn = _connect() | |
| cur = conn.cursor() | |
| indexes = [ | |
| ("idx_activity_soa", "activity", "soa_id"), | |
| ("idx_visit_soa", "visit", "soa_id"), | |
| ("idx_matrix_cells_soa", "matrix_cells", "soa_id"), | |
| ("idx_activity_concept_soa", "activity_concept", "soa_id"), | |
| ("idx_instances_soa", "instances", "soa_id"), | |
| ("idx_timing_soa", "timing", "soa_id"), | |
| """Add standalone soa_id indexes on selected high-traffic tables. | |
| The existing UNIQUE constraints cover (soa_id, uid) lookups, but bare | |
| WHERE soa_id=? list queries do full table scans without a leading index. | |
| These indexes cover the ~259 soa_id filter sites in the codebase, limited | |
| to tables that do not already have a leading (soa_id, ...) index. | |
| """ | |
| try: | |
| conn = _connect() | |
| cur = conn.cursor() | |
| # Only add standalone soa_id indexes for tables that lack an existing | |
| # leading (soa_id, ...) index from a UNIQUE constraint. | |
| indexes = [ | |
| ("idx_matrix_cells_soa", "matrix_cells", "soa_id"), | |
| ("idx_activity_concept_soa", "activity_concept", "soa_id"), |
| _type_entry = type_code_map.get(type, ([], [], [], [])) | ||
| t_code, t_decode, t_codeSystem, t_codeSystemVersion = _type_entry | ||
|
|
There was a problem hiding this comment.
type_code_map.get(type, ([],[],[],[])) can yield empty lists when visit.type is NULL/blank or when the code association is missing. The code later indexes into t_code[0] / t_decode[0], which will raise IndexError and fail USDM encounter generation. Treat type as optional (set it to None when missing/unmapped), similar to how timings now handle missing codes.
| @@ -0,0 +1,414 @@ | |||
| # Workflow for linking Biomedical Concept with Scheduled Activity Instance | |||
|
|
|||
| Assumes that the Activties and Scheduled Activity Instances have been created to form the SOA Matrix. | |||
There was a problem hiding this comment.
Typo: “Activties” → “Activities”. This affects readability and searchability in the documentation.
| Assumes that the Activties and Scheduled Activity Instances have been created to form the SOA Matrix. | |
| Assumes that the Activities and Scheduled Activity Instances have been created to form the SOA Matrix. |
| cur.execute( | ||
| "INSERT INTO footnote (soa_id, footnote_uid, name, label, description, text, dictionary_uid) VALUES (?,?,?,?,?,?,?)", | ||
| ( | ||
| soa_id, | ||
| uid, | ||
| body.name, | ||
| body.label or None, | ||
| body.description or None, | ||
| body.text or None, | ||
| body.dictionary_uid or None, | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Footnote text is stored verbatim (intended XHTML) and later rendered with | safe in the edit template. Without server-side sanitization/validation, this creates a stored-XSS risk. Consider sanitizing text on create/update (e.g., allowlist tags/attributes) or storing a safe structured representation.
Added submodule to support validation against the USDM v4.0 schema .
Added a markdown file to explain the biomedical concept workflow.
Various fixes.