diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0b903ee --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,45 @@ +name: Deploy site (staging) + +on: + push: + branches: [main] + paths: ["site/**", "assets/brand/**", ".github/workflows/deploy.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - uses: astral-sh/setup-uv@v6 + - uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node22-quartz-npm-${{ hashFiles('site/build.sh', 'site/quartz.config.yaml', 'site/plugins/**/package.json') }} + restore-keys: | + ${{ runner.os }}-node22-quartz-npm- + - name: Check and build staging + run: bash site/build.sh --target staging + - name: Deploy to Cloudflare staging + if: env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != '' + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ env.CLOUDFLARE_API_TOKEN }} + accountId: ${{ env.CLOUDFLARE_ACCOUNT_ID }} + command: deploy --config site/wrangler.jsonc --env staging + - name: Cloudflare deployment not configured + if: env.CLOUDFLARE_API_TOKEN == '' || env.CLOUDFLARE_ACCOUNT_ID == '' + run: echo "::notice::Staging build succeeded. Deployment skipped because Cloudflare secrets are unset." diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml deleted file mode 100644 index 5956021..0000000 --- a/.github/workflows/site.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Deploy site - -on: - push: - branches: [main] - paths: - - "knowledge/**" - - "site/**" - workflow_dispatch: - -env: - # Kept in sync with QUARTZ_SHA in site/build.sh; only used here as a cache key. - QUARTZ_SHA: 97a2d05f80c4c50534959b1d0d41cc4b3895625e - -permissions: - contents: read - pages: write - id-token: write - -# Only one deploy at a time; let an in-flight run finish rather than cancelling it. -concurrency: - group: pages - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 24 - - - name: Cache npm - uses: actions/cache@v4 - with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ env.QUARTZ_SHA }} - restore-keys: | - ${{ runner.os }}-npm- - - # Drives the exact same script a contributor runs locally, so CI and - # local builds can't drift apart. See site/build.sh and site/README.md. - - name: Build site - run: bash site/build.sh - - - uses: actions/configure-pages@v5 - - - uses: actions/upload-pages-artifact@v3 - with: - path: public - - deploy: - needs: build - runs-on: ubuntu-latest - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9f89241..a08397f 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -18,3 +18,7 @@ jobs: run: uv run .agents/skills/validate/scripts/okf_validate.py knowledge --strict - name: House rules run: uv run tools/check_bundle.py + - name: Source manifest and recovery tests + run: | + python3 tools/sources.py manifest + python3 -m unittest discover -s tests -p test_sources.py diff --git a/.gitignore b/.gitignore index 053dee5..75afe7e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,12 @@ public/ # tracked, despite the blanket `dist/` rule above. !site/okf-meta-plugin/dist/ !site/okf-meta-plugin/dist/** +viz.html +.playwright-cli/ + +# Raw source material reviewed for the bundle (may contain unpublished planning); never committed +/sources +.wrangler/ + +# Local review renders, research scratch, and disposable outputs. +/scratch/ diff --git a/.peruseshow b/.peruseshow new file mode 100644 index 0000000..953caaa --- /dev/null +++ b/.peruseshow @@ -0,0 +1,2 @@ +# Local review artifacts stay ignored by Git but are browsable in Peruse. +scratch/review/ diff --git a/CHANGELOG.md b/CHANGELOG.md index eae2643..440ae94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,29 @@ All notable reader-facing changes to this knowledgebase. Format follows [Keep a ## [Unreleased] +### Changed + +- Website commands now use `task site:...` from the repository root; knowledge checks and review commands remain separate. +- The website builds only `site/content/`, with its own navigation and OIDM branding. Dev and staging show a draft banner; production builds require page review stamps. + +- Moved the old generated bundle to temporary `archive/knowledge`; `knowledge/` now starts a fresh reconstruction from supported sources. + +- Consolidated planning into one implementation plan, preserved owner guidance and source inventories in references, and moved temporary review previews out of tracked documentation. + +- Tightened 112 knowledge pages and their navigation descriptions, reducing repetition while preserving examples and source extracts; a five-part review then restored qualifiers, disclaimers, and quoted source material the tightening had dropped, and reverted twelve pages. +- Diagrams are Excalidraw-rendered SVGs composed for the text column, with click-to-zoom on the site; Mermaid is no longer used. + ### Added +- Full Lucide and Feather license notices in diagram icons and SVGs; PNG exports now include accompanying license files. + +- Source manifest with provenance and handling rules, plus commands to recover and checksum-verify selected source files from Tigris without overwriting local changes. + +- Source-grounded summaries of 15 project diagram boards, indexed by idea and pillar with links to the source material. +- Reusable OIDM and Anatomic Locations logos for site content and diagrams, with the original style guide and usage notes. + - First full draft of the knowledgebase: 115 documents in OKF 0.2 covering the overview and vision, a 48-term glossary, the semantic foundation (finding models, common data elements, anatomic locations, exam types, terminologies), the data structures (Observation, Exam Finding List, Imaging Problem List, Imaging Persona, FHIR and IHE alignment), the applications, the roadmap and open questions, project history, a repository map, and verbatim reference extracts. - Authoring guide, contributing guide, and migration ledger recording every document migrated from a working repository. - Two validation gates (OKF 0.2 conformance and house rules) run in continuous integration. -- Rendered site built with Quartz and deployed to GitHub Pages, with frontmatter badges showing each document's type, status, provenance, and verification. +- Website built with Quartz, with separate Cloudflare targets for dev, staging, and production. - All documents are status draft pending the project lead's verification. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 594e448..e57fae3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,16 @@ # Contributing -Thank you for helping document the Open Imaging Data Model. This repository holds the canonical high-level documentation; code documentation stays in the working repositories. +This repository holds source-grounded Open Imaging Data Model knowledge and its presentation pages. Code documentation stays in the working repositories. ## Before you write -Read the [authoring guide](knowledge/guides/authoring-guide.md). It defines the document types, the frontmatter every file needs, the trust workflow, how links work, and how to migrate a document from a working repository. Agents follow the same guide. +Start with the [implementation plan](docs/plans/implementation.md) for current destinations, source rules, and review state. Maintain that plan as work progresses rather than adding overlapping task plans. + +New knowledge drafting remains paused until the owner completes the remaining execution-plan review. The approved organization and first node do not approve the unanswered review workflow. + +Read [Writing the new knowledge graph](docs/knowledge-authoring.md). It describes fresh reconstruction, metadata, links, sources, and review. The old generated bundle is temporarily held in `archive/knowledge`; write new documents from the outline and checked sources, then consult the archive for omissions. Do not edit old prose into replacements. Delete the archive after recovery is complete, as recorded in the implementation plan. + +Source collection and recovery instructions are in [Recover and verify source material](docs/sources.md). Run `task sources:manifest` when changing source metadata; recovery code tests run in CI without private data. ## Ground rules @@ -18,7 +24,7 @@ Read the [authoring guide](knowledge/guides/authoring-guide.md). It defines the 1. Branch from `main`. 2. Write or edit under `knowledge/`. New agent-written documents start with `status: draft`. -3. Update the directory `index.md`, add a line to `knowledge/log.md`, and, for migrations, a row in the migration ledger. +3. Update the directory `index.md` and add a line to `knowledge/log.md`. Record substantive recovery decisions in the implementation plan as needed. 4. Run both checkers and fix every error: ```bash diff --git a/DEV_LOG.md b/DEV_LOG.md index 8125d28..331c703 100644 --- a/DEV_LOG.md +++ b/DEV_LOG.md @@ -21,3 +21,107 @@ Engineering narrative for the oidm-knowledge repository: decisions, blockers, an - Phases 2 to 5 complete: six Opus agents wrote the semantic foundation, data structures, applications, and roadmap; each appended source conflicts to a collected file that became `roadmap/open-questions.md` (90 entries plus 21 upstream defects to file as issues). Project lead decisions applied: the taxonomies are the "MGB exam-oriented sub-taxonomies"; the next-generation vocabulary document is published as draft; the use case catalog is published with its committee credit. - Integration: ledger (118 rows) and log merged from agent scratch files; naming made consistent; a dead URL and an exam count corrected; both validators report 115 concepts, 0 errors, 0 warnings; the site rebuilds. Remaining Phase 6 work is the project lead's verification pass, then the first release entry and the follow-up issues (source-repo link-back PRs, upstream defect reports, manuscripts). - Added the project lead's stated goals verbatim to the plan so that documents citing the planning interview have a written source (open question 82). +- Initial local commit made, marked pre-review. Added `Taskfile.yml` (`task check|build|serve|publish|viz`). `task publish` validates, builds Quartz with a bucket-specific config, rewrites internal links to explicit `.html` paths with `tools/flatten_site_links.py` (Quartz has no file-style link option; plain object storage has no clean-URL rewriting), and uploads to the Tigris bucket. Published at https://oidm-public.t3.storage.dev/oidm-knowledge/index.html and verified. Root index now carries a pre-review banner. +- Bucket links, second pass: the first flattening only rewrote hrefs in the HTML, so links built in the browser (explorer sidebar, search results, graph node clicks) still pointed at extensionless URLs and returned 404 on the bucket. `tools/flatten_site_links.py` now also injects a small script into every page that applies the same rule at link creation and click time, and patches the emitted JavaScript so direct `window.location` navigations pass through it. Verified in a real browser against the bucket: sidebar click, search result click, and body links all resolve. +- Diagrams: the project lead rejected Mermaid. Replaced all three Mermaid blocks (architecture, data-structure hierarchy, repository map) with Excalidraw diagrams built by small Python builders in `tools/diagrams/` (shared helpers in `excalib.py`), rendered to SVG through the Excalidraw library in a headless browser (`render_excalidraw.py`, module pinned to 0.18.0 after the unpinned esm.sh build broke), and iterated by viewing each render: architecture took three versions, the repository map four, the hierarchy two. The `.excalidraw` source sits beside each document with its SVG. Diagram iteration was delegated to a Sonnet agent after the first diagram. +- Site: Quartz assumes a host that rewrites clean URLs; the bucket does not. A Starlight spike (file-style output, site-graph plugin for graph and backlinks) is running in the scratchpad; the choice of generator goes to the project lead for review before anything changes. New rule recorded: every tool selection is reviewed with the project lead first. +- Sources: the project lead added seven manuscript and deck files to the bucket folder oidm-knowledge-sources; an agent is extracting and summarizing them into the gitignored `sources/bucket/`. +- Repository review: both validators pass (115 concepts, zero errors or warnings), and the Quartz build succeeds for all 133 Markdown files. All concepts remain unverified drafts. Source inventories now exist for the imported manuscripts/decks and 15 Excalidraw canvases; integration remains outstanding. Recorded publishing workflow gaps, source-watching behavior, documentation discrepancies, and the absence of a configured Git remote in `docs/plans/repository-review.md`. Review only; no publishing or commits. +- Concurrent editing: a Codex session is rewriting bundle documents in this working tree (112 documents, generated.by set to codex/gpt-6, prose tightened, descriptions changed, no warnings remaining after reconciliation). Per the project lead, that run proceeds first; structure and index reconciliation follow once it finishes. Rule for this session: never overwrite a document whose generated.by is another agent's. +- Verification tooling added: `task verify -- ` stamps a human verified entry and status stable; `task review-status` and `task review-list -- ` report progress. The project lead starts verification on the stable areas (glossary, history, references, semantic foundation). +- Remote created at github.com/openimagingdata/oidm-knowledge (public); the deploy workflow is disabled; a local pre-push guard blocks agent pushes of main, so the project lead pushes. +- Editorial pass completed with three sub-agents using pstack `unslop` and `technical-writing`: 18 concepts shortened from 21,186 to 15,732 body words (25.7%). Synchronized index descriptions and retained examples, citations, and draft status. Cross-review restored qualifications lost during shortening. Scope and verification are recorded in `docs/plans/lean-content-pass.md`; no publishing or commits by this session. +- Hosting decided by the project lead: a Cloudflare static site, extensible with Workers. Set up as an assets-only Worker (`site/wrangler.jsonc`, html_handling auto-trailing-slash, which matches Quartz's clean-URL links natively). Quartz stays the generator on maturity grounds after a survey of wiki and knowledge-graph tools found nothing else both mature and maintained; typed-edge graph is a plugin to write later. Retired the object-storage pipeline (link flattener, bucket publish script, GitHub Pages workflow) in favor of `task deploy` and `.github/workflows/deploy.yml` (cloudflare/wrangler-action; needs CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID secrets). First deploy waits on the project lead's `wrangler login`. +- First Cloudflare deploy: https://oidm-knowledge-dev.talkasab.workers.dev, verified in a browser (clean URLs, sidebar, search, SVG diagrams, no console errors). Targets: dev (default), staging, prod as separate Workers via wrangler environments; `SITE_BASE_URL` sets Quartz's baseUrl per target at build time. The push-to-main workflow deploys to staging; production is a deliberate `task deploy:prod`. +- Full editorial review completed after the project lead rejected the initial 18-page cutoff. Three agents and the main editor read the remaining 97 concepts, all 17 indexes, and the historical bundle log; 94 further concepts changed, with three concise glossary references retained. Across both passes, concept bodies total 108,656 → 94,944 words (12.6% reduction at the review snapshot). Cross-review restored technical constraints and checked the element-binding quotation against its pinned source. Coverage and unchanged-page reasons are in `docs/plans/lean-content-coverage.tsv`; results and count methodology are in `docs/plans/lean-content-full-review.md`. Both validators pass with zero errors or warnings, and whitespace checks pass. Concurrent hosting/tooling work was preserved; no commits or publication by this session. + +## 2026-09-22 + +- Restructure planning with the Codex session via `docs/plans/`: Codex wrote `restructure-around-project-ideas.md`; this session wrote `restructure-joint-notes.md` and two idea inventories (`idea-inventory-semantic.md`, 46 ideas; `idea-inventory-structures-and-uses.md`, 41 ideas) from the repositories' design documents, plans, prompts, skills, and ADRs. The project lead withdrew an inventory-derived 41-page list: the structure is the human one (the original brief and the January deck), now four pillars (semantic foundation; data structures; applications and uses; SDKs and sample applications). Ideas mined from repository documents and prompts are the team's own ideas and enter as first-class content; only agent inference is limited to linking prose. `idea-placement.md` places all 87 inventoried ideas under the pillar pages by source kind. +- Read the SIIM 2026 "Reports of the Future" deck (June 2026, public) directly, including its six figures, at the project lead's request. Its Patient Context / Foundation Context framing, three axes, "connective tissue" of relationships and external citations, "resolve, don't reinvent" SDK mechanism, and the ask now sit above the four pillars in the joint notes; a relationships-and-citations page was added to pillar 1. Reading recorded in `sources/bucket/REPORT.md` section 8; source-map rows added to the build plan. No drafting has started; page list awaits Codex's reply and the project lead's review. +- Planning conversation with the Codex session, framed by Codex at the project lead's request, produced a shared understanding recorded in the joint notes: the source eligibility rule, "related representations" wording, the organizing method, and real deletion of redundant generated material. The project lead ruled during it: a CDE and a finding model are the same content in two collections with different review expectations, both moving to the CDE schema rewrite's classes; the OIFM reorganization around that graph is urgent; and the structure is five named pillars (Foundation Context, Data Structures, SDKs, Use Cases, Sample Applications), used by name. The "Observation Type" umbrella the sessions had floated is withdrawn. The joint notes now carry the decisions, the agreed outline with the current pages that collapse into each part, and next steps; the placement table is being re-sectioned under the five names; the build plan gained a 2026-09-22 decisions table. The ACR-RSNA-CDEs session was told the rulings. Still no drafting. + + +- Codex completed three staged pillar drafts in `knowledge/drafts/`: Data Structures, Use Cases, and Sample Applications, totaling 2,704 body words excluding metadata and footnotes. Independent source review corrected coding guarantees, durable-identity claims, provenance review boundaries, and differing status vocabularies. Citations and pinned Git source paths check out; full bundle validation awaits the remaining Claude drafts and index reconciliation. Handoff is recorded in `docs/plans/restructure-codex-drafts.md`; source cross-review and page replacement remain pending. No commit or publication by this session. +- Drafting, Claude side: seven pages staged under `knowledge/drafts/` (introduction, foundation-context, finding-models-and-cdes, anatomic-locations, exam-types, standards, sdks) by four Opus agents, each reviewed by the main session and corrected (inventory IDs removed from prose, agent inferences cut, the deck's own labels used, an internal board cited without its link, American spelling). Three independent source-fidelity reviews of the Codex drafts written to `sources/review/`. Bundle validates at 144 documents, 0 errors, 0 warnings. Committed on `bootstrap`; pull request #1 opened from `bootstrap` into a newly created `main`. + +- Codex completed the source-fidelity cross-review of all seven Claude drafts, with three agents assisting; findings are in `sources/review/drafts-.md`. Claude pages were not edited by this session. Corrected the three Codex drafts and recorded dispositions, including unsupported review claims, in the corresponding response files. Key fixes cover the worked graph example, source-specific status and transport claims, live application links, and team evidence for built demonstrations. Revised bodies total 3,074 words. Strict OKF and bundle checks pass (144 documents, zero errors/warnings), with citation-ID and whitespace checks passing. Updated the drafting plan, joint handoff, and bundle log. Replacement remains separate; no commit or publication by Codex. + +## 2026-09-30 + +- Imported 16 original OIDM and Anatomic Locations logo/mark assets from Tigris into `knowledge/assets/brand/`, plus the 2023 style guide and a source/checksum manifest in `docs/brand/`. Documented logo typography, colors, the guide's conflicting light-blue RGB and hex values, and usage in site content and Excalidraw builders. Site and diagram READMEs link to the notes. The local Quartz build passed and emitted all assets unchanged; both bundle validators, manifest integrity checks, SVG embedding checks, and whitespace checks passed. The asset import leaves theme and diagram application for a separate task. Plan: `docs/plans/2026-09-30-brand-assets.md`, complete. + +## 2026-10-01 + +- Compared current OKF skills and toolchains, including lorsabyan and serradura, and qualified the earlier QMD suggestion. Ran 29 labeled external validator fixtures with baseline-policy warnings excluded: installed/current scaccogatto reported 13/19 problem cases and accepted 10/10 clean cases; lorsabyan reported 19/19 and accepted 10/10. Recorded the shared impossible-date crash, differing check scope, missing backfill agent payload, and unsafe index-prose rewriting. Actual-bundle trials exposed 15 repository-versus-bundle source-path warnings in the alternative validator. Findings and upstream reuse priorities are in `docs/references/okf-tooling-comparison.md`; no installation, bundle changes, commit, or publication. + +- Reviewed OKF tooling origins and current reuse options. All 13 installed skill files match upstream `scaccogatto/okf-skills` commit `d8393f3` byte for byte; custom code is the house checker, verification helper, and publishing integration. Recorded Google's canonical-repository move, available upstream MCP/CI support, QMD and Basic Memory tradeoffs, and metadata compatibility differences in `docs/references/okf-tooling-review.md`. Review plan complete. No tooling changes, commit, or publication. + +- Captured and visually reviewed all 15 unique Excalidraw boards in `links.txt` (17 URL occurrences). Native SVG/PNG exports, source contents, searchable text, and checksum metadata are in gitignored `sources/linked-diagrams/`; 15 source-grounded draft summaries and their index are staged in `docs/references/linked-diagrams/`. Three untouched SVG exports have normalized copies for XML compatibility. Verified coverage, image decoding, SVG parsing, 63 checksums, and local links; OKF conformance has zero errors, with 50 external-directory link warnings whose targets were checked separately. Plan `docs/plans/2026-10-01-linked-diagrams.md` is complete. The proposed layer migration, detailed reference conversion, and figure redraws await the owner's go; no bundle or presentation moves, upload, commit, or publication by this session. + +## 2026-10-02 + +- Verified all 307 Tigris objects against local sizes and SHA-256 via isolated downloads. Classified the collection and added a tracked manifest of 25 source groups / 118 artifacts; 189 review, research, and older-capture files remain privately inventoried and untouched. Implemented manifest/pull/check tasks with verified temporary downloads, conflict refusal, and source selection. Clean-directory recovery and repeat-pull checks passed for all 118 artifacts, as did eight failure-path tests and repository validation. CI checks the manifest and recovery tests without credentials. Object ACLs are disabled; anonymous HEAD checks returned 403 for three representative objects, without claiming exhaustive policy verification. No reviewer correspondence was inspected, no remote objects or access settings changed, and no commit was made. Source storage phase complete; detailed reference work follows. + +- Confirmed the project lead's Tigris upload with a recursive read-only listing of `oidm-knowledge-sources`: all 307 local file paths match remote object keys, with no missing or extra objects. Saved a private remote inventory alongside the local checksum inventory. Updated current instructions to replace bucket-creation/upload prerequisites with remote checksum verification, source classification, and manifest-based recovery. No remote writes; object-name coverage does not establish byte equality or access settings. + +- After the project lead created the destination, moved all 307 files from the repository source directory to `~/data/oidm-knowledge-sources`. Verified 154,370,058 bytes against per-file SHA-256 checksums and preserved all relative paths through an ignored `sources` symlink. The 31 private review files retain their separate directory; the full local inventory stays outside Git. Updated the existing implementation plan and source-location instructions with explicit upload selection, private-access checks, remote collision handling, and download/hash verification. No Tigris writes or commit. + +- Prepared the requested source relocation. The requested `~/data/oidm-knowledge-sources` directory is absent in this environment; asked whether to create it here or use another machine/mount. Counted 307 local source-area files without reading source documents and checked installed Tigris copy options. The configured CLI reports that `oidm-knowledge-sources` does not exist. Recorded the destination clarification and upload prerequisites in the existing implementation plan. No files moved or uploaded. + +- Consolidated planning into `docs/plans/implementation.md`, covering the private-source-to-reference-to-repository-knowledge-to-presentation pipeline. Removed superseded/completed plans and the old bundle plan after extracting cited owner guidance, source-review observations, and an 87-idea coverage reference. Kept completed brand/board validation with their reference documentation. Moved 116 generated previews intact into ignored `scratch/review/`, enabled local review with `.peruseshow`, and retained accepted content assets. Repaired citations and entrypoint instructions. Both bundle validators pass with zero errors or warnings, 404 local documentation links resolve, and the Quartz build succeeds. Move checksums and the local Peruse allowlist checks pass; the running Peruse server was unreachable for a live check. Pipeline implementation remains unfinished; no commit or deployment. + +Historical paths below identify records at the time of each entry. Current work is tracked only in the implementation plan; retained source evidence is in references, and removed committed records remain in Git history. + +- Reviewed the cleanup plans against the current repository and publishing code. Recorded the September replacement versus October layer conflict, mixed source/review artifacts, inconsistent task states, and unimplemented presentation separation in `docs/plans/2026-10-02-cleanup-review.md`. Proposed document reconciliation, file classification, one source-to-page pilot, and incremental migration. Both bundle validators pass with zero errors or warnings. Assessment only; no content moves, publishing changes, or commit. + +- Rechecked all 15 linked-diagram summaries after the share-link and meeting-date cleanup; generalized remaining discussion-history wording in four summaries. Local source artifacts remain linked. Both repository validators pass with zero errors or warnings; standalone reference conformance has zero errors and 50 external-directory link warnings, with targets checked separately. No commit by this session. + +- Refreshed `docs/HANDOFF.md` with completed brand and board-source work, links to provenance and validation records, and remaining reference-conversion work. Aligned capture staging notes with the layer plan's agreed-in-principle status; migration has not started. + +## 2026-10-03 + +- Recorded the project lead's reset in the existing implementation plan and handoff. Withdrew the assistant-selected SIIM pilot and content migration sequence; labeled the page map and figure queue as historical. The generated bundle is material to mine critically, with an organizing principle to be discussed one decision at a time before a replacement plan or content edits. Preserved completed cleanup and source-storage/tooling records. No content reorganization or commit. + +## 2026-10-05 + +- Recorded the owner's Foundation Context and Data Structures outline and the collaborative SDK outline in the existing implementation plan as candidate OKF knowledge nodes, with cross-section connections and unresolved boundaries. Requested a read-only Claude review through Herdr pane `wA:p5`. Updated the handoff; preserved the crafted presentation pages and all other working-tree content. Applications, Use Cases, and content execution remain pending discussion. No commit or publication. +- Saved the pending Applications outline after file access was restored, including the owner's anatomy and linked-exam browsing interfaces, green findings, object creation within PACS, and outcome tracking. Kept the outline hierarchy, replaced remaining vague uses of "account" in the proposed outline with "knowledge document," and updated the handoff and planning status. Outcome-tracking subdivisions remain proposals; Use Cases and content execution are still pending. No changes under `site/`, commit, or publication. + +## 2026-10-06 + +- Moved the remaining old generated bundle intact to `archive/knowledge`, preserving 138 files and 1,181,586 bytes with identical per-file SHA-256 checksums. Established a clean `knowledge/` with index and log only. Preserved recorded owner guidance separately in `docs/references/project-direction.md`; updated planning, handoff, contributor instructions, and changelog. The agreed method writes fresh from sources and the owner's outline, consults the archive afterward for omissions, and deletes the archive once useful recovery is complete. Strict OKF validation, house checks, source-manifest validation, updated-instruction links, and archive-preservation checks pass. Outward-facing work is untouched; no commit. + +- Audited the saved outline against the organizing conversation. Preserved the owner's original nested outline and subsequent additions in a separate reference, captured Claude's full review from the review pane, and linked both from the plan and handoff. Made the unfinished Use Cases section explicit, retained the named clinical code-system leads and relevant-prior application, and removed contradictory or obsolete planning instructions. No new graph concepts, site edits, or commit. + +- Recorded the owner's explicit Imaging Persona node for information to retrieve and how to represent it, and a Data Structures introduction covering ongoing community development grounded in implementation. Clarified evolving content guidelines and the separation of SDK library code, envisioned Use Cases, and implemented Sample Applications. Moved the proposed-use inventory under Use Cases and recorded the owner-identified green findings vendor prototype under Sample Applications. Preserved the owner's clarifications verbatim; no new graph documents, site changes, or commit. + +- Recorded preliminary Foundation content versioning with possible version-specific pointers and deprecation links to updated or replacing nodes. Added a proposed dedicated SDK node for longitudinal finding linking, connected to IPL representation, and dispatched the owner-requested research agent on broader clinical-information reconciliation. Both detailed versioning design and broader reconciliation remain open; no site changes or commit. + +- Saved the focused clinical-reconciliation research with six primary sources spanning IHE RECON, FHIR representation elements, clinical-document consolidation, and recent LLM temporal and consistency research. Distinguished published standards from trial profiles and research proposals, and linked the reference to Imaging Persona without adopting a schema or resolving conflicts by assumption. No site changes or commit. + +- Wrote a proposed execution sequence for the fresh knowledge tree into the single implementation plan, at the owner's request. It covers a first Foundation schema overview node, tree and document boundaries, source support, fresh drafting followed by archive checks, unresolved-design status, owner review and validation, and final archive deletion. The owner agreed to the schema overview as the first node and approved the tree structure, document-boundary approach, and evidence and attribution approach; the drafting and archive-checking workflow is the next review aspect. The remaining aspects await sequential discussion. No knowledge documents, directories, site files, or commits were created by this planning step. + +- Completed the owner-approved site-session handoff in root-owned files: included `site/Taskfile.yml` under `site:`, removed duplicate website tasks and hostname variables, retained knowledge verification tooling, and defaulted root review listing to `knowledge/`. Updated root README, handoff, planning state, and changelog for the separate `site/content/` website and namespaced commands. Knowledge and site checks pass; task discovery, eight routing dry runs, read-only review reports, documentation links, and site-path preservation checks pass. No site edits, review stamps, deployment, staging, or commit. + +## 2026-10-14 + +- Captured the clean-start checkpoint: first node, tree structure, and evidence approach are approved; the complete node cycle's owner-review point is the unanswered question. Recorded the remaining review sequence and confirmed that no knowledge concepts have been written. Added the later Claude review's explicit withdrawal of the unsupported IDR/FHIR claim while preserving the initial review as historical evidence. Research and root/site task integration are complete; no site changes or commit. + +## 2026-10-17 + +- Prepared and, with owner approval, committed original-file download and verification tooling as `21e37a0`. The manifest covers 25 source groups and 118 artifacts. Eight tests, size/SHA-256 verification of the selected local files, strict OKF conformance, house checks, manifest validation, task routing, documentation links, and whitespace checks passed. No live download was repeated. Incorporated the commit into `bootstrap` while verifying that every original working file retained its contents and mode, including all website work. Removed the temporary worktree and branch at the owner's request. Planning/reference consolidation is next; the knowledge reset stays last, and both require separate commit permission. Updated the plan and handoff without starting knowledge drafts or editing website files. + +## 2026-10-19 + +- Prepared the planning-consolidation commit in the original checkout. The single implementation plan retains the approved outline and pending review question, accounts for superseded plans, and links the preserved owner guidance, review correction, and research references. Verified all 87 inventory entries and source anchors, eight pinned Git source paths, the unchanged historical coverage table, and identical local copies of all 29 tracked previews. An exported copy of the proposed commit passes strict OKF conformance, house checks, and manifest validation with zero errors or warnings; all 169 local documentation links resolve. All 115 existing concepts and the old bundle's build plan remain unchanged in the proposed commit. The archive move, new knowledge index/log, and website changes remain separate. No new branch, worktree, concept draft, or commit was created for this phase; commit permission is pending. + +- At the owner's request, Astra independently reviewed the staged consolidation and found it ready with minor fixes, with no significant lost decision. Removed a conflicting application-inventory prerequisite and qualified website command availability for this commit. Also shortened the repeated handoff checkpoints and removed the obsolete migration-ledger requirement from the staged contributor instructions. Approved organization and first-node decisions, the open review question, and website/reset exclusions remain unchanged; the agent review is not owner sign-off. + +## 2026-10-22 + +- Committed the reviewed planning consolidation as `ea7dfeb` with the owner's explicit permission. The index was cleared and every remaining working file retained its contents and mode. Website and knowledge-reset changes remained separate. +- Coordinated the remaining split with Claude through Herdr: website code, root task integration, and site-only documentation first; archive move and new graph state last. Claude checked the site build independently of `knowledge/` and verified that production rejects unstamped pages. Both archive validators and active-bundle checks pass. Verified effective GitHub rules requiring a PR and one approving review, despite the classic branch-protection endpoint returning 404; the local hook reserves updates to `main` for the owner. No new branch/worktree, commit, push, merge, deployment, or review stamp was created during this coordination. +- Committed the exact reviewed website proposal as `5da01ae` with the owner's explicit permission. Confirmed the index was clear and every remaining working file retained its contents and mode. Prepared the graph/bundle reset separately, including the archive, fresh index/log, and updated graph instructions. Website code and deployment routing are outside the graph proposal; graph commit permission and publication approval remain pending. +- With separate owner approval, committed the knowledge reset as `6e7546a`, pushed `bootstrap`, and opened PR #2. Bundle CI passed. The owner retains review and merge responsibility; no new worktree or knowledge draft was created. +- Addressed Copilot's PR #2 icon-license finding: saved the full upstream Lucide/Feather license, added nonvisual notices to all nine Lucide SVGs and their existing diagram copies, and made PNG exports produce accompanying license files. Verified that all 15 edited icon/diagram files differ only in metadata, all nine icons preserve notices through recoloring, and a rendered SVG retains seven embedded notices. Before/after PNG renders are byte-identical. Repository and website checks pass. The owner authorized committing and publishing the fix to PR #2. diff --git a/README.md b/README.md index 9a010fa..f8f7bdf 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,44 @@ # OIDM knowledge -Canonical, public, high-level documentation for the Open Imaging Data Model (OIDM): the finding models, common data elements, anatomic locations, and exam types that form its semantic foundation; the Observation, Exam Finding List, Imaging Problem List, and Imaging Persona data structures built on them; and the applications that use them. +Source-grounded, high-level documentation for the Open Imaging Data Model (OIDM): the finding models, common data elements, anatomic locations, and exam types that form its semantic foundation; the Observation, Exam Finding List, Imaging Problem List, and Imaging Persona data structures built on them; and the applications that use them. -The content is an [Open Knowledge Format](https://okf.md) 0.2 bundle in [`knowledge/`](knowledge/index.md). Start at the [root index](knowledge/index.md), then the overview and the glossary. Working repositories in the [openimagingdata](https://github.com/openimagingdata) organization keep code documentation and reference this repository for everything else. +The repository-only knowledge graph is an [Open Knowledge Format](https://okf.md) 0.2 bundle in [`knowledge/`](knowledge/index.md). It currently contains its index and change log; concept reconstruction has not started. The old generated bundle is temporarily held in [`archive/knowledge/`](archive/README.md) to check fresh drafts for useful omissions, and will be deleted after recovery is complete. Working repositories in the [openimagingdata](https://github.com/openimagingdata) organization retain their code documentation. + +## Current work + +Follow the [source-to-pages implementation plan](docs/plans/implementation.md) for private source storage, detailed references, and the repository knowledge graph. The outward-facing pages and website are a separate track in `site/`, planned in [site/ROADMAP.md](site/ROADMAP.md). The website builds only `site/content/`, which is independent Markdown content rather than an OKF bundle. See [site instructions](site/README.md) for its checks, review process, and deployment. + +Owner guidance and historical evidence remain in [project direction](docs/references/project-direction.md) and [idea coverage](docs/references/idea-coverage.md). + +Local review renders and working research live in ignored `scratch/review/`, exposed through `.peruseshow`. Accepted assets remain tracked with the content. Private source files live at `~/data/oidm-knowledge-sources`; an ignored `sources` symlink preserves existing local citations. All 307 objects in the `oidm-knowledge-sources` Tigris bucket have been checksum-verified. Use the [source instructions](docs/sources.md) and `task sources:pull` / `task sources:check` for the manifest-selected collection. ## Reading it - Browse on GitHub from [`knowledge/index.md`](knowledge/index.md). -- Rendered site: coming in the first release. +- Source notes: [15 linked diagram summaries](docs/references/linked-diagrams/index.md), with source locators and connections to the five pillars. +- Website: `task site:check` checks its content and navigation; `task site:build` renders `site/content/` with Quartz into `public/`. Use `task site:deploy` for dev, `task site:deploy:staging` for staging, and `task site:deploy:prod` for the public website. See [site instructions](site/README.md) for deployment and production review requirements. - Agents: read `knowledge/index.md` first, follow links only into what the task needs, and weigh `status`, `verified`, and `stale_after` in each document's frontmatter. ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) and the [authoring guide](knowledge/guides/authoring-guide.md). Two checkers must pass: +See [CONTRIBUTING.md](CONTRIBUTING.md) and [Writing the new knowledge graph](docs/knowledge-authoring.md). `task check` runs both bundle checkers and validates the source manifest. The bundle checkers can also run directly: ```bash uv run .agents/skills/validate/scripts/okf_validate.py knowledge --strict uv run tools/check_bundle.py ``` +Root `task verify`, `task review-status`, and `task review-list` apply to the knowledge graph. Use `task site:verify`, `task site:review-status`, and `task site:review-list` for website pages. Verification commands record actual human review. + ## Layout | Path | Purpose | |---|---| -| `knowledge/` | The OKF bundle | -| `tools/` | House-rules checker | +| `knowledge/` | The new repository-only OKF graph, reconstructed from fresh documents | +| `archive/knowledge/` | Temporary old generated bundle for recovery; delete when recovery is complete | +| `Taskfile.yml` | Knowledge checks, source recovery, review tools, visualization, and included `site:...` tasks | +| `site/` | Independent website content, tasks, checks, review tools, Quartz configuration, and Cloudflare deployment | +| `tools/` | Knowledge house-rules checker, verification tool, source recovery, and diagram builders | | `.agents/skills/` | Installed OKF skills (author, validate, visualize, backfill) | | `CHANGELOG.md` | Reader-facing changes | | `DEV_LOG.md` | Engineering narrative | diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..acdbcf3 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,61 @@ +# Task runner for the OIDM knowledgebase. Install: https://taskfile.dev +# +# task list tasks +# task check validate the knowledge bundle and source manifest +# task verify record an actual human review of knowledge documents +# task site:check validate website pages and navigation +# task site:build build the website from site/content/ +# task site:deploy build and deploy the dev website +# task viz render the bundle as a single-file interactive graph (viz.html) +version: "3" + +includes: + site: ./site/Taskfile.yml + +tasks: + default: + silent: true + cmds: + - task --list + + check: + desc: Validate the bundle and tracked source manifest + cmds: + - uv run .agents/skills/validate/scripts/okf_validate.py knowledge --strict + - uv run tools/check_bundle.py + - python3 tools/sources.py manifest + + sources:manifest: + desc: Validate the source manifest without accessing private files + cmds: + - python3 tools/sources.py manifest {{.CLI_ARGS}} + + sources:check: + desc: Verify local source artifacts against the manifest + cmds: + - python3 tools/sources.py check {{.CLI_ARGS}} + + sources:pull: + desc: Download missing source artifacts from Tigris and verify their checksums + cmds: + - python3 tools/sources.py pull {{.CLI_ARGS}} + + verify: + desc: "Record reviewed knowledge documents: task verify -- knowledge/path.md [...]" + cmds: + - uv run tools/verify.py {{.CLI_ARGS}} + + review-status: + desc: Show verified / total knowledge documents per directory + cmds: + - uv run tools/verify.py --status + + review-list: + desc: "List unverified knowledge documents; defaults to knowledge/" + cmds: + - uv run tools/verify.py --list {{.CLI_ARGS | default "knowledge"}} + + viz: + desc: Render the bundle as one self-contained HTML graph at viz.html + cmds: + - uv run .agents/skills/visualize/scripts/okf_visualize.py knowledge -o viz.html --title "OIDM knowledge" diff --git a/archive/README.md b/archive/README.md new file mode 100644 index 0000000..4f4fe91 --- /dev/null +++ b/archive/README.md @@ -0,0 +1,7 @@ +# Temporary knowledge archive + +`knowledge/` here preserves the old generated bundle, including working-tree edits present when it was moved on October 6, 2026. Its content is untrusted recovery material, not the current knowledge graph. + +Write new documents from the owner's outline and verified sources into `../knowledge/`. Consult this archive afterward for relevant omissions and source leads. Do not edit archived prose into replacements. + +The reconstruction and disposal steps are in [the implementation plan](../docs/plans/implementation.md). Delete `archive/knowledge` when useful material has been recovered and active documents no longer depend on it. Recorded owner guidance is preserved separately in [project direction](../docs/references/project-direction.md). diff --git a/archive/knowledge/applications/finding-and-location-coding.md b/archive/knowledge/applications/finding-and-location-coding.md new file mode 100644 index 0000000..18a6db1 --- /dev/null +++ b/archive/knowledge/applications/finding-and-location-coding.md @@ -0,0 +1,140 @@ +--- +type: Concept +title: Finding and location coding +description: How deterministic lookup and language models assign OIFM finding codes and RadLex location codes. +tags: [applications, coding, anatomic-locations, llm, extraction] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +stale_after: 2027-09-21 +sources: + - id: coding-design + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/coding-agent-design.md + title: Coding agent design, imaging-problem-list dev branch + last_modified: 2026-03-16 + - id: coding-prompts + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/coding-agent-prompts.md + title: Coding agent prompt catalog, imaging-problem-list dev branch + - id: coding-archive + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/archive/finding-and-location-code-assignment-plan.md + title: Finding and location code assignment plan, version 3, archived, imaging-problem-list dev branch + last_modified: 2026-02-19 + - id: anat-rules + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/anatomic-location-assignment-rules.md + title: Anatomic location assignment rules, imaging-problem-list dev branch + - id: anat-plan + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/anatomic-location-efl-ipl.md + title: Anatomic locations for Exam Finding Lists and Imaging Problem Lists plan, imaging-problem-list dev branch + last_modified: 2026-06-10 +--- + +# Why coding is its own step + +[Extraction](/glossary/extraction.md) records what the report says. Coding matches findings to existing definitions. The current pipeline persists uncoded extractions and runs coding separately, without rereading the report. Coding can be rerun with different models.[^coding-design] Two earlier designs did it inside extraction and were retired. + +Finding and location codes are independent. A finding receives an [OIFM identifier](/glossary/oifm.md) for its [finding model](/glossary/finding-model.md). A stated location receives a [RadLex identifier](/glossary/radlex-id.md) from the [anatomic locations](/glossary/anatomic-location.md) dataset. Either can succeed while the other fails. + +# The pipeline + +Coding uses the completed extraction's flat merged finding list. Each finding carries its name, [presence](/glossary/presence.md), location fields, verbatim quote, and source section. The design treats these as sufficient context because extraction has resolved chunk-level ambiguity.[^coding-design] + +The pipeline has five phases. Two call language models. + +| Phase | What happens | +|---|---| +| 1. Fast path | Exact or synonym lookup in the finding index by name, and in the anatomic location index by specific anatomy. Findings that resolve skip the rest for that axis | +| 2. Term generation | Two agents, one for findings and one for locations, generate two or three diverse search terms for each finding that missed the fast path | +| 3. Index search | Batch embedding search over all terms at once; results are distributed back per finding, deduplicated, and capped | +| 4. Code selection | Two agents, one for findings and one for locations, choose from the candidate set, running concurrently under a semaphore | +| 5. Assembly | Fast-path and selected results merge into one coding bundle per finding | + +Four agents perform term generation and selection. Term generation uses a different model because traces showed reasoning tokens added no quality benefit when proposing synonyms. Candidate selection requires reasoning. + +Each phase handles its failures. Failed term generation falls back to the finding name. Failed search or selection marks the finding unresolved without blocking other findings. + +The prompts carry the exam information, modality, body part, and study description, because it disambiguates both terms and candidates, and deliberately omit the full report text; testing found identical results with 22 percent fewer input tokens without it.[^coding-prompts] + +## What the fast path is worth + +In chest radiograph prototype tests, exact and synonym lookup resolved 13 of 16 unique finding names and 12 of 15 unique locations without model calls.[^coding-design] Deterministic top-candidate selection was insufficient for locations, which needed contextual reasoning even when finding codes resolved deterministically. + +# The coding record + +A coding bundle holds one finding code and a list of location codes. Findings can span sides or structures, so "lungs" resolves to both lungs.[^coding-design] + +```python +class FindingCodingBundle(StrictBaseModel): + finding_code: FindingCode = Field(default_factory=FindingCode) + location_codes: list[LocationCode] = Field(default_factory=list) +``` + +Both code objects record the result, method, and failure reason. + +| Field | Finding code | Location code | +|---|---|---| +| `status` | `coded` or `unmapped` | `coded` or `unmapped` | +| identifier | `oifm_id`, `oifm_name` | `location_id`, `location_name` | +| `method` | `fast-path`, `llm`, or `unresolved` | same three values | +| `reason` | why it is unresolved | why it is unresolved | +| `reasoning` | the model's explanation of the choice or the rejection | same | +| `candidates` | the alternates that were considered | the alternates that were considered | +| `closest_candidate_id` | the nearest miss when unresolved | not carried | + +Unresolved reasons distinguish content gaps from processing failures. + +| Finding reason | Meaning | +|---|---| +| `too_specific` | The candidates narrow beyond what the report states | +| `too_broad` | The candidates are too general to be useful | +| `wrong_concept` | The candidates are different clinical entities | +| `definition_mismatch` | A candidate's name matches but its definition differs, which flags a gap in the ontology | +| `no_candidates` | The search returned nothing | +| `coding_error` | A model or infrastructure failure | + +| Location reason | Meaning | +|---|---| +| `no_candidate_match` | The location is known but no candidate fits, which flags a gap in the index | +| `location_unknown` | The location cannot be determined from the available context | +| `no_candidates` | The search returned nothing | +| `coding_error` | A model or infrastructure failure | + +`definition_mismatch` and `no_candidate_match` identify gaps in [the content catalog](/semantic-foundation/finding-models/content-catalog.md) and [the anatomic location data model](/semantic-foundation/anatomic-locations/data-model.md). + +Model explanations persist with the extraction but never enter logs or trace attributes because they can quote report content. + +# Where the location comes from + +The assignment rules determine the anatomic structure before code selection, governed by a written precedence ladder, applied per observation and agreed during a correction pass over the sample data.[^anat-rules] + +1. Use the most specific anatomy stated in the report text or section context. Add a side only when stated. +2. Otherwise use the finding's own target organ, at the finding's anatomic granularity and never finer. +3. Otherwise, use the coarse exam region, sided only if the exam is sided. + +An organ always beats the exam region when the finding has a real target organ. For example, "lung bases clear" on an abdominal study codes to lung. Resolve laterality from the whole report section, including its heading. Never infer a side from another exam or clinical priors. Split separable bilateral lesions into left and right, but keep a diffuse bilateral process generic and unsided. Leave structures absent from the ontology unassigned. See [the assignment rules reference](/data-structures/anatomic-location-assignment-rules.md) for examples and the exam-to-region map. + +Generic and specific locations for the same finding code across exams, such as "kidney" and "left kidney", still produce separate [Imaging Problem List](/glossary/imaging-problem-list.md) groups. Deciding whether those are one problem or two is called anatomic-compatibility reconciliation and is an open follow-on.[^anat-plan] + +# The review artifact + +The sample Exam Finding List enrichment script writes a review CSV of every location decision alongside the enriched files. In the 2026-06-10 pass, 260 of 275 findings received a location. The regenerated [Imaging Problem List](/data-structures/imaging-problem-list.md) groups by finding code and location identifier.[^anat-plan] + +# Earlier designs, now archived + +Three generations exist in the repository, and only the third is current. + +| Generation | Shape | Status | +|---|---|---| +| Per-finding deterministic then adjudication | One deterministic attempt per finding, then an adjudicating model call | Retired | +| Version 3, batch per chunk | Fast path, then three model calls per report chunk: generate search terms for findings and locations together, select finding codes, select location codes | Marked completed 2026-02-19, then superseded | +| Current, flat merged finding list | Fast path, then four agents across two phases, operating on the merged finding list rather than per chunk | Current, implementation complete on `dev` | + +The current design split term generation into finding and location agents so each could use a cheaper model.[^coding-archive][^coding-design] + +# Where this sits + +Coding runs as an asynchronous job in [the report extraction platform](/applications/report-extraction-platform.md). It uses the finding model corpus and anatomic location dataset to assign standard identifiers to an [Observation](/glossary/observation.md). See [the architecture overview](/overview/architecture.md). + +[^coding-design]: Coding agent design, imaging-problem-list dev branch +[^coding-prompts]: Coding agent prompt catalog, imaging-problem-list dev branch +[^coding-archive]: Finding and location code assignment plan, version 3, archived +[^anat-rules]: Anatomic location assignment rules, imaging-problem-list dev branch +[^anat-plan]: Anatomic locations for Exam Finding Lists and Imaging Problem Lists plan diff --git a/archive/knowledge/applications/finding-model-forge.md b/archive/knowledge/applications/finding-model-forge.md new file mode 100644 index 0000000..7088753 --- /dev/null +++ b/archive/knowledge/applications/finding-model-forge.md @@ -0,0 +1,142 @@ +--- +type: Project Profile +title: Finding Model Forge +description: The application at fmf.oidm.org for authoring finding models with AI assistance and reviewing drafts. +tags: [applications, finding-models, authoring, llm, mongodb] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +stale_after: 2027-09-21 +sources: + - id: forge-readme + resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/README.md + title: FindingModelForge README, dev branch + - id: forge-workflow + resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/finding-model-creation-workflow.md + title: Finding model creation workflow, FindingModelForge dev branch + last_modified: 2025-08-31 + - id: forge-draft + resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/DRAFT_WORKFLOW.md + title: Finding model draft workflow, FindingModelForge dev branch + - id: forge-db + resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/database.md + title: MongoDB collections and access, FindingModelForge dev branch + - id: forge-public-review + resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/tasks/done/PRD-public-draft-review.md + title: Public draft review feature requirements, FindingModelForge dev branch + - id: forge-comments + resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/tasks/done/comments-feature-prd.md + title: Comments feature requirements, FindingModelForge dev branch + - id: forge-iteration + resource: https://github.com/openimagingdata/FindingModelForge/blob/af357f8decf029d99863ece63f03e1a7e86c18e5/tasks/model_iteration_feature_plan.md + title: Model iteration feature implementation plan, FindingModelForge feature/model-editing branch + last_modified: 2025-11-27 + - id: forge-issue-13 + resource: https://github.com/openimagingdata/FindingModelForge/issues/13 + title: FindingModelForge issue 13, "Implement model editing", opened 2025-10-16 + - id: forge-zold + resource: https://github.com/openimagingdata/FindingModelForge/tree/f55dc858e1e3c3fde64ccd0d53b46ec07374ede6/zOld/plans + title: FindingModelForge zOld/plans, the superseded application's planning notes + - id: deck + resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal + title: Open Imaging Data Model 2026 Status Update, January 2026 +--- + +# Purpose + +**Live:** [fmf.oidm.org](https://fmf.oidm.org) (sign-in with GitHub). + +Finding Model Forge lets radiologists create [finding model](/glossary/finding-model.md) definitions without programming. It assigns identifiers and standard codes automatically and accepts drafts for review. The January 2026 Open Imaging Data Model (OIDM) deck presents it as a tool for developing finding models before formal [common data element](/glossary/cde.md) adoption.[^deck] + +The FastAPI application wraps the `findingmodel` Python library for generation, similarity search, and identifier assignment.[^forge-readme] See [the authoring workflow](/semantic-foundation/finding-models/authoring-workflow.md) for the library. + +# What a user does with it + +Users sign in with GitHub OAuth. Creation has two steps followed by draft editing, replacing an earlier five-step wizard.[^forge-workflow] HTMX intercepts HTTP 303 responses and swaps content within one page without browser navigation. + +| Stage | User action | What the server does | +|---|---|---| +| 1. Basic information | Enter the finding name, press "Generate Description" | Generates a description from the name | +| 2. Description refinement | Review or edit the description, optionally add synonyms, press "Check for Similar" | Runs a semantic similarity search against existing models; shows near-matches, or creates a draft if there are none | +| 3. Draft editing | Edit description, manage synonyms, write the attributes in markdown; press "Update & Preview" | Autosaves as you type, then generates the full finding model JSON including identifiers and standard codes | +| 4. Preview and submission | Review the generated model, press "Submit Draft" | Moves the draft to `submitted` and locks editing | + +The finding name becomes read-only when a draft is created. Creation sessions track the first two steps and expire after an hour of inactivity. Users resume drafts from their profile page. Submitted drafts open in the final display view.[^forge-draft] + +# The draft object and its lifecycle + +A draft stores its owner, finding name, description, synonyms, attributes markdown, generated model JSON, status, and a timestamped action log recording who performed each action.[^forge-draft] + +```text +draft ──submit──▶ submitted ──▶ under-review ──▶ added + └──▶ declined +``` + +Five statuses exist: `draft`, `submitted`, `under-review`, `added`, and `declined`. Submission locks further editing; everything after it is administrative review. On `dev`, public drafts allow contributors to read and comment before submission. A separate comment feature covers `submitted`, `under-review`, `added`, and `declined` drafts. Both are stated as serving collaborative refinement rather than approval gating.[^forge-public-review][^forge-comments] + +# Data it reads and writes + +MongoDB stores drafts, users, and organizations through asynchronous Motor access.[^forge-db] Redis caches user and finding model data. Cache calls become no-ops when Redis is unavailable.[^forge-draft] The `findingmodel` library requires two DuckDB files in the platform data directory, one for finding models and one for [anatomic locations](/glossary/anatomic-location.md), to support similarity search and code lookup without web services.[^forge-readme] + +Drafts store generated JSON inline in [the finding model format](/semantic-foundation/finding-models/finding-model-format.md), with [OIFM identifiers](/glossary/oifm.md) and per-[attribute](/glossary/attribute.md) identifiers. The definitive corpus lives in [the content repository](/semantic-foundation/finding-models/content-catalog.md). + +# Language model use + +The workflow documents three library calls and their time budgets.[^forge-workflow] + +| Step | Task | Budgeted time | +|---|---|---| +| Stage 1 | Generate a description from the finding name | 2 to 5 seconds, with a default description as fallback | +| Stage 2 | Semantic similarity search against existing models | 15 to 30 seconds | +| Stage 3 | Generate the complete finding model JSON, including identifier assignment and standard codes | 30 to 90 seconds | + +# Architecture + +One FastAPI process serves pages and the API. Jinja2 renders templates, Flowbite supplies components, Alpine.js manages client state, and HTMX swaps server-rendered fragments. Project conventions place interactivity in Alpine and HTMX instead of custom JavaScript. Vite builds static assets, which the application resolves through its manifest.[^forge-readme][^forge-draft] + +# Deployment + +Live at [fmf.oidm.org](https://fmf.oidm.org). The container requires MongoDB, Redis, a GitHub OAuth application, and two DuckDB data files.[^forge-readme] + +# Repository and branch of record + +`FindingModelForge` in the `openimagingdata` organization. See [the repository map](/repositories/repository-map.md) for how it relates to everything else. + +| Branch | Tip commit | Date | State | +|---|---|---|---| +| `main` | `f55dc85` | 2025-08-20 | Stale relative to `dev`; the deployed feature set predates the refactor | +| `dev` | `15d9ebc` | 2026-01-02 | Branch of record, 96 commits ahead of `main`, unmerged | +| `feature/model-editing` | `af357f8` | 2025-11-28 | 60 commits ahead of `main`, unmerged | + +Neither long-lived branch has an open pull request as of 2026-09-21. + +# What is in flight + +**The `dev` refactor.** Ninety-six commits consolidate a service-layer extraction, a router cleanup, standardized HTMX and Alpine patterns, and rebuilt test infrastructure. Its complexity assessment records remaining service and router work. Public drafts and comments are complete on `dev` and absent from `main`.[^forge-public-review][^forge-comments] + +**Model editing, issue 13.** The `feature/model-editing` branch lets users request changes in natural language.[^forge-iteration] Model names cannot change, and each user can have one iteration draft per published model. The action log records changes, and manual edits are prohibited. The `model_editor` module in `findingmodel` 0.6.0 preserves [OIFM identifiers](/glossary/oifm.md) and rejects unsafe changes. Sprint 1, natural-language iteration, was complete on 2025-11-27. Sprint 2, markdown-edit iteration, and Sprint 3, enhanced history, have not started. + +# Open issues + +| Issue | Title | Opened | State | +|---|---|---|---| +| 13 | Implement model editing | 2025-10-16 | Open; partly implemented on `feature/model-editing` | +| 9 | Bring the VPS compose/deploy into repository | 2025-05-30 | Open | +| 7 | Make a dev Docker Compose file | 2025-05-27 | Open | + +# History + +The superseded application and its plans remain in `zOld/`.[^forge-zold] The plans chose MongoDB with Beanie after considering embedded vector stores, shared the FastAPI process between pages and API, and kept GitHub OAuth authorization in the session. Three mini-sprint lists from November 2024 through April 2025 start with generation from a finding name, then add identifiers, a user table, and searchable [common data element](/glossary/cde.md) content. Most checkboxes remain unchecked, including CSV batch creation and links to eventual CDE codes. + +# What it realizes + +Forge lets [contributors](/glossary/contributor.md) assign [organization code](/glossary/oidm-organization-code.md) identifiers, write attributes and values, and attach [index codes](/glossary/index-code.md) for external terminologies. See [the architecture overview](/overview/architecture.md) and [the authoring workflow](/semantic-foundation/finding-models/authoring-workflow.md). + +[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 +[^forge-readme]: FindingModelForge README, dev branch +[^forge-workflow]: Finding model creation workflow, FindingModelForge dev branch +[^forge-draft]: Finding model draft workflow, FindingModelForge dev branch +[^forge-db]: MongoDB collections and access, FindingModelForge dev branch +[^forge-public-review]: Public draft review feature requirements, FindingModelForge dev branch +[^forge-comments]: Comments feature requirements, FindingModelForge dev branch +[^forge-iteration]: Model iteration feature implementation plan, FindingModelForge feature/model-editing branch +[^forge-zold]: FindingModelForge zOld/plans, the superseded application's planning notes diff --git a/archive/knowledge/applications/finding-models-site.md b/archive/knowledge/applications/finding-models-site.md new file mode 100644 index 0000000..bf4f1be --- /dev/null +++ b/archive/knowledge/applications/finding-models-site.md @@ -0,0 +1,87 @@ +--- +type: Project Profile +title: Finding model catalog site +description: The static Astro catalog built from finding model definitions in a git submodule. +tags: [applications, finding-models, catalog, astro] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +stale_after: 2027-09-21 +sources: + - id: site-config + resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/astro.config.mjs + title: finding-models-site Astro configuration, main branch + - id: site-loader + resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/src/data/loadFindingModels.ts + title: finding-models-site build-time model loader, main branch + - id: site-index + resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/src/pages/index.astro + title: finding-models-site home page, main branch + - id: site-submodule + resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/.gitmodules + title: finding-models-site submodule declaration, main branch + - id: site-deploy + resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/.github/workflows/deploy.yml + title: finding-models-site GitHub Pages deployment workflow, main branch +--- + +# Purpose + +**Live:** [openimagingdata.github.io/finding-models-site](https://openimagingdata.github.io/finding-models-site/). + +The catalog site displays [finding model](/glossary/finding-model.md) definitions without requiring installation or reading JSON. [Finding Model Forge](/applications/finding-model-forge.md) authors the definitions. + +# What a user does with it + +The home page links to three actions.[^site-index] + +- "Browse Finding Models" opens the catalog, with a detail page for each definition. +- "Launch Finding Model Forge" opens sign-in at `fmf.oidm.org`. +- "Submit Issue" opens an issue against the `findingmodels` content repository. + +The home page search box has no implemented behavior. + +# Data it reads + +The `findingmodels` content repository is a git submodule at `external/findingmodels`.[^site-submodule] At build time, the loader parses every `.json` file in `defs/` and derives page slugs from file names.[^site-loader] The site has no database, API calls, or runtime fetches. + +```typescript +const modelDir = path.resolve('./external/findingmodels/defs'); +``` + +That single line is the whole coupling between the site and the corpus, and it is why the content repository stays the source of truth for finding model identity. See [the content catalog](/semantic-foundation/finding-models/content-catalog.md) and [the finding model format](/semantic-foundation/finding-models/finding-model-format.md). + +# Language model use + +None. The site is a deterministic renderer. + +# Architecture + +The site uses Astro 5 static output, Tailwind CSS 4 through the Astro Vite plugin, and pnpm.[^site-config] Four source files define the layout, home page, model list, and model detail page. The `/finding-models-site/` base path resolves URLs under the GitHub Pages subpath. + +# Deployment + +Live at [openimagingdata.github.io/finding-models-site](https://openimagingdata.github.io/finding-models-site/), verified responding on 2026-09-21. On pushes to `main`, GitHub Actions checks out the repository with submodules, installs with pnpm, builds, and publishes `dist/`.[^site-deploy] New definitions appear only after the submodule pointer is updated and the site rebuilds. + +# Repository and branch of record + +`finding-models-site` in the `openimagingdata` organization; see [the repository map](/repositories/repository-map.md). + +| Property | Value | +|---|---| +| Branch of record | `main`, the only branch | +| Tip commit | `6298fa3`, 2025-06-19 | +| Open issues | None | +| Open pull requests | None | +| Status | Maintenance: it is the current artifact for its purpose, with no commits since June 2025 | + +The repository's README is the unmodified Astro starter boilerplate and describes nothing project-specific. + +# What it realizes + +The site reads portable definition files without a service or credentials. The extraction platform similarly resolves [OIFM identifiers](/glossary/oifm.md) against a published registry file. See [the architecture overview](/overview/architecture.md). + +[^site-config]: finding-models-site Astro configuration, main branch +[^site-loader]: finding-models-site build-time model loader, main branch +[^site-index]: finding-models-site home page, main branch +[^site-submodule]: finding-models-site submodule declaration, main branch +[^site-deploy]: finding-models-site GitHub Pages deployment workflow, main branch diff --git a/archive/knowledge/applications/imaging-problem-list-viewer.md b/archive/knowledge/applications/imaging-problem-list-viewer.md new file mode 100644 index 0000000..c929ecf --- /dev/null +++ b/archive/knowledge/applications/imaging-problem-list-viewer.md @@ -0,0 +1,131 @@ +--- +type: Project Profile +title: Imaging Problem List viewer +description: The deployed Imaging Problem List browser and the undeployed anatomy-first viewer on dev. +tags: [applications, ipl, viewer, anatomy, frontend] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +stale_after: 2027-09-21 +sources: + - id: ipl-main-readme + resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/viewer/README.md + title: imaging-problem-list viewer README, main branch + - id: ipl-main-claude + resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/CLAUDE.md + title: imaging-problem-list domain model notes, main branch + - id: ipl-deploy + resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/DEPLOYMENT.md + title: imaging-problem-list deployment guide, main branch + - id: ipl-status-commit + resource: https://github.com/openimagingdata/imaging-problem-list/commit/06f64a7893b444b761dc069ed86140a081195eac + title: "imaging-problem-list commit 06f64a7, \"Display findings in status-based sections instead of filtering\", 2025-11-18" + - id: viewer-v2-plan + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/viewer-v2-anatomy-dashboard.md + title: viewer_v2 anatomy-aware IPL viewer plan, imaging-problem-list dev branch + - id: anat-plan + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/anatomic-location-efl-ipl.md + title: Anatomic locations for Exam Finding Lists and Imaging Problem Lists plan, imaging-problem-list dev branch + - id: ipl-dev-claude + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/CLAUDE.md + title: imaging-problem-list domain model notes, dev branch +--- + +# Purpose + +**Live:** the deployed viewer is at [imaging-problem-list.pages.dev](https://imaging-problem-list.pages.dev) (main-branch data, without anatomic locations). The anatomy-first `viewer_v2` on `dev` has no public deployment as of 2026-09-21; the plan's intended address, `ipl-anatomy.pages.dev`, does not respond. + +The viewer displays a patient's [Imaging Problem List](/glossary/imaging-problem-list.md) by finding, showing its history and current presence. The January 2026 status deck lists the browser as a demonstration application. + +Two viewers exist. The deployed one is on `main`. A second-generation, anatomy-first viewer called `viewer_v2` sits on `dev` and is not deployed. + +# The deployed viewer + +## What a user does with it + +Navigation has three levels, managed in client-side state.[^ipl-main-readme] + +1. The patient's [Imaging Problem List](/data-structures/imaging-problem-list.md) shows findings across exams. A dropdown or `?patient=` query parameter selects the patient. +2. A finding's exam entry opens the [Exam Finding List](/data-structures/exam-finding-list.md). +3. The exam view opens the source report text. + +Status sections replaced status filtering on 2025-11-18.[^ipl-status-commit] The browser computes four statuses from date-sorted observations without storing them in the data. + +| Status | Meaning | +|---|---| +| Current | [Present](/glossary/presence.md) on the most recent exam that mentions it | +| Always | Present on every observation | +| Resolved | Present in the past, absent now | +| Never | Never described as present | + +The [body region](/glossary/body-region.md) filter covers chest, abdomen, pelvis and genitourinary, musculoskeletal, and head and neck. A static table of 98 finding-to-region mappings replaced keyword matching. A finding's exam-group popover lists all its observations, grouped by report identifier. A second lookup table shortens exam names, such as "MR Brain WO and W contrast IV" to "MR Brain w/wo". The design prioritizes dark mode and stores the preference in browser local storage. + +## Data it reads + +The viewer reads static files organized by patient and exam. + +```text +data/patients.json +data/patients//patient.json +data/patients//ipl.json +data/patients//exams//efl.json +data/patients//exams//report.txt +data/finding_region_mappings.json +data/exam_type_mappings.json +``` + +The deployed bundle contains two patients. One has 10 exams and 98 aggregated findings. The other has 2 exams.[^ipl-main-readme] Repository scripts generate the files from [sample data](/data-structures/sample-data.md). The viewer needs no application server, API, or database. + +## Language model use + +None in the viewer. Everything it renders was produced upstream. + +## Architecture + +One page manages Alpine.js state, with Tailwind and Flowbite loaded from a content delivery network and no build step. JavaScript computes statuses, filters regions, and assembles popovers from JSON. + +## Deployment + +Live at [imaging-problem-list.pages.dev](https://imaging-problem-list.pages.dev), verified responding on 2026-09-21. A Wrangler command deploys `viewer/` to the `imaging-problem-list` Cloudflare Pages project. There is no continuous deployment.[^ipl-deploy] + +## A documentation discrepancy + +The domain notes and viewer README describe an older three-state scheme: present, resolved, and not present or ruled out.[^ipl-main-claude] The shipped code computes the four states above. See [open questions](/roadmap/open-questions.md). + +# viewer_v2, the anatomy-first viewer + +`viewer_v2/` is a React, Vite, TypeScript, and Tailwind static application on `dev`. Its plan is marked "Body-map redesign implemented; ready for review" and is not archived, so it is in flight.[^viewer-v2-plan] + +Each finding identifier in the Imaging Problem List defines one clinical problem row. Anatomy grouping must not merge or split findings. Clinical reconciliation of compatible locations belongs to the follow-on anatomic location work.[^anat-plan] + +The viewer presents the ten-exam patient on an abstract body schematic. Right extremities appear on the viewer's left, left extremities on the right, and unsided findings stay separate. Zones show active finding names as clickable chips. The detail pane opens through region, cluster, finding, and observation, with timelines, definition metadata, and links to exams, Exam Finding Lists, and reports. This version requires a dark radiology-workstation palette. + +Quotes are highlighted by exact match after whitespace normalization and folding of Unicode compatibility forms, dashes, and smart quotes. There is no fuzzy matching. Unmatched quotes produce warnings in generated data. + +The browser reads only generated files under `viewer_v2/public/data/`. A build script combines the example Imaging Problem List, Exam Finding Lists, report files, finding display metadata, and `anatomic-locations` package. Every top-level file has a schema version. Generation fails on duplicate or missing finding identifiers, unresolved report references, or unresolved anatomy without an explicit unlocalized fallback. A check task regenerates the bundle and fails if it differs from committed output. + +The bundle contains 123 findings across 10 exams for one patient. Two findings use the unlocalized fallback. The manifest contains seven warnings for display: four exact-evidence misses, two missing-anatomy fallbacks, and one missing finding definition. + +The plan names a separate Cloudflare Pages project, `ipl-anatomy`, for the built output. No site responds at `ipl-anatomy.pages.dev` as of 2026-09-21. Further deployment automation is outside the plan's scope. + +# Repository and branch of record + +`imaging-problem-list` in the `openimagingdata` organization; see [the repository map](/repositories/repository-map.md). + +| Branch | Tip commit | Date | Role | +|---|---|---|---| +| `main` | `06f64a7` | 2025-11-18 | The deployed viewer and the stable data-structure specification | +| `dev` | `36fa30c` | 2026-07-22 | Branch of record for current state, 258 commits ahead; carries `viewer_v2` and the extraction platform | + +The same repository holds [the report extraction platform](/applications/report-extraction-platform.md). Its one open issue, number 1, asks for a formal model layer for Observation, Exam Finding List, and Imaging Problem List and is tracked in [the data model roadmap](/roadmap/ipl-data-model-system.md). + +# What it realizes + +The viewer displays the [Observation](/glossary/observation.md), [Exam Finding List](/glossary/exam-finding-list.md), and [Imaging Problem List](/glossary/imaging-problem-list.md) [hierarchy](/data-structures/hierarchy.md). `viewer_v2` groups navigation by [anatomic location](/glossary/anatomic-location.md), using codes assigned under [the assignment rules](/data-structures/anatomic-location-assignment-rules.md). On `dev`, Imaging Problem List groups use both the finding code and location identifier.[^ipl-dev-claude] + +[^ipl-main-readme]: imaging-problem-list viewer README, main branch +[^ipl-main-claude]: imaging-problem-list domain model notes, main branch +[^ipl-deploy]: imaging-problem-list deployment guide, main branch +[^ipl-status-commit]: imaging-problem-list commit 06f64a7, 2025-11-18 +[^viewer-v2-plan]: viewer_v2 anatomy-aware IPL viewer plan, imaging-problem-list dev branch +[^anat-plan]: Anatomic locations for Exam Finding Lists and Imaging Problem Lists plan, imaging-problem-list dev branch +[^ipl-dev-claude]: imaging-problem-list domain model notes, dev branch diff --git a/archive/knowledge/applications/index.md b/archive/knowledge/applications/index.md new file mode 100644 index 0000000..3f2949b --- /dev/null +++ b/archive/knowledge/applications/index.md @@ -0,0 +1,11 @@ +# Applications + +Applications built on OIDM, with their data, deployment, and development status. See [the repository map](/repositories/repository-map.md) for source repositories and branches. + +* [Finding Model Forge](./finding-model-forge.md) - The application at fmf.oidm.org for authoring finding models with AI assistance and reviewing drafts. +* [Finding model catalog site](./finding-models-site.md) - The static Astro catalog built from finding model definitions in a git submodule. +* [Imaging Problem List viewer](./imaging-problem-list-viewer.md) - The deployed Imaging Problem List browser and the undeployed anatomy-first viewer on dev. +* [Report extraction platform](./report-extraction-platform.md) - The platform on imaging-problem-list dev for report extraction, coding, storage, review, and evaluation. +* [Finding and location coding](./finding-and-location-coding.md) - How deterministic lookup and language models assign OIFM finding codes and RadLex location codes. +* [IPL MVP extraction and labeling](./ipl-mvp-extraction.md) - The superseded 2025 report-extraction prototype, its embedding-based finding matches, and measured limits. +* [Open Imaging Reporting SDK](./reporting-sdk.md) - The proposed vendor-facing reporting SDK, its 2023 plugin framework precursor, and the fact that no artifact exists. diff --git a/knowledge/applications/ipl-mvp-extraction.md b/archive/knowledge/applications/ipl-mvp-extraction.md similarity index 50% rename from knowledge/applications/ipl-mvp-extraction.md rename to archive/knowledge/applications/ipl-mvp-extraction.md index eaf9443..2adb3e5 100644 --- a/knowledge/applications/ipl-mvp-extraction.md +++ b/archive/knowledge/applications/ipl-mvp-extraction.md @@ -1,10 +1,10 @@ --- type: Project Profile title: IPL MVP extraction and labeling -description: The 2025 four-stage prototype that extracted findings from reports and matched them to finding models by embedding similarity, its measured results, and why it was superseded. +description: The superseded 2025 report-extraction prototype, its embedding-based finding matches, and measured limits. tags: [applications, extraction, llm, embeddings, lineage] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } stale_after: 2027-09-21 sources: - id: mvp-readme @@ -27,9 +27,9 @@ sources: # Purpose -This repository was the first end-to-end attempt at the [Imaging Problem List](/glossary/imaging-problem-list.md) goal: read radiology reports, pull findings out of them, and attach each finding to a standard [finding model](/glossary/finding-model.md) definition, so that a finding could be tracked across a patient's studies. Its own framing is longitudinal monitoring of radiologic findings using common data elements.[^mvp-readme] +This was the first end-to-end prototype to extract radiology findings and match them to [finding model](/glossary/finding-model.md) definitions for longitudinal tracking. Its stated goal was an [Imaging Problem List](/glossary/imaging-problem-list.md) using common data elements.[^mvp-readme] -It is superseded. [The report extraction platform](/applications/report-extraction-platform.md) on the `imaging-problem-list` development branch does everything this does and the things this could not. This profile records what it established and what it measured, because both shaped the design that replaced it. The wider sequence is in [extraction approaches](/history/extraction-approaches.md). +[The report extraction platform](/applications/report-extraction-platform.md) on `imaging-problem-list`'s development branch supersedes it. See [extraction approaches](/history/extraction-approaches.md) for the wider history. # What a user does with it @@ -46,7 +46,7 @@ A single configuration file holds the models, the file paths, the extraction pro # The data and the schema -The schema is deliberately thin. A finding is a name and a boolean [presence](/glossary/presence.md); a mapping adds the matched finding model identifier and name, a confidence score, and a status.[^mvp-schemas] +A finding has a name and boolean [presence](/glossary/presence.md). A mapping adds the matched model identifier and name, a confidence score, and a status.[^mvp-schemas] ```python class Finding(BaseModel): @@ -62,22 +62,22 @@ class MappedFinding(BaseModel): status: str ``` -It produces neither an [Exam Finding List](/glossary/exam-finding-list.md) nor an Imaging Problem List, and no FHIR output at all; the README lists generating FHIR Observation resources as a future enhancement.[^mvp-readme] There is no location, no size, no severity, and no [change from prior](/glossary/change-from-prior.md). +The output lacks location, size, severity, and [change from prior](/glossary/change-from-prior.md). It does not produce an [Exam Finding List](/glossary/exam-finding-list.md), Imaging Problem List, or FHIR resources. The README lists FHIR Observation generation as a future enhancement.[^mvp-readme] -The reference set of finding models is a static local JSON file of 15 curated neurological finding models, not a live read from the content repository. That is the file the repository calls its reference, and swapping it for a real service is named as a future enhancement. +The reference set is a local JSON file of 15 curated neurological finding models. Replacing it with a service is listed as a future enhancement. # Language model use -Two calls, both to hosted services.[^mvp-readme] +Extraction and matching call hosted services.[^mvp-readme] -- **Extraction.** A general-purpose model reads each report under a system prompt and returns findings as name and presence pairs. -- **Matching.** Each extracted finding name is embedded, and the embedding is compared by cosine similarity against the embedded names of the 15 reference models. The highest scoring match above a configurable threshold of 0.70 is accepted; everything below it is flagged for manual review. +- A general-purpose model reads each report under a system prompt and returns finding names and presence values. +- Matching compares embeddings of extracted names with embeddings of the 15 reference model names by cosine similarity. It accepts the highest match above the configurable 0.70 threshold and flags lower scores for manual review. -Matching is therefore name-to-name similarity. No index lookup, no candidate generation, and no model judgment about whether the candidate actually fits. +Matching uses name similarity without index lookup, candidate generation, or model judgment of candidate fit. # Architecture -Four standalone Python scripts over JSON Lines files, with Pydantic for validation and cosine similarity over embedding vectors. No service, no database, no web interface. A mockups directory holds interface images for a review and labeling tool that was never built. +Four standalone Python scripts read and write JSON Lines, using Pydantic validation and cosine similarity over embeddings. There is no service, database, or web interface. A mockups directory holds images for a review and labeling interface that was never built. # Results and limits, as measured @@ -93,7 +93,7 @@ The README reports one informal run.[^mvp-readme] That is a match rate of roughly 6 percent. The three matches quoted as successes score 0.778, 0.806, and 0.761. There is no labeled evaluation set and no metrics harness. -The README states its own limitations plainly: binary presence and absence only with no severity or size, manually curated reference models limited to neurological findings, no handling of negated findings, and a static threshold. The negation limitation matters most, because explicit negatives are exactly what an Exam Finding List is supposed to carry. +The README lists binary presence, missing severity and size, a neurological reference set, unsupported negation, and a static threshold as limitations. Unsupported negation prevents the prototype from extracting the explicit negatives expected in an Exam Finding List. # Repository and branch of record @@ -112,11 +112,11 @@ No open issues and no pull requests, open or closed. Status: lineage. All three were pushed within one week and none was merged. -**A persistent FHIR design note.** One file, without a markdown extension, proposing that each finding be tracked as a persistent FHIR Observation with a stable identifier, so that repeat mentions of the same lesion across reports attach to one entity with its own timeline. It sketches a three-step pipeline, extract and map to codes, create or link a resource, then query the finding's timeline, and a patient to resource to dated-observation shape. It adds no code and names two modules that were never written.[^mvp-fhir] The idea is a genuine precursor to the grouping question the current Imaging Problem List answers structurally; see [FHIR mapping](/data-structures/fhir-mapping.md). +**A persistent FHIR design note.** A file without a markdown extension proposes persistent FHIR Observations with stable identifiers, linking repeated mentions of a lesion into one timeline. It sketches extraction and code mapping, resource creation or linking, and timeline queries, organized by patient, resource, and dated observation. It names two unwritten modules and adds no code.[^mvp-fhir] See [FHIR mapping](/data-structures/fhir-mapping.md) for the related grouping question. -**A typed-agent port.** The extraction step rewritten to use a typed agent with a declared result type and automatic retries, replacing a raw client call with manual JSON fence stripping. The schemas are unchanged. The commit was captured mid-draft, with explanatory prose left inside the Python file.[^mvp-pydantic] +**A typed-agent port.** The extraction step uses a typed agent with a declared result type and automatic retries. It replaces a raw client call and manual JSON fence stripping without changing schemas. The commit is a draft, with explanatory prose left in the Python file.[^mvp-pydantic] -**A longitudinal sample series.** The eight generic sample reports replaced with nine reports for one synthetic multiple sclerosis patient spanning 2019 to 2024, from an initial workup through follow-up studies.[^mvp-ms] It pairs with the FHIR design note's track-a-finding-over-time use case. +**A longitudinal sample series.** Nine reports for one synthetic multiple sclerosis patient replace the eight generic reports. They span initial workup and follow-up from 2019 to 2024, matching the FHIR note's longitudinal use case.[^mvp-ms] # What it established diff --git a/archive/knowledge/applications/report-extraction-platform.md b/archive/knowledge/applications/report-extraction-platform.md new file mode 100644 index 0000000..d9a5b46 --- /dev/null +++ b/archive/knowledge/applications/report-extraction-platform.md @@ -0,0 +1,191 @@ +--- +type: Project Profile +title: Report extraction platform +description: The platform on imaging-problem-list dev for report extraction, coding, storage, review, and evaluation. +tags: [applications, extraction, llm, ipl, evaluation, phi] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +stale_after: 2027-09-21 +sources: + - id: dev-readme + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/README.md + title: imaging-problem-list README, dev branch + - id: dev-claude + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/CLAUDE.md + title: imaging-problem-list architecture notes, dev branch + - id: initial-plan + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/initial-extraction-plan.md + title: Initial extraction plan and schema, imaging-problem-list dev branch + - id: validator + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/prompts/validator_prompt_example.md + title: Chunk-level validator prompt, imaging-problem-list dev branch + - id: eval-usage + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/eval-usage.md + title: Evaluation harness user guide, imaging-problem-list dev branch + - id: evals-redesign + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extractor-evals-redesign.md + title: Extractor evals redesign plan, imaging-problem-list dev branch + last_modified: 2026-07-07 + - id: review-workflow + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/human-review-workflow.md + title: Human review workflow for gold extractions, imaging-problem-list dev branch + - id: reviewer-plan + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extraction-reviewer.md + title: Standalone extraction reviewer plan, imaging-problem-list dev branch + - id: reviewer-workflows + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extraction-reviewer-workflows.md + title: Extraction reviewer integration and workflow plan, imaging-problem-list dev branch + last_modified: 2026-07-07 + - id: reviewer-ux + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extraction-reviewer-ux.md + title: Extraction reviewer UX evaluation plan, imaging-problem-list dev branch + last_modified: 2026-07-20 + - id: ui-review-ux + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extractor-ui-review-ux.md + title: Extractor UI review UX evaluation and redesign plan, imaging-problem-list dev branch + last_modified: 2026-07-19 + - id: local-hardening + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/archive/local-only-mode-hardening.md + title: Local-only mode hardening plan, imaging-problem-list dev branch + last_modified: 2026-04-12 + - id: local-tightening + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/local-only-future-tightening.md + title: Future local-only tightening plan, imaging-problem-list dev branch + - id: mlx + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/local-model-mlx-reassessment.md + title: Local model reassessment plan, imaging-problem-list dev branch + last_modified: 2026-07-01 + - id: thinking + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/pydantic-ai-thinking-capability-simplification.md + title: Reasoning plumbing simplification plan, imaging-problem-list dev branch +--- + +# Purpose + +The platform extracts structured, coded findings from radiology reports for [Exam Finding Lists](/glossary/exam-finding-list.md) and [Imaging Problem Lists](/glossary/imaging-problem-list.md). It is the project's seventh extraction approach. See [extraction approaches](/history/extraction-approaches.md) for its predecessors. + +It lives on the `dev` branch of `imaging-problem-list`, 258 commits ahead of `main` as of tip commit `36fa30c`, 2026-07-22. `main` remains the stable data-structure specification and the deployed [viewer](/applications/imaging-problem-list-viewer.md). Nothing described here has merged. + +# What a user does with it + +The platform has four command-line entry points and a web interface.[^dev-readme] + +| Entry point | What it does | +|---|---| +| `finding-extractor` | Extract findings from one report, choosing model and reasoning level | +| `finding-extractor-batch` | Run a directory of reports, interactively or detached, with resume | +| `finding-extractor-code` | Run the post-extraction coding pipeline over a saved extraction | +| `finding-extractor-eval` | Score extraction quality against a dataset | +| Extractor web interface | Submit a report, browse reports, open an extraction result, and review it | + +The review workflow batch-generates a candidate extraction file for each report. A reviewer checks and corrects each file, copies it to a gold filename, and validates it against the extraction model.[^review-workflow] + +# The data + +Input is plain report text. A helper splits a spreadsheet into one file per report and strips attestations and boilerplate. + +**Extraction output.** The schema, set out in the original extraction plan, is a finding name, a [presence](/glossary/presence.md) value, an optional location, a list of attributes, and the verbatim report text the finding came from.[^initial-plan] + +| Field | Type | Note | +|---|---|---| +| `finding_name` | string | Standard terminology, not raw report phrasing | +| `presence` | `present`, `absent`, `indeterminate`, `possible` | `possible` exists for hedged language such as "raising the possibility of" or "cannot exclude" | +| `location` | body region, specific anatomy, [laterality](/glossary/laterality.md) | Optional | +| `attributes` | key and value pairs | Standard keys: size, acuity, change from prior, severity, count, morphology | +| `report_text` | string | The verbatim quote supporting the finding | + +Non-finding text is captured separately. + +SQLite stores data through SQLModel, with Alembic migrations. SHA-256 hashes deduplicate report text. Each extraction row records its report, timestamp, model, reasoning setting, and JSON payload, including findings and non-finding text. Separate correction rows hold reviewer proposals for new findings, updates by index or JSON path, and follow-up comments.[^dev-readme] + +[Finding and location coding](/applications/finding-and-location-coding.md) populates coding bundles in the same extraction row and updates coded and unresolved counts. + +# Language model use + +Extraction agents support OpenAI, Anthropic, Google, OpenRouter, Ollama for local models, and vLLM for on-premises deployments.[^dev-readme] A five-level reasoning setting maps to each provider's controls. Some local model families require JSON-schema output instead of tool calling.[^dev-claude] + +Three architectural decisions shape the pipeline.[^dev-claude] + +- **Chunking.** Long reports are semantically chunked, extracted with bounded concurrency, merged, and deduplicated. A reviewer sub-agent can trigger re-extraction of specific chunks. +- **Verbatim-quote validation.** Exact report quotes are required and validated during generation and afterward. +- **Coding as a separate job.** Coding runs separately after extraction and can be rerun with different models. + +## The validator contract + +The validator compares each chunk's extraction with its text. It returns the chunk identifier, a re-extraction decision, problems, and rationale. Six problem types justify re-extraction.[^validator] + +| Problem type | What it catches | +|---|---| +| `hallucination` | Clinically meaningful content unsupported by the chunk text | +| `missed_finding` | Report text describing a finding that no structure represents | +| `wrong_presence` | Present when it is not, or absent when it is possible | +| `over_specific_finding_name` | A finding name more specific than the text supports | +| `incorrect_blanket_negative` | A blanket negative mapped to the wrong or over-specific absent findings | +| `incorrect_location` | Wrong, too specific, or too general location | + +Formatting and style alone do not justify re-extraction. Only the chunk is evidence. Surrounding chunks are advisory. The prompt maps "clear lungs" to an absent "pulmonary parenchymal abnormality", addressing the blanket-negative problem first recorded in `CDEStaging`. See [extraction approaches](/history/extraction-approaches.md). + +# Architecture + +FastAPI serves the API. Long work is asynchronous: an extraction or coding request returns 202 with a job identifier, TaskIQ workers pull the job through a Redis broker, and the client polls job status. Typed settings use the `IPL_` environment namespace. Logfire spans and structured logs must exclude raw report text and verbatim quotes.[^dev-claude] + +Two browser interfaces serve different people. + +The connected extractor interface, built like the viewer, uses Alpine.js, Flowbite, and Tailwind from a content delivery network, without a build step. It submits reports, lists them, and shows extraction results. + +The standalone reviewer is one HTML file that a non-developer can open locally without installation or network access. Reviewers mark findings approved, flagged, unsure, or missing, add notes, and export a zip of per-file review JSON.[^reviewer-plan] Its vanilla JavaScript and absence of any content delivery network are a deliberate exception to the project's frontend conventions, justified by the requirement that it work from a local file with nothing installed.[^reviewer-workflows] Its plan is complete. It is the primary tool for extraction quality assurance and gold-case adjudication. + +# Evaluation + +The evaluation harness runs extraction over a named report dataset and scores the output against ground truth. Optional thresholds on finding F1, presence accuracy, and verbatim checks cause a nonzero exit when unmet.[^eval-usage] + +On 2026-07-07, the evaluation redesign was deliberately descoped rather than discarded, after three and a half months without implementation. The plan cited thresholds, continuous-integration gates, and a thirty-case benchmark as barriers.[^evals-redesign] A 2026-05-14 selection round had made a local model the default based on average latency and thirty percent more findings. Those measures could not distinguish improved recall from fabricated findings. + +Version one retains quote-first matching, attribute-value scoring, frozen run configurations with per-case artifacts, a ten-case gold set, and a non-blocking scorecard task. The redesigned evaluation depends on the gold set adjudicated with the standalone reviewer.[^reviewer-workflows] + +# Local models and protected health information + +Local-only mode supports the planned protected health information use case. A hardening pass completed 2026-04-12 moved enforcement out of the command-line boundary into a shared preflight helper that every entry point calls, closing gaps where the batch command, the API, and the worker could still reach a cloud provider with local-only set. It also detects cloud-suffixed model names in the local runtime.[^local-hardening] The evaluation command is excluded from this guarantee, on the stated ground that evaluation datasets are curated fixtures rather than patient data. + +Deferred hardening items have explicit reopen triggers. Detecting aliases whose definitions point to cloud sources is deferred because runtime inspection is unstable and model-definition parsing is not authoritative.[^local-tightening] + +The reassessment plan opened 2026-07-01 calls for new local model comparisons since the May 2026 round. It identifies one candidate family as unverified for radiology extraction because it is tuned for agentic coding.[^mlx] + +# Plans in flight + +Eight plans are active on `dev`, with statuses and dates recorded below. + +| Plan | Date | Status | +|---|---|---| +| Extraction reviewer integration and workflows | created 2026-07-07 | Phase A in progress; the reviewer becomes the primary human-review instrument and feeds gold adjudication | +| Extractor evals redesign | updated 2026-07-07 | Active, descoped to version one | +| Extractor interface review UX | created 2026-07-19 | Proposed; its core finding is that the review page does not show the source report text at all | +| Extraction reviewer UX | created 2026-07-20 | Active; Phase 0 complete 2026-07-20, Phases 1 through 3 remain | +| Local model reassessment | created 2026-07-01 | Planned, not started | +| Future local-only tightening | not dated | Not started, active backlog with per-item reopen triggers | +| Reasoning plumbing simplification | not dated | Fully specified, not started; blocked on a library version bump that would retire roughly 728 lines of per-provider translation code[^thinking] | +| Anatomy-first viewer | not dated | Implemented, ready for review; see [the viewer profile](/applications/imaging-problem-list-viewer.md) | + +The sample Exam Finding List location-coding plan was completed on 2026-06-10. Reconciling generic and specific locations across exams remains open because they produce separate Imaging Problem List groups. The table excludes two plans completed in July 2026. + +# Open issues + +Issue 1, "Create System of Data Models", requests formal [Observation](/glossary/observation.md), Exam Finding List, and Imaging Problem List models with schema export. It is unclaimed. See [the data model roadmap](/roadmap/ipl-data-model-system.md). The single open pull request is empty after its design document moved to a separate repository. + +# What it realizes + +[Extraction](/glossary/extraction.md) and [coding](/glossary/coding.md) produce [Observations](/glossary/observation.md) for an [Exam Finding List](/data-structures/exam-finding-list.md). [Anatomic locations](/glossary/anatomic-location.md) follow [the assignment rules](/data-structures/anatomic-location-assignment-rules.md), and [OIFM identifiers](/glossary/oifm.md) resolve against [the published registry](/semantic-foundation/finding-models/content-catalog.md). The [provenance](/glossary/provenance.md) requirement that each Observation carry a marker of how it was produced is met here by storing the model, the reasoning setting, and the verbatim quote with every extraction. See [the architecture overview](/overview/architecture.md). + +[^dev-readme]: imaging-problem-list README, dev branch +[^dev-claude]: imaging-problem-list architecture notes, dev branch +[^initial-plan]: Initial extraction plan and schema, imaging-problem-list dev branch +[^validator]: Chunk-level validator prompt, imaging-problem-list dev branch +[^eval-usage]: Evaluation harness user guide, imaging-problem-list dev branch +[^evals-redesign]: Extractor evals redesign plan, imaging-problem-list dev branch +[^review-workflow]: Human review workflow for gold extractions, imaging-problem-list dev branch +[^reviewer-plan]: Standalone extraction reviewer plan, imaging-problem-list dev branch +[^reviewer-workflows]: Extraction reviewer integration and workflow plan, imaging-problem-list dev branch +[^local-hardening]: Local-only mode hardening plan, imaging-problem-list dev branch +[^local-tightening]: Future local-only tightening plan, imaging-problem-list dev branch +[^mlx]: Local model reassessment plan, imaging-problem-list dev branch +[^thinking]: Reasoning plumbing simplification plan, imaging-problem-list dev branch diff --git a/archive/knowledge/applications/reporting-sdk.md b/archive/knowledge/applications/reporting-sdk.md new file mode 100644 index 0000000..f6e7f59 --- /dev/null +++ b/archive/knowledge/applications/reporting-sdk.md @@ -0,0 +1,76 @@ +--- +type: Concept +title: Open Imaging Reporting SDK +description: The proposed vendor-facing reporting SDK, its 2023 plugin framework precursor, and the fact that no artifact exists. +tags: [applications, reporting, roadmap, vendors] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +stale_after: 2027-09-21 +sources: + - id: deck + resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal + title: Open Imaging Data Model 2026 Status Update, January 2026, applications pillar and call to action + - id: site-framework + resource: https://www.openimagingdata.org/oidm-based-next-gen-reporting-assistance/ + title: "Site post: OIDM-Based Next-gen Reporting Assistance Framework, 2023-07-16" + - id: site-about + resource: https://www.openimagingdata.org/about/ + title: openimagingdata.org About page, last modified 2023-06-21 + - id: cde-demo + resource: https://github.com/openimagingdata/CDETemplateDemo/blob/e09f2553de58b2c88ea26e9673750a62803d4dbc/mapper-logic/src/mappers/obsToMustache.ts + title: CDETemplateDemo Observation to template-data mapper, the rendering precedent +--- + +# Status first + +The [Open Imaging Reporting SDK](/glossary/open-imaging-reporting-sdk.md) is concept only, as confirmed by the project lead. No repository, package, specification, issue, or branch for it was found in the repositories read for this knowledgebase. Its goal is recorded in [the 2026 roadmap](/roadmap/roadmap-2026.md). + +# What the deck says + +The January 2026 status update names "an Open Imaging Reporting SDK enabling vendor-driven innovation" under its applications pillar. It also lists demonstration applications, including an Imaging Problem List browser and early draft-generation tools.[^deck] + +The deck calls for an ACR-OIDM structured imaging results working group, structure before FHIR and other standards, and vendor-driven work. Reporting SDKs from a reporting vendor are its example. It does not commit the project to shipping an SDK. + +# The 2023 precursor + +A 2023-07-16 site post proposes a reporting assistance framework as a plugin container inside the reporting tool.[^site-framework] + +The proposal describes this behavior: + +- Developers write plugins in a programming language such as JavaScript. +- The container supplies an OIDM-defined structure containing current findings, prior reports, and the exams being reported. +- Plugins inspect that context and issue four kinds of command: insert generated text, request information from the radiologist, alert the radiologist to a problem, or send data to an external system. +- The container reruns every plugin whenever context changes, including after new machine-generated data or a radiologist's edit. + +The post describes the result as a suite of assistant scripts continually reviewing the data around a radiologist and issuing commands to assist. That is a plugin architecture rather than a library, which is the main way it differs from what the deck's shorthand suggests. See [the site articles](/history/site-articles.md). + +# The About page promise + +The About page, last modified 2023-06-21, commits to programming interfaces in TypeScript and JavaScript, Python, and C#.[^site-about] The first two were begun and are now [lineage repositories](/history/lineage-repositories.md). No C# library was written. + +`findingmodel`, its anatomic locations package, and `med-ontology-lookup` resolve definitions and codes. They provide no reporting toolkit. See [the terminology lookup profile](/semantic-foundation/terminologies/med-ontology-lookup.md). + +# The one working precedent + +`CDETemplateDemo`, written in 2023 and unchanged since, is the project's only working precedent for reporting SDK functionality. It flattens a [CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md) into a dictionary keyed by component display name, derives boolean flags including presence and absence, and renders a text template. One template produces a full sentence for a present finding and a short clause for an absent one.[^cde-demo] It was demonstrated as a web service at SIIM in June 2023. + +The demo generates prose without a language model, demonstrating the rendering needed by plugins that insert text. See [CDE template rendering](/history/cde-template-rendering.md). + +# What would have to exist + +Stated only as what the sources describe, not as a design. The proposed context structure includes current report text, the order, and prior reports. The [Exam Finding List](/data-structures/exam-finding-list.md) covers one exam's findings, and the [Imaging Problem List](/data-structures/imaging-problem-list.md) covers patient history, but neither carries that full context. The [Imaging Persona](/glossary/imaging-persona.md), the deck's name for surrounding clinical context, is also concept only. Rendering has a working precedent but no maintained implementation. The plugin container has no implementation. + +# Where this is recorded + +| Claim | Source | Date | +|---|---|---| +| Open Imaging Reporting SDK as the applications pillar | Status deck | January 2026 | +| Vendor-driven reporting SDKs as the call to action | Status deck | January 2026 | +| Plugin container, standard context structure, four commands, continuous re-run | Site post | 2023-07-16 | +| Programming interfaces in TypeScript and JavaScript, Python, and C# | About page | 2023-06-21 | +| Structured Observation rendered to prose through a template | `CDETemplateDemo` | 2023-06 | + +[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 +[^site-framework]: "OIDM-Based Next-gen Reporting Assistance Framework", 2023-07-16 +[^site-about]: openimagingdata.org About page, last modified 2023-06-21 +[^cde-demo]: CDETemplateDemo Observation to template-data mapper diff --git a/knowledge/data-structures/anatomic-location-assignment-rules.md b/archive/knowledge/data-structures/anatomic-location-assignment-rules.md similarity index 92% rename from knowledge/data-structures/anatomic-location-assignment-rules.md rename to archive/knowledge/data-structures/anatomic-location-assignment-rules.md index ac700e8..ad71073 100644 --- a/knowledge/data-structures/anatomic-location-assignment-rules.md +++ b/archive/knowledge/data-structures/anatomic-location-assignment-rules.md @@ -1,10 +1,10 @@ --- type: Reference title: Anatomic location assignment rules -description: The precedence, laterality, bilateral, and specificity rules that decide which anatomic location is attached to a finding in an Exam Finding List, migrated from the imaging-problem-list development branch. +description: Migrated rules for assigning anatomy, laterality, and specificity to Exam Finding List observations. tags: [data-structures, anatomic-location, laterality, coding, migrated] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: anat-rules resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/anatomic-location-assignment-rules.md @@ -19,7 +19,7 @@ sources: # About this document -This is a near-verbatim migration of `docs/anatomic-location-assignment-rules.md` from the `imaging-problem-list` repository's development branch.[^anat-rules] The rules were agreed for a correction pass over the `sample_data/example2` findings and are stated there as "the intended spec for tuning the automated location-coding step later." Wording and structure follow the original; only terminology and links have been adjusted for this knowledgebase. +Migrated near-verbatim from `docs/anatomic-location-assignment-rules.md` on `imaging-problem-list`'s development branch.[^anat-rules] Wording and structure follow the original; only terminology and links have been adjusted for this knowledgebase. The rules were agreed for a correction pass over the `sample_data/example2` findings and are "the intended spec for tuning the automated location-coding step later." The subject is how an `anatomicLocation` of `{locationId, locationDisplay}`, a [RadLex identifier](/glossary/radlex-id.md) from the [`anatomic-locations`](https://pypi.org/project/anatomic-locations/) index, is assigned to each [observation](/data-structures/observation.md) in an [Exam Finding List](/data-structures/exam-finding-list.md).[^anat-pkg] diff --git a/archive/knowledge/data-structures/exam-finding-list.md b/archive/knowledge/data-structures/exam-finding-list.md new file mode 100644 index 0000000..347a4c7 --- /dev/null +++ b/archive/knowledge/data-structures/exam-finding-list.md @@ -0,0 +1,104 @@ +--- +type: Data Structure +title: Exam Finding List +description: Findings from one exam, their fields and sources, and planned FHIR and IHE encodings. +tags: [data-structures, exam-finding-list, loinc, fhir, provenance] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +sources: + - id: ipl-main + resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/README.md + title: imaging-problem-list README, main branch, the stable specification + - id: ipl-main-claude + resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/CLAUDE.md + title: imaging-problem-list domain model notes, main branch + - id: ipl-claude-dev + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/CLAUDE.md + title: imaging-problem-list domain model notes, dev branch + - id: efl-sample + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/sample_data/example2/xr_shoulder_20210522_efl.json + title: An Exam Finding List with anatomic locations, imaging-problem-list dev branch + - id: efl-diagram + resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/exam-finding-list.png + title: exam-finding-list.png, the diagram in the imaging-problem-list README + - id: excel-script + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/scripts/generate_efl_from_excel.py + title: generate_efl_from_excel.py, the Exam Finding List generator + - id: deck + resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal + title: "Open Imaging Data Model 2026 Status Update, January 2026" +--- + +# Definition + +"In the context of an imaging exam, a list of the findings declared as present/absent on that exam."[^ipl-main] An [Exam Finding List](/glossary/exam-finding-list.md) combines an exam's identity with its [observations](/data-structures/observation.md). It is the second level of [the hierarchy](/data-structures/hierarchy.md). + +The imaging-problem-list README states two requirements. The list "must also have basic information (keyed by a curated list of [LOINC](/glossary/loinc.md) codes) about what exam this is." And "the same finding type may be declared as present multiple times; each time is a separate entry in the EFL."[^ipl-main] + +# Fields + +The JSON fields follow the stable specification on `main`, with the development branch's addition marked.[^ipl-main-claude][^efl-sample] + +| Field | Type | Required | Notes | +|---|---|---|---| +| `$schema` | URL string | present in samples | Points at `github.com/openimagingdata/imaging-problem-list/schema/exam-problem-list-schema.json`. No such file exists; see below. | +| `diagnosticReportId` | UUID string | yes | identity of the report this list came from. The [Imaging Problem List](/data-structures/imaging-problem-list.md) points back at it. | +| `patientInfo.patientIdentifier` | string | yes | medical record number | +| `patientInfo.patientDOB` | date string | yes | birth date | +| `examInfo.studyIdentifier` | string | yes | the exam's own identifier | +| `examInfo.studyDateTime` | ISO 8601 timestamp | yes | when the exam happened; this is what orders observations over time | +| `examInfo.studyLoincCode` | LOINC code | yes | the [exam type](/glossary/exam-type.md), for example `26158-6` for a left shoulder radiograph | +| `examInfo.studyDescription` | string | yes | display name of the exam | +| `findings[]` | array | yes | one entry per observation instance | +| `findings[].observationId` | string | yes | unique within this list | +| `findings[].findingCode` | `OIFM_[A-Z]{3,4}_[0-9]{6}` | yes | the [OIFM](/glossary/oifm.md) identifier | +| `findings[].findingDescription` | string | yes | the finding model's display name | +| `findings[].attributes[]` | array | yes | each with `attributeCode`, `attributeDescription`, `attributeValueCode`, `attributeValueDescription` | +| `findings[].anatomicLocation` | `{locationId, locationDisplay}` | optional, **development branch only** | a [RadLex identifier](/glossary/radlex-id.md) from the `anatomic-locations` package, "omitted when the finding is not anatomically localizable"[^ipl-claude-dev] | +| `findings[].reportText` | string | optional | verbatim quote from the report | + +`anatomicLocation` is the only field the development branch adds. Data produced against the stable specification does not carry it, and the stable branch's own sample files do not have it. + +See [the Exam Finding List example](/references/exam-finding-list-example.md) for a sample record and field walkthrough. + +## The exam header + +The LOINC code identifies the exam type, allowing queries such as "was this looked for on a chest study?" The ten sample exams use seven LOINC codes covering radiography, computed tomography, ultrasound, and magnetic resonance. The imaging-problem-list README requires a curated list of codes, the purpose of the [exam types](/semantic-foundation/exam-types/) work. No such list has been published. + +## Repeatable findings + +One entry per instance is a deliberate rule, not an artifact. "The same finding type may appear multiple times in one exam (e.g., multiple kidney stones). Each instance gets a separate entry with its own `observationId`."[^ipl-main-claude] The number of entries gives the count. The [Imaging Problem List](/data-structures/imaging-problem-list.md) groups them by source report for display as one timeline row per exam. + +# Where the contents come from + +An Exam Finding List "can be generated by LLM from an existing report or generated live during exam time; each Observation should include some kind of [provenance](/glossary/provenance.md) marker."[^ipl-main] No provenance field exists in the current format. The marker is a stated requirement without an implementation. + +The deck lists dictation, artificial intelligence tools, and interpretation-time interfaces as sources of findings.[^deck] Provenance would distinguish AI-produced and radiologist-dictated observations in the same list. The [FHIR lineage](/data-structures/fhir-mapping.md) used `status`, with `preliminary` for the machine and `final` for the radiologist. + +The sample lists come from a spreadsheet. `generate_efl_from_excel.py` reads these columns: Exam Date, Exam Type, Exam Code, Finding, OIDM Finding Model Name, OIDM FMID, Presence OIFMA_ID, Present/Absent, and Text. It groups rows by exam and writes one file per exam with a fresh UUID as the `diagnosticReportId`.[^excel-script] The [report extraction platform](/applications/report-extraction-platform.md) extracts and codes findings from report text. + +# The diagram + +The README's hand-drawn `exam-finding-list.png` shows a chest computed tomography report beside a findings table.[^efl-diagram] The report's FINDINGS section highlights positive statements in green. These include biapical scarring, subsegmental atelectasis, nine numbered pulmonary nodules, a calcified granuloma, ectatic pulmonary arteries, coronary artery calcifications, a pacing device, colonic diverticular disease, thoracic dextroscoliosis, a T10 compression fracture, and degenerative changes. Pink marks the explicit negatives: "No pleural effusion", "No enlarged supraclavicular, axillary, mediastinal or hilar lymph nodes", and "No pericardial effusion". + +The table is headed "Exam Finding List" with Finding, CDE Set ID, and Count columns. Pulmonary nodule has a count of 8, subsegmental atelectasis 2, and the four negatives 0. Zero records an observation of absence. Starred rows mark findings missing from the ontology at the time. The figure uses RadElement `RDES####` [CDE set](/glossary/cde-set.md) codes, predating the use of OIFM identifiers. + +# FHIR and IHE + +The documented FHIR encoding is "**DiagnosticReport** containing a list of **Observation** objects, with a finding code on each Observation and a list of components with attribute codes and values (especially present/absent and change from prior)."[^ipl-main] One [FHIR DiagnosticReport](/glossary/fhir-diagnostic-report.md), one Observation per finding, attributes as components. + +That mapping is documented and implemented nowhere. The extraction platform's source code contains no FHIR resource classes or references to `DiagnosticReport` or `fhir`. Its only FHIR documents are two input samples, and those do not use the documented component pattern, because that pattern describes the output. See [FHIR mapping](/data-structures/fhir-mapping.md). + +The deck states that an Exam Finding List "connects to IHE Imaging Diagnostic Report (IDR) FHIR representation."[^deck] No branch of `imaging-problem-list` mentions the [IDR profile](/glossary/imaging-diagnostic-report.md). See [IHE IDR alignment](/data-structures/ihe-idr-alignment.md) for the differences to resolve in pursuing this goal. + +# Schema status + +There is no published JSON Schema. Every sample's `$schema` URL points to `schema/exam-problem-list-schema.json`, which the repository notes say "doesn't exist yet."[^ipl-main-claude] Prose, samples, and the two scripts that write and read the structure define it. [Issue 1](https://github.com/openimagingdata/imaging-problem-list/issues/1) requests formal models with JSON Schema export. See [the IPL data model system roadmap](/roadmap/ipl-data-model-system.md). + +[^ipl-main]: imaging-problem-list README, main branch +[^ipl-main-claude]: imaging-problem-list domain model notes, main branch +[^ipl-claude-dev]: imaging-problem-list domain model notes, dev branch +[^efl-sample]: An Exam Finding List with anatomic locations, dev branch +[^efl-diagram]: exam-finding-list.png, the diagram in the imaging-problem-list README +[^excel-script]: generate_efl_from_excel.py, the Exam Finding List generator +[^deck]: "Open Imaging Data Model 2026 Status Update", January 2026 diff --git a/knowledge/data-structures/fhir-mapping.md b/archive/knowledge/data-structures/fhir-mapping.md similarity index 72% rename from knowledge/data-structures/fhir-mapping.md rename to archive/knowledge/data-structures/fhir-mapping.md index 686d0f6..ce2ed37 100644 --- a/knowledge/data-structures/fhir-mapping.md +++ b/archive/knowledge/data-structures/fhir-mapping.md @@ -4,7 +4,7 @@ title: FHIR mapping description: Everything OIDM has documented about representing its data structures in FHIR, from the CDE-labeled Observation pattern of the lineage repositories to the current Exam Finding List and Imaging Problem List mappings, and the plain fact that no current code emits FHIR. tags: [data-structures, fhir, observation, cde, mapping] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: ipl-main resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/README.md @@ -40,15 +40,15 @@ sources: # The stance -OIDM's position on FHIR is stated in the deck's call to action as two words: **structure first**, with FHIR and other standards following.[^deck] The project builds "functional data structures that can then be expressed in FHIR and other standards" rather than starting from the standard and working back. That ordering explains the state of everything below: the mappings are written down carefully, they have precedent in working examples, and no current code produces them. +The deck calls for **structure first**, followed by FHIR and other standards.[^deck] OIDM builds "functional data structures that can then be expressed in FHIR and other standards". The mappings are written down carefully, they have precedent in working examples, and no current code produces them. # The founding pattern: the CDE-labeled FHIR Observation -The project began from FHIR rather than beside it. Its About page defines the unit of radiology data as a "[CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md) object", and the 2023 post that set the direction works the idea through with a pulmonary nodule.[^site-about][^site-findings] +OIDM began with FHIR. Its About page defines the unit of radiology data as a "[CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md) object". A 2023 post illustrates it with a pulmonary nodule.[^site-about][^site-findings] ## The worked example -`FHIRSamples` holds four hand-built resources for a lung cancer screening scenario. They remain the clearest statement of the pattern. +`FHIRSamples` holds four hand-built resources for a lung cancer screening scenario. **The report.** A [FHIR DiagnosticReport](/glossary/fhir-diagnostic-report.md) with `category` coded in SNOMED CT and HL7 v2 table 0074, `code` coded as [LOINC](/glossary/loinc.md) `87279-6` for a screening chest computed tomography, an inline `ImagingStudy`, `result` referencing three Observations, and a `conclusion` with a SNOMED `conclusionCode`.[^fhir-report] @@ -67,15 +67,15 @@ The project began from FHIR rather than beside it. Its About page defines the un } ``` -Three patterns come out of it. +The example demonstrates three patterns. **Set code, element code, value code.** The chain from `RDES195` to `RDE1717` to `RDE1717.1`, all through one coding system, is what "CDE-labeled" means concretely. The current [Exam Finding List](/data-structures/exam-finding-list.md) uses the identical chain with a different vocabulary: `OIFM` identifier, `OIFMA` identifier, dot-suffixed value code. -**Status carries provenance.** The example has the same finding twice, once as an AI observation with `status` `preliminary` and once as the radiologist's with `status` `final`. The two resources are otherwise identical. Distinguishing a machine-produced finding from a confirmed one is a field value, not a separate resource type.[^fhir-ai] That is the closest precedent for the "provenance marker" the Exam Finding List specification asks for, and it is also exactly the ACR priority the deck names, correlating artificial intelligence observations against radiologist observations.[^deck] +**Status carries provenance.** The same finding appears twice, as an AI observation with `status` `preliminary` and a radiologist's observation with `status` `final`. The resources are otherwise identical.[^fhir-ai] Distinguishing a machine-produced finding from a confirmed one is a field value, not a separate resource type. That is the closest precedent for the "provenance marker" the Exam Finding List specification asks for. It also supports the ACR priority named in the deck, correlating AI and radiologist observations.[^deck] **Assessments are second-order observations.** A fourth resource carries the Lung-RADS category as a component of `RDES267`, and its `derivedFrom` points at both the imaging study **and** the radiologist's finding Observation.[^fhir-lungrads] An assessment built on top of a finding is an Observation referring to an Observation. That is the precedent for any future modelling of the reporting-system categories the roadmap names. -Two absences in the example are worth recording. No `bodySite` appears on the finding Observations, because anatomy was implicit in the CDE definition rather than explicit on the resource. No Patient resource is included; `subject.reference` is a bare reference. +The finding Observations omit `bodySite` because anatomy was implicit in the CDE definition. No Patient resource is included. `subject.reference` is a bare reference. ## The Python model @@ -96,9 +96,9 @@ Field by field, the Exam Finding List mapping is close to mechanical. `diagnosti # What is actually implemented -Nothing. A search for `DiagnosticReport` or `fhir` across the extraction platform's entire source tree on the development branch returns no matches. No FHIR resource classes exist anywhere in the current Python code, and no OIDM tool emits a FHIR resource. +Nothing. A search for `DiagnosticReport` or `fhir` across the extraction platform's entire source tree on the development branch returns no matches. The development branch's extraction source has no FHIR resource classes and no tool that emits FHIR resources. -The only real FHIR documents in the current repositories are two **input** samples, `powerscribe-fhir.json` and `chest-ct-fhir.json`, which are pre-transformation DiagnosticReports carrying contained `Patient`, `ImagingStudy`, and `Observation` resources.[^powerscribe] They are worth reading because they differ from the prescribed output in two instructive ways. Their finding Observations code `bodySite` with [RadLex](/glossary/radlex.md), which the prescribed mapping does not mention. And their components carry DICOM codes for series, instance, and SOP identifiers rather than presence and change-from-prior attribute codes. That is not a contradiction: the component pattern the specification describes applies to the Exam Finding List output, not to the reporting system's input. +The current repositories contain two FHIR input samples, `powerscribe-fhir.json` and `chest-ct-fhir.json`. These pre-transformation DiagnosticReports contain `Patient`, `ImagingStudy`, and `Observation` resources.[^powerscribe] Their finding Observations code `bodySite` with [RadLex](/glossary/radlex.md), which the prescribed mapping does not mention. Their components carry DICOM codes for series, instance, and SOP identifiers. That is not a contradiction: the documented component pattern applies to the Exam Finding List output, not to the reporting system's input. | Piece | Status | |---|---| @@ -110,7 +110,7 @@ The only real FHIR documents in the current repositories are two **input** sampl # The open question underneath -The component pattern that every OIDM encoding uses is the one IHE's Imaging Diagnostic Report profile declines to use. IDR states that `Observation.component` "is not used", because FHIR limits components to values "not useful on their own" and using it "has the potential to significantly complicate queries", preferring a root Observation whose `hasMember` references the associated observations. Since the deck names IDR as the Exam Finding List's target representation, that disagreement sits directly under the mapping. It is set out in [IHE IDR alignment](/data-structures/ihe-idr-alignment.md), along with the other points where the two models differ. +The component pattern that every OIDM encoding uses is the one IHE's Imaging Diagnostic Report profile declines to use. IHE's Imaging Diagnostic Report profile states that `Observation.component` "is not used". FHIR limits components to values "not useful on their own", and using them "has the potential to significantly complicate queries". IDR instead uses a root Observation whose `hasMember` references associated observations. The deck names IDR as the Exam Finding List's target, so OIDM's component mappings need reconciliation. See [IHE IDR alignment](/data-structures/ihe-idr-alignment.md). [^ipl-main]: imaging-problem-list README, main branch [^ipl-claude-dev]: imaging-problem-list domain model notes, dev branch diff --git a/archive/knowledge/data-structures/hierarchy.excalidraw b/archive/knowledge/data-structures/hierarchy.excalidraw new file mode 100644 index 0000000..b3a5080 --- /dev/null +++ b/archive/knowledge/data-structures/hierarchy.excalidraw @@ -0,0 +1,950 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "rectangle", + "id": "obs", + "x": 0, + "y": 40, + "width": 250, + "height": 70, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "obs_t", + "type": "text" + }, + { + "id": "a_obs_efl", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "obs_t", + "x": 10, + "y": 50, + "width": 230, + "height": 50, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Observation\none finding, one exam", + "originalText": "Observation\none finding, one exam", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "obs", + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "obs_fields", + "x": 280, + "y": 40, + "width": 235.48, + "height": 87.5, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "findingCode (OIFM)\nfindingDescription\nanatomicLocation (RID)\nattributes[] with value codes\nreportText", + "originalText": "findingCode (OIFM)\nfindingDescription\nanatomicLocation (RID)\nattributes[] with value codes\nreportText", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "obs_status", + "x": 280, + "y": 135.5, + "width": 241.27999999999997, + "height": 16.25, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "JSON in use; no published schema", + "originalText": "JSON in use; no published schema", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "obs_tick", + "x": 250, + "y": 75.0, + "width": 30, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 30, + 0 + ] + ] + }, + { + "type": "rectangle", + "id": "efl", + "x": 0, + "y": 225.75, + "width": 250, + "height": 70, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "efl_t", + "type": "text" + }, + { + "id": "a_obs_efl", + "type": "arrow" + }, + { + "id": "a_efl_ipl", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "efl_t", + "x": 10, + "y": 235.75, + "width": 230, + "height": 50, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Exam Finding List\nevery observation of one exam", + "originalText": "Exam Finding List\nevery observation of one exam", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "efl", + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "efl_fields", + "x": 280, + "y": 225.75, + "width": 235.48, + "height": 70.0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "diagnosticReportId\npatientInfo\nexamInfo with LOINC exam code\nfindings[]: the observations", + "originalText": "diagnosticReportId\npatientInfo\nexamInfo with LOINC exam code\nfindings[]: the observations", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "efl_status", + "x": 280, + "y": 303.75, + "width": 241.27999999999997, + "height": 16.25, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "JSON in use; no published schema", + "originalText": "JSON in use; no published schema", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "efl_tick", + "x": 250, + "y": 260.75, + "width": 30, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 30, + 0 + ] + ] + }, + { + "type": "arrow", + "id": "a_obs_efl", + "x": 125.0, + "y": 110, + "width": 0.0, + "height": 115.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 115.75 + ] + ], + "startBinding": { + "elementId": "obs", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "efl", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_obs_efl_l", + "x": 231.52, + "y": 155.875, + "width": 64.96, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "per exam", + "originalText": "per exam", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ipl", + "x": 0, + "y": 394.0, + "width": 250, + "height": 70, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ipl_t", + "type": "text" + }, + { + "id": "a_efl_ipl", + "type": "arrow" + }, + { + "id": "a_ipl_persona", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ipl_t", + "x": 10, + "y": 404.0, + "width": 230, + "height": 50, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Imaging Problem List\none patient, keyed by finding", + "originalText": "Imaging Problem List\none patient, keyed by finding", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ipl", + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ipl_fields", + "x": 280, + "y": 394.0, + "width": 243.6, + "height": 87.5, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "patient\nfindings[] keyed by\n(findingCode, locationId)\neach with dated observations[]\nstatus derived, not stored", + "originalText": "patient\nfindings[] keyed by\n(findingCode, locationId)\neach with dated observations[]\nstatus derived, not stored", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ipl_status", + "x": 280, + "y": 489.5, + "width": 271.44, + "height": 16.25, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "JSON in use; grouping changed on dev", + "originalText": "JSON in use; grouping changed on dev", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "ipl_tick", + "x": 250, + "y": 429.0, + "width": 30, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 30, + 0 + ] + ] + }, + { + "type": "arrow", + "id": "a_efl_ipl", + "x": 125.0, + "y": 295.75, + "width": 0.0, + "height": 98.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 98.25 + ] + ], + "startBinding": { + "elementId": "efl", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ipl", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_efl_ipl_l", + "x": 227.46, + "y": 332.875, + "width": 73.08, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "all exams", + "originalText": "all exams", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "persona", + "x": 0, + "y": 579.75, + "width": 250, + "height": 70, + "strokeColor": "#64748b", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "persona_t", + "type": "text" + }, + { + "id": "a_ipl_persona", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "persona_t", + "x": 10, + "y": 589.75, + "width": 230, + "height": 50, + "strokeColor": "#475569", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Imaging Persona\nplus clinical context", + "originalText": "Imaging Persona\nplus clinical context", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "persona", + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "persona_fields", + "x": 280, + "y": 579.75, + "width": 308.56, + "height": 70.0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "clinical context (orders, indications)\nmedical baseline (problems, labs)\nspecialized history (oncology)\nsurgical history (operative, implants)", + "originalText": "clinical context (orders, indications)\nmedical baseline (problems, labs)\nspecialized history (oncology)\nsurgical history (operative, implants)", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "persona_status", + "x": 280, + "y": 657.75, + "width": 188.5, + "height": 16.25, + "strokeColor": "#475569", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "concept only, no artifact", + "originalText": "concept only, no artifact", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "persona_tick", + "x": 250, + "y": 614.75, + "width": 30, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 30, + 0 + ] + ] + }, + { + "type": "arrow", + "id": "a_ipl_persona", + "x": 125.0, + "y": 464.0, + "width": 0.0, + "height": 115.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 115.75 + ] + ], + "startBinding": { + "elementId": "ipl", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "persona", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_ipl_persona_l", + "x": 215.28, + "y": 509.875, + "width": 97.44, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "with context", + "originalText": "with context", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "note", + "x": 0, + "y": 714.0, + "width": 603.1999999999999, + "height": 32.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Codes come from the semantic foundation: OIFM and OIFMA identifiers from finding\nmodels, RID from anatomic locations, LOINC for the exam.", + "originalText": "Codes come from the semantic foundation: OIFM and OIFMA identifiers from finding\nmodels, RID from anatomic locations, LOINC for the exam.", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": {} +} \ No newline at end of file diff --git a/knowledge/data-structures/hierarchy.md b/archive/knowledge/data-structures/hierarchy.md similarity index 60% rename from knowledge/data-structures/hierarchy.md rename to archive/knowledge/data-structures/hierarchy.md index f57874b..90fc26e 100644 --- a/knowledge/data-structures/hierarchy.md +++ b/archive/knowledge/data-structures/hierarchy.md @@ -1,10 +1,10 @@ --- type: Concept title: The data structure hierarchy -description: The four-level ladder from Observation to Exam Finding List to Imaging Problem List to Imaging Persona, what each level adds, how identifiers thread through it, and how far each level is built. +description: The four data structure levels, their identifiers, and implementation status. tags: [data-structures, hierarchy, observation, exam-finding-list, imaging-problem-list, imaging-persona] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } sources: - id: deck resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal @@ -25,7 +25,7 @@ sources: # The ladder -The second of OIDM's three strategic pillars is a hierarchy of data structures, described in the January 2026 status deck as "the atomic Observation, then the Exam Finding List, then the Imaging Problem List, then the 'Imaging Persona'."[^deck] Each level is the one below it, collected and re-keyed. +The second of OIDM's three strategic pillars is a data structure hierarchy. The January 2026 status deck describes it as "the atomic Observation, then the Exam Finding List, then the Imaging Problem List, then the 'Imaging Persona'."[^deck] Each level is the one below it, collected and re-keyed. | Level | Scope | What it adds | Key it is organized by | |---|---|---|---| @@ -34,40 +34,25 @@ The second of OIDM's three strategic pillars is a hierarchy of data structures, | [Imaging Problem List](/data-structures/imaging-problem-list.md) | one patient | time: every observation of a finding across every exam | the finding | | [Imaging Persona](/data-structures/imaging-persona.md) | one patient | clinical context around the imaging: orders, baseline, specialized and surgical history | the patient | -The turn happens at level three. An [Exam Finding List](/glossary/exam-finding-list.md) is organized by exam, the way radiology reports have always been organized. An [Imaging Problem List](/glossary/imaging-problem-list.md) is "organized by finding, meaning pathology, rather than by chronology,"[^deck] which is what makes the deck's core query, "was finding X present on the most recent study?", a lookup rather than a search through a stack of reports. - -```mermaid -flowchart LR - subgraph L1["Observation"] - O1["finding code
anatomic location
attribute values"] - end - subgraph L2["Exam Finding List"] - E1["diagnosticReportId
patientInfo
examInfo (LOINC)
findings[]"] - end - subgraph L3["Imaging Problem List"] - P1["patient
findings[] grouped by
(findingCode, locationId)
each with observations[]"] - end - subgraph L4["Imaging Persona"] - A1["clinical context
medical baseline
specialized history
surgical history"] - end - O1 -->|"one exam's worth"| E1 - E1 -->|"one patient's exams, re-keyed by finding"| P1 - P1 -->|"surrounded by clinical context"| A1 -``` +The turn happens at level three. An [Exam Finding List](/glossary/exam-finding-list.md) is organized by exam, the way radiology reports have always been organized. An [Imaging Problem List](/glossary/imaging-problem-list.md) is "organized by finding, meaning pathology, rather than by chronology,"[^deck] allowing a lookup for the deck's query, "was finding X present on the most recent study?" + +[![From one observation to a patient's imaging history: an Observation carries a finding code, anatomic location, and attribute values; one exam's worth of observations roll up into an Exam Finding List; one patient's Exam Finding Lists re-key by finding into an Imaging Problem List; that list, surrounded by clinical context, becomes an Imaging Persona.](./hierarchy.svg)](./hierarchy.svg) + +*Diagram source: `hierarchy.excalidraw` beside this document (click the image for full size), generated by `tools/diagrams/build_hierarchy.py`.* # What each level adds -**Observation.** The deck states it as "what + where + attributes": a finding tag, an [anatomic location](/glossary/anatomic-location.md), and lesion characteristics, carrying presence indicators, change from prior, and measurements, in "a universal structure across systems."[^deck] It has no identity outside the list that contains it. +**Observation.** The deck describes "what + where + attributes" in "a universal structure across systems."[^deck] A finding tag, an [anatomic location](/glossary/anatomic-location.md), and lesion characteristics record presence, change from prior, and measurements. Its identity is local to the list that contains it. -**Exam Finding List.** "In the context of an imaging exam, a list of the findings declared as present/absent on that exam."[^ipl-main] Two of its properties are stated as requirements rather than conveniences. It "must also have basic information (keyed by a curated list of LOINC codes) about what exam this is," and "the same finding type may be declared as present multiple times; each time is a separate entry."[^ipl-main] The second rule is why three kidney stones are three observations rather than one observation with a count of three. +**Exam Finding List.** "In the context of an imaging exam, a list of the findings declared as present/absent on that exam."[^ipl-main] It "must also have basic information (keyed by a curated list of LOINC codes) about what exam this is," and "the same finding type may be declared as present multiple times; each time is a separate entry."[^ipl-main] Three kidney stones therefore produce three observations. **Imaging Problem List.** "In the context of a patient, the list of findings that have been described as present/absent in exams of the patient."[^ipl-main] Each entry holds every observation of that finding, each carrying its source report, exam date, exam type, and presence. On the development branch the grouping key is finding code **and** anatomic location, so "one finding code at distinct sites (e.g. ascending vs. abdominal aortic aneurysm) yields separate entries."[^ipl-claude-dev] -**Imaging Persona.** Named in the deck and nowhere else. It is the goal of surrounding the problem list with clinical context, medical baseline, specialized history, and surgical history.[^deck] No specification or sample exists. +**Imaging Persona.** The deck proposes adding clinical context, medical baseline, specialized history, and surgical history to the problem list.[^deck] No other project document names it, and no specification or sample exists. # How identifiers thread through -Nothing in the hierarchy embeds a definition. Every level refers to the [semantic foundation](/semantic-foundation/) by code, which is what lets two systems agree on a finding without sharing a database. +The structures refer to definitions in the [semantic foundation](/semantic-foundation/) by code. They do not embed definitions. Shared codes let systems agree on a finding without sharing a database. | Identifier | Form | Where it enters | Example | |---|---|---|---| @@ -77,7 +62,7 @@ Nothing in the hierarchy embeds a definition. Every level refers to the [semanti | [RadLex identifier](/glossary/radlex-id.md) | `RID####`, with sided structures composite | `anatomicLocation.locationId` | `RID42239`, and `RID39518_RID5824` for a left shoulder | | [LOINC](/glossary/loinc.md) code | LOINC part number | `examInfo.studyLoincCode`, and `exam_type_code` on a problem list observation | `26158-6` | -The letter block inside an OIFM identifier is the [organization code](/glossary/oidm-organization-code.md) of the contributing organization, so a single exam's findings routinely mix codes from several contributors. The five-finding worked example does exactly that, drawing on `GMTS`, `OIDM`, and `CDE` identifiers in one list.[^efl-sample] +The letter block in an OIFM identifier is the contributor's [organization code](/glossary/oidm-organization-code.md). One exam can use several contributors' codes. The five-finding worked example combines `GMTS`, `OIDM`, and `CDE` identifiers.[^efl-sample] # Implementation status @@ -89,7 +74,7 @@ The letter block inside an OIFM identifier is the [organization code](/glossary/ | Imaging Persona | Concept only | Four named context categories in the deck. No artifact. | | FHIR encoding of any level | Documented only | The mappings are written down and implemented nowhere. See [FHIR mapping](/data-structures/fhir-mapping.md). | -The absence at the bottom of the ladder is the one the project has flagged itself. Issue 1 asks for Pydantic models for Observation, Exam Finding List, and Imaging Problem List, with "extensive annotation to generate JSON schemas" and camelCase aliases on export against snake_case attributes internally.[^issue1] Until that lands, the structures are defined by prose, by sample data, and by the scripts that read and write them. The stated direction is collected in [the IPL data model system roadmap](/roadmap/ipl-data-model-system.md). +Issue 1 requests Pydantic models for Observation, Exam Finding List, and Imaging Problem List, with "extensive annotation to generate JSON schemas" and camelCase export aliases for snake_case attributes.[^issue1] Prose, samples, and scripts currently define the structures. See [the IPL data model system roadmap](/roadmap/ipl-data-model-system.md). [^deck]: "Open Imaging Data Model 2026 Status Update: Realizing Object-Oriented Imaging Results", January 2026 [^ipl-main]: imaging-problem-list README, main branch diff --git a/archive/knowledge/data-structures/hierarchy.svg b/archive/knowledge/data-structures/hierarchy.svg new file mode 100644 index 0000000..296fb8b --- /dev/null +++ b/archive/knowledge/data-structures/hierarchy.svg @@ -0,0 +1,2 @@ +Observationone finding, one examfindingCode (OIFM)findingDescriptionanatomicLocation (RID)attributes[] with value codesreportTextJSON in use; no published schemaExam Finding Listevery observation of one examdiagnosticReportIdpatientInfoexamInfo with LOINC exam codefindings[]: the observationsJSON in use; no published schemaper examImaging Problem Listone patient, keyed by findingpatientfindings[] keyed by(findingCode, locationId)each with dated observations[]status derived, not storedJSON in use; grouping changed on devall examsImaging Personaplus clinical contextclinical context (orders, indications)medical baseline (problems, labs)specialized history (oncology)surgical history (operative, implants)concept only, no artifactwith contextCodes come from the semantic foundation: OIFM and OIFMA identifiers from findingmodels, RID from anatomic locations, LOINC for the exam. \ No newline at end of file diff --git a/knowledge/data-structures/ihe-idr-alignment.md b/archive/knowledge/data-structures/ihe-idr-alignment.md similarity index 74% rename from knowledge/data-structures/ihe-idr-alignment.md rename to archive/knowledge/data-structures/ihe-idr-alignment.md index 487edf1..95a7833 100644 --- a/knowledge/data-structures/ihe-idr-alignment.md +++ b/archive/knowledge/data-structures/ihe-idr-alignment.md @@ -4,7 +4,7 @@ title: IHE IDR alignment description: What the IHE Imaging Diagnostic Report profile specifies for encoding findings as FHIR Observations, the deck's claim that the Exam Finding List connects to it, and the fact that no OIDM repository mentions it. tags: [data-structures, ihe, idr, fhir, alignment, goal] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: idr-extract resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/notes/ihe-idr-extract.md @@ -22,9 +22,9 @@ sources: # Status first -This is a stated goal, not existing work. The January 2026 deck's slide on the [Exam Finding List](/data-structures/exam-finding-list.md) ends with one line: it "connects to IHE Imaging Diagnostic Report (IDR) FHIR representation."[^deck] That is the entire claim from the OIDM side. Searching the `imaging-problem-list` repository for "IHE" or "IDR" returns nothing on any branch, and no OIDM repository references the profile. +IDR alignment is a goal. The January 2026 deck says the [Exam Finding List](/data-structures/exam-finding-list.md) "connects to IHE Imaging Diagnostic Report (IDR) FHIR representation."[^deck] That is the entire claim from the OIDM side. No branch of `imaging-problem-list` mentions "IHE" or "IDR", and no OIDM repository references the profile. -What does exist is a careful reading of the profile from the allied vocabulary work: a 72-page public-comment supplement, read directly and extracted on the `next-gen-2026` branch of `ACR-RSNA-CDEs`.[^idr-extract][^idr-supplement] That extract is the source for everything below, and it was written to test a different model, the next-generation CDE vocabulary, against the profile. It is the best available statement of what alignment would involve. +The `next-gen-2026` branch of `ACR-RSNA-CDEs` contains an extract of the 72-page public-comment supplement.[^idr-extract][^idr-supplement] The extract tests the next-generation CDE vocabulary against the profile and is the source for the account below. It is the best available statement of what alignment would involve. # What the profile is @@ -42,7 +42,7 @@ The profile's own illustration: "the presence of a tumor is a finding, a recorde # The observation grammar -Every IDR observation has a target entity and content, split across five slots. This is the grammar an [Exam Finding List](/data-structures/exam-finding-list.md) would have to fit. +Every IDR observation has a target entity and content in five slots. An [Exam Finding List](/data-structures/exam-finding-list.md) would need to map to them. | Slot | FHIR location | Rule stated in the supplement | |---|---|---| @@ -58,9 +58,9 @@ For grouped findings, IDR says that "Observation.code of the root finding shall # Where OIDM and IDR differ -Four differences are recorded in the extract. None of them has been worked through on the OIDM side; all are listed there as items to raise during public comment. +The extract records four unresolved differences as items to raise during public comment. OIDM has not worked through them. -**Components against hasMember.** IDR states that `Observation.component` "is not used", because FHIR limits components to values "not useful on their own" and using it "has the potential to significantly complicate queries". Every OIDM encoding that carries attributes uses components: the documented Exam Finding List mapping says so explicitly, "a list of components with attribute codes and values", and both lineage FHIR samples implement it that way.[^ipl-main] This is the sharpest of the four, because it is the mechanism rather than a vocabulary choice. See [FHIR mapping](/data-structures/fhir-mapping.md). +**Components against hasMember.** IDR states that `Observation.component` "is not used", because FHIR limits components to values "not useful on their own" and using it "has the potential to significantly complicate queries". All OIDM attribute encodings use components. The Exam Finding List mapping specifies "a list of components with attribute codes and values", as implemented by both lineage FHIR samples.[^ipl-main] This is the sharpest of the four, because it is the mechanism rather than a vocabulary choice. See [FHIR mapping](/data-structures/fhir-mapping.md). **The word "observation" means different things.** IDR's observation is "a feature or characteristic that is visible in an image", closer to a single attribute value than to an OIDM [Observation](/glossary/observation.md), which is a whole finding with its location and attributes. IDR's "finding" is narrower too: it is the presence or absence determination, not the named entity. The extract flags this as a vocabulary collision, and notes that the next-generation vocabulary work moved away from the word "observation" for exactly this reason. @@ -70,7 +70,11 @@ Four differences are recorded in the extract. None of them has been worked throu # What IDR is asking -The extract also records three questions IDR puts to the RadElement side, which are requirements on any alignment: what extensibility is permitted when encoding [CDE sets](/glossary/cde-set.md), and in particular whether additional sub-observations may be included; what the coding system identifier for RadElement codes is; and whether a presence value belongs in the parent of a grouped observation or as its first child.[^idr-extract] +The extract also records three questions IDR puts to the RadElement side, which are requirements on any alignment:[^idr-extract] + +- What extensions are permitted when encoding [CDE sets](/glossary/cde-set.md), particularly additional sub-observations? +- What is the coding system identifier for RadElement codes? +- Does presence belong in the parent of a grouped observation or its first child? # Where this stands @@ -81,7 +85,7 @@ The extract also records three questions IDR puts to the RadElement side, which | The differences are identified | Four, listed above, all recorded as items to raise, none resolved | | Anything is built | No | -The profile extract itself is not migrated into this bundle; it lives on the `next-gen-2026` branch of `ACR-RSNA-CDEs` and is linked above. The unresolved items are carried in [open questions](/roadmap/open-questions.md). +The extract remains on `ACR-RSNA-CDEs`'s `next-gen-2026` branch. It has not been migrated into this bundle. See [open questions](/roadmap/open-questions.md) for the unresolved items. [^idr-extract]: IHE IDR Phase II extract, ACR-RSNA-CDEs next-gen-2026 branch [^idr-supplement]: IHE Imaging Diagnostic Report Phase II public comment draft, 4 March 2026 diff --git a/archive/knowledge/data-structures/imaging-persona.md b/archive/knowledge/data-structures/imaging-persona.md new file mode 100644 index 0000000..8453e83 --- /dev/null +++ b/archive/knowledge/data-structures/imaging-persona.md @@ -0,0 +1,64 @@ +--- +type: Concept +title: Imaging Persona +description: "The fourth level of the hierarchy, a stated goal with no artifact: the clinical context around a patient's Imaging Problem List, and the life-cycle uses the 2026 deck gives for it." +tags: [data-structures, imaging-persona, roadmap, concept-only] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +sources: + - id: deck + resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal + title: "Open Imaging Data Model 2026 Status Update: Realizing Object-Oriented Imaging Results, January 2026" + - id: ipl-mvp-fhir + resource: https://github.com/openimagingdata/IPL-MVP-ExtractionAndLabeling/blob/5ea6720e114d3e2cdded5c39ecc5a2eb108e97fb/FHIR_Example_Structure + title: FHIR_Example_Structure, a longitudinal FHIR persistence design note on the IPL-MVP-ExtractionAndLabeling Persistent_FHIR_Resources branch + - id: site-structure + resource: https://www.openimagingdata.org/data-model-structure-and-function/ + title: '"Data Model: Structure and Function", openimagingdata.org, 2024-01-25' +--- + +# Status first + +The [Imaging Persona](/glossary/imaging-persona.md) is a goal named only in the January 2026 status deck as the fourth level of [the data structure hierarchy](/data-structures/hierarchy.md).[^deck] No schema, no sample, no code. Everything below is what the deck states.[^deck] + +# What the deck describes + +The slide "Imaging Persona (big picture)" names four context categories for a patient's [Imaging Problem List](/data-structures/imaging-problem-list.md).[^deck] + +| Category | Contents named | +|---|---| +| Clinical context | orders, indications | +| Medical baseline | problem list, allergies, laboratory results | +| Specialized history | oncology, treatments | +| Surgical history | operative records, pathology, implants | + +These categories add context beyond imaging results. Three of the four categories come from the electronic medical record rather than from radiology, which makes the persona as much an integration claim as a data structure. + +# Why: the imaging life cycle + +A separate slide names uses of the Imaging Problem List across the imaging life cycle that need additional context.[^deck] + +| Stage | Uses the deck names | +|---|---| +| Planning | magnetic resonance safety, mobility, prior authorization | +| Intra-exam | rules-based protocoling | +| Interpretation | findings-oriented views in the picture archiving system, real-time quality control | +| Post-interpretation | passive screening, research, outcomes | + +Read against the four categories, the dependency is concrete. Magnetic resonance safety screening is a question about implants, which is surgical history. Prior authorization needs the order and indication, which is clinical context. Rules-based protocoling needs prior findings and the clinical question. Passive screening needs the medical baseline to assess a finding's relevance to the patient. A problem list alone answers none of these; a problem list plus that context answers all four. + +The 2024 site post describes the model as organizing observations, current report text, imaging studies, patient, order, prior studies, tracked observations, and electronic health record data.[^site-structure] The persona corresponds to the patient context in that list. + +# A related unimplemented idea + +An unmerged branch of the early extraction prototype contains `FHIR_Example_Structure`, a design note on persistent findings. Each finding becomes "a **tracked entity** with a FHIR ID that persists," so later reports of the same lesion add dated values to the existing resource.[^ipl-mvp-fhir] Its timeline shows one liver lesion at 1.6 cm, 2.1 cm, and 2.8 cm across three reports. Example queries are "show me the timeline for liver lesion" and "how has the renal calculus changed?". + +It is a different idea from the persona. It addresses identity of a finding over time, which the current [Imaging Problem List](/data-structures/imaging-problem-list.md) approximates by grouping on finding code and location rather than by tracking a lesion. This is the repositories' only written design for durable patient imaging resources, a possible basis for a persona. It remains on an unmerged, single-commit branch of a superseded prototype. See [IPL-MVP extraction](/applications/ipl-mvp-extraction.md). + +# What would have to be decided + +Nothing about the persona has been specified, so this section states only what the deck leaves open rather than proposing answers. The deck leaves storage versus query-time assembly, ownership of each context category, encoding, and maintenance unspecified. See [open questions](/roadmap/open-questions.md) and [the 2026 roadmap](/roadmap/roadmap-2026.md). + +[^deck]: "Open Imaging Data Model 2026 Status Update", January 2026 +[^ipl-mvp-fhir]: FHIR_Example_Structure, IPL-MVP-ExtractionAndLabeling, Persistent_FHIR_Resources branch +[^site-structure]: "Data Model: Structure and Function", openimagingdata.org, 2024-01-25 diff --git a/knowledge/data-structures/imaging-problem-list.md b/archive/knowledge/data-structures/imaging-problem-list.md similarity index 53% rename from knowledge/data-structures/imaging-problem-list.md rename to archive/knowledge/data-structures/imaging-problem-list.md index a6653c7..a976edb 100644 --- a/knowledge/data-structures/imaging-problem-list.md +++ b/archive/knowledge/data-structures/imaging-problem-list.md @@ -1,10 +1,10 @@ --- type: Data Structure title: Imaging Problem List -description: The per-patient structure that reorganizes a patient's imaging findings by finding rather than by date, its fields, its grouping key, the temporal status computed over it, and what it is meant to become in FHIR. +description: A patient's observations grouped by finding, with fields, grouping rules, computed status, and planned FHIR encoding. tags: [data-structures, imaging-problem-list, anatomic-location, fhir] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } sources: - id: ipl-main resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/README.md @@ -37,9 +37,9 @@ sources: # Definition -"In the context of a patient, the list of findings that have been described as present/absent in exams of the patient."[^ipl-main] An [Imaging Problem List](/glossary/imaging-problem-list.md) is the third level of [the hierarchy](/data-structures/hierarchy.md) and the project's signature structure. It takes every [Exam Finding List](/data-structures/exam-finding-list.md) for one patient and re-keys the [observations](/data-structures/observation.md) inside them by finding. +"In the context of a patient, the list of findings that have been described as present/absent in exams of the patient."[^ipl-main] An [Imaging Problem List](/glossary/imaging-problem-list.md) groups [observations](/data-structures/observation.md) from a patient's [Exam Finding Lists](/data-structures/exam-finding-list.md) by finding. It is the third level of [the hierarchy](/data-structures/hierarchy.md). -That single change of key is the whole point. The deck states the core query the structure exists to answer: "was finding X present on the most recent study?"[^deck] Against a stack of reports organized by date that question is a search. Against a problem list it is a lookup. +Grouping by finding allows a lookup for the deck's core query: "was finding X present on the most recent study?"[^deck] # Fields @@ -64,23 +64,23 @@ That single change of key is the whole point. The deck states the core query the | `observations[].anatomicLocation` | `{locationId, locationDisplay}` | **development branch only**; repeats the finding's location | | `observations[].reportText` | string | the verbatim span, carried through so the timeline reads without fetching the source exams | -Note the field naming: the problem list uses snake_case for its own fields while carrying camelCase forward on the fields copied from the Exam Finding List, so `finding_type_code` and `anatomicLocation` sit in the same object. Two worked entries are in [the Imaging Problem List example](/references/imaging-problem-list-example.md). +The problem list uses snake_case for its own fields and retains camelCase for fields copied from the Exam Finding List. Thus `finding_type_code` and `anatomicLocation` share an object. See two worked entries in [the Imaging Problem List example](/references/imaging-problem-list-example.md). # The grouping key -On the stable branch, observations group by finding code alone. On the development branch the key is the pair of finding code and [anatomic location](/glossary/anatomic-location.md) identifier, with the rationale stated in the repository's own domain notes: +The stable branch groups by finding code. The development branch pairs that code with the [anatomic location](/glossary/anatomic-location.md) identifier. Its domain notes explain: > Groups observations by finding type **and** anatomic location (`locationId`) across exams, preserving references to each source report, so one finding code at distinct sites (e.g. ascending vs. abdominal aortic aneurysm) yields separate entries; consumers key on the IPL finding `id`, not `finding_type_code`.[^ipl-claude-dev] -The generator implements it directly, keying on the tuple of finding code and location identifier, with observations that have no location grouping under a null location.[^ipl-script] +The generator uses this tuple, grouping observations without a location under a null location.[^ipl-script] -The effect on real data is large. The same ten exams for one synthetic patient produce 98 problem list entries when grouped by finding code alone and 123 when grouped by finding code and location.[^ipl-sample] The observation count is unchanged; the grouping is finer. +The same ten exams for one synthetic patient produce 98 entries grouped by code alone and 123 grouped by code and location.[^ipl-sample] The observation count is unchanged. -This is why the documented rule is that consumers key on `id`. A finding code is no longer unique within a list. +Consumers key on `id` because a finding code is no longer unique within a list. # Temporal status -Status is **computed at display time, not stored**. No status field exists in the file. The viewer derives it per finding from the observation list.[^viewer-app] +The viewer computes each finding's status from its observations at display time. The file has no status field.[^viewer-app] 1. No observations gives **Unknown**. 2. Sort the observations by exam date, most recent first. @@ -89,46 +89,48 @@ Status is **computed at display time, not stored**. No status field exists in th 5. If the most recent is not `present` but some earlier observation was, the status is **Resolved**. 6. If no observation was ever `present`, the status is **Never**. -The viewer sections a patient's list under those four headings and offers them as a filter. +The viewer groups and filters findings by those four statuses. -Two things follow. **Never is not the same as absent from the list.** A finding with only absent observations is a finding that was looked for on named exams and not found, and the list records that it was looked for. **The scheme has four states, not three.** The stable branch's prose still describes an older three-state model of Present, Resolved, and Not Present or Ruled Out.[^ipl-main-claude] That wording is stale relative to the code on both branches, which computes four. The discrepancy is recorded in [the Imaging Problem List glossary entry](/glossary/imaging-problem-list.md). +A finding with only absent observations records that it was checked for on specific exams and not found. A finding missing from the list carries no such record. + +The stable branch's prose describes an older model with three states: Present, Resolved, and Not Present or Ruled Out.[^ipl-main-claude] That wording is stale relative to the code on both branches, which computes four. See [the Imaging Problem List glossary entry](/glossary/imaging-problem-list.md) for the discrepancy. # What the structure is for -The deck lists four properties beyond the core query.[^deck] +The deck lists these properties and a demonstration.[^deck] -- **Organized by finding, meaning pathology, rather than by chronology.** The reorganization is the feature. -- **Precision filtering by way of anatomy-embedded definitions.** This is what the grouping-key change on the development branch buys, and what the anatomy-aware viewer renders. -- **Dynamic tracking of appearance, disappearance, and change.** The computed status is the first cut at this; measurement trends over time are not modelled. +- Organized by finding, meaning pathology, rather than by chronology. The grouping key implements this. +- Filtering through anatomy-embedded definitions. The development branch's grouping key and viewer support this. +- Tracking appearance, disappearance, and change. Computed status provides an initial implementation. Measurement trends over time are not modelled. - A public demonstration at `imaging-problem-list.pages.dev`. -The uses the deck names across the imaging life cycle, from protocoling to passive screening, are covered under [Imaging Persona](/data-structures/imaging-persona.md), because they draw on context beyond imaging results. +See [Imaging Persona](/data-structures/imaging-persona.md) for uses such as protocoling and passive screening that need context beyond imaging results. # The pipeline diagram -The README carries `imaging-problem-list-process.png`, a hand-drawn pipeline in three stages.[^ipl-diagram] On the left, Report 1 through Report n feed an **Extractor**, drawn as a neural network and fed from below by a database labelled "Findings (CDE 'stub' defs)". The Extractor emits one Findings List per report. Those feed an **Assembler**, also drawn as a neural network, which emits the Imaging Problem List on the right. +The README's hand-drawn `imaging-problem-list-process.png` shows Report 1 through Report n feeding an Extractor, drawn as a neural network.[^ipl-diagram] A database labelled "Findings (CDE 'stub' defs)" also feeds the Extractor. Its Findings Lists feed an Assembler, also drawn as a neural network, which produces the Imaging Problem List. -The output panel is the argument. Four finding cards, Pulmonary Nodule, Pulmonary Apical Scarring, Subsegmental Atelectasis, and Pleural Effusion, each list their observations by dated exam. Pulmonary Nodule shows "Chest CT 2020-11-23 (x8)", the multiplicity collapsed into one dated row. Pleural Effusion shows five rows of which four are marked "(absent)", which is the negative-as-data claim drawn out: a finding whose entire history is absence still earns a card. +Four output cards list observations by dated exam: Pulmonary Nodule, Pulmonary Apical Scarring, Subsegmental Atelectasis, and Pleural Effusion. Pulmonary Nodule shows "Chest CT 2020-11-23 (x8)", combining repeated findings into one row. Pleural Effusion has five rows, four marked "(absent)". The list also retains findings whose entire history records absence. -The Assembler as drawn is a learned component. The implementation is not: `generate_ipl_from_efls.py` is deterministic aggregation.[^ipl-script] +The diagram depicts a learned Assembler, but `generate_ipl_from_efls.py` uses deterministic aggregation.[^ipl-script] # FHIR The documented encoding is "**Report** containing a list of **Condition** objects (labeled with the finding identifier), where each Condition object also contains a list of **Observation** objects which document which exams (**DiagnosticReports**) the finding type has been documented on, including the exam date and exam type (LOINC type)."[^ipl-main] One [FHIR Condition](/glossary/fhir-condition.md) per problem list entry, with the observation trail underneath it. -The mapping is documented and implemented nowhere. No Condition resource is produced by any code in the repository. Its container is also called a "Report," which is not a FHIR resource name. See [FHIR mapping](/data-structures/fhir-mapping.md). +The mapping is unimplemented. No code in the repository produces a Condition resource. Its container is called a "Report," which is not a FHIR resource name. See [FHIR mapping](/data-structures/fhir-mapping.md). # Known limitation -Grouping by exact location identifier is too strict when the same problem is described at different granularity on different exams. The assignment rules state the gap: +Exact location matching splits a problem described at different levels of detail across exams. The assignment rules state: > Parent/child or generic-vs-specific pairs for the *same finding code across exams* (e.g. "lung" vs "lower lobe of right lung"; "kidney" vs "left kidney") still produce separate IPL groups. Deciding whether such observations are the same problem or distinct is the **anatomic-compatibility reconciliation** step.[^anat-rules] -That step is named in the anatomic location plan and has not been started. It is the most concrete open problem in the structure. The full precedence, laterality, and specificity rules that produce the locations are in [anatomic location assignment rules](/data-structures/anatomic-location-assignment-rules.md). +The anatomic location plan names this step, but work has not started. See [anatomic location assignment rules](/data-structures/anatomic-location-assignment-rules.md) for precedence, laterality, and specificity rules. # How it is produced -`generate_ipl_from_efls.py` reads a directory of `*_efl.json` files, sorted by filename so the chronology follows, takes the patient block from the first one, groups every finding by the key above, and writes one `ipl.json`.[^ipl-script] It is ordinary Python with no model calls. The rendering side is the [Imaging Problem List viewer](/applications/imaging-problem-list-viewer.md); the sample data it runs on is described in [sample data](/data-structures/sample-data.md). +`generate_ipl_from_efls.py` reads `*_efl.json` files sorted by filename to follow chronology. It takes the first file's patient block, groups findings by the key above, and writes one `ipl.json` without model calls.[^ipl-script] See the [Imaging Problem List viewer](/applications/imaging-problem-list-viewer.md) and its [sample data](/data-structures/sample-data.md). [^ipl-main]: imaging-problem-list README, main branch [^ipl-main-claude]: imaging-problem-list domain model notes, main branch diff --git a/archive/knowledge/data-structures/index.md b/archive/knowledge/data-structures/index.md new file mode 100644 index 0000000..f355d3a --- /dev/null +++ b/archive/knowledge/data-structures/index.md @@ -0,0 +1,14 @@ +# Data structures + +OIDM records findings as Observations, groups them into Exam Finding Lists and Imaging Problem Lists, and proposes adding clinical context through the Imaging Persona. Codes link these structures to the semantic foundation. Start with the hierarchy. + +* [The data structure hierarchy](./hierarchy.md) - The four data structure levels, their identifiers, and implementation status. +* [Observation](./observation.md) - The atomic unit of OIDM: one finding, seen or explicitly excluded, on one exam, with its location and attribute values, as it exists today inside an Exam Finding List and in the extraction pipeline. +* [Exam Finding List](./exam-finding-list.md) - Findings from one exam, their fields and sources, and planned FHIR and IHE encodings. +* [Imaging Problem List](./imaging-problem-list.md) - A patient's observations grouped by finding, with fields, grouping rules, computed status, and planned FHIR encoding. +* [Imaging Persona](./imaging-persona.md) - The fourth level of the hierarchy, a stated goal with no artifact: the clinical context around a patient's Imaging Problem List, and the life-cycle uses the 2026 deck gives for it. +* [Anatomic location assignment rules](./anatomic-location-assignment-rules.md) - Migrated rules for assigning anatomy, laterality, and specificity to Exam Finding List observations. +* [Technical imaging findings](./technical-imaging-findings.md) - A migrated draft catalog of modality-specific findings and search terms for coding them. +* [FHIR mapping](./fhir-mapping.md) - Everything OIDM has documented about representing its data structures in FHIR, from the CDE-labeled Observation pattern of the lineage repositories to the current Exam Finding List and Imaging Problem List mappings, and the plain fact that no current code emits FHIR. +* [IHE IDR alignment](./ihe-idr-alignment.md) - What the IHE Imaging Diagnostic Report profile specifies for encoding findings as FHIR Observations, the deck's claim that the Exam Finding List connects to it, and the fact that no OIDM repository mentions it. +* [Sample data](./sample-data.md) - Synthetic exam and problem list samples, branch differences, viewer bundles, and generation scripts. diff --git a/knowledge/data-structures/observation.md b/archive/knowledge/data-structures/observation.md similarity index 56% rename from knowledge/data-structures/observation.md rename to archive/knowledge/data-structures/observation.md index 21eb7e1..29a46fa 100644 --- a/knowledge/data-structures/observation.md +++ b/archive/knowledge/data-structures/observation.md @@ -4,7 +4,7 @@ title: Observation description: "The atomic unit of OIDM: one finding, seen or explicitly excluded, on one exam, with its location and attribute values, as it exists today inside an Exam Finding List and in the extraction pipeline." tags: [data-structures, observation, presence, extraction] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } sources: - id: deck resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal @@ -37,9 +37,9 @@ sources: # What an observation is -An [observation](/glossary/observation.md) is one finding, seen or explicitly excluded, on one exam. The deck frames it as "what + where + attributes": a finding tag, an [anatomic location](/glossary/anatomic-location.md), and lesion characteristics, carrying presence indicators, change from prior, and measurements, in "a universal structure across systems."[^deck] +An [observation](/glossary/observation.md) is one finding, seen or explicitly excluded, on one exam. The deck describes "what + where + attributes" in "a universal structure across systems."[^deck] A finding tag, an [anatomic location](/glossary/anatomic-location.md), and lesion characteristics record presence, change from prior, and measurements. -Two of those three parts point at the [semantic foundation](/semantic-foundation/). The "what" is a [finding model](/glossary/finding-model.md) identifier. The "where" is a RadLex identifier. Only the "how" is local to the observation, and even there each [attribute](/glossary/attribute.md) and each chosen value carry codes from the finding model's own definition. +The [semantic foundation](/semantic-foundation/) supplies the "what" as a [finding model](/glossary/finding-model.md) identifier and the "where" as a RadLex identifier. The "how" records local characteristics, with each [attribute](/glossary/attribute.md) and chosen value coded from the finding model's definition. A negative observation is an observation. Absence is recorded the same way presence is, with a presence attribute whose value is `absent`, because a radiologist stating "no fracture" has actively looked and not found. That commitment is what makes the [Imaging Problem List](/data-structures/imaging-problem-list.md) able to distinguish a finding nobody checked for from one that was checked for and ruled out. @@ -79,21 +79,21 @@ Each entry in `attributes[]` carries four fields: `attributeCode`, an `OIFMA_[A- } ``` -That record is the first finding of a left shoulder radiograph in the sample data.[^efl-sample] A full list in context is in [the Exam Finding List example](/references/exam-finding-list-example.md). +This is the first finding from a sample left shoulder radiograph.[^efl-sample] See [the Exam Finding List example](/references/exam-finding-list-example.md) for a full list. ## Presence and the dot codes [Presence](/glossary/presence.md) is the attribute every observation carries. Its values in the sample data are `present`, `absent`, and `indeterminate`. -Value codes are positional, not semantic: a value code is the attribute identifier plus a dot and the value's zero-based position in the finding model's value list. Consuming code in the extraction repository documents the standard ordering as "`.1` = present, `.0` = absent."[^ipl-claude-dev] That ordering holds for the large majority of published finding models but not all of them, so a consumer that hard-codes `.1` as present is wrong for the minority that order their values differently. The [presence glossary entry](/glossary/presence.md) records the counts and the conflict. +Value codes are positional, not semantic: a value code is the attribute identifier plus a dot and the value's zero-based position in the finding model's value list. The extraction repository documents the standard ordering as "`.1` = present, `.0` = absent."[^ipl-claude-dev] Most published finding models follow that order, but hard-coding `.1` as present misreads those that do not. The [presence glossary entry](/glossary/presence.md) records the counts and the conflict. ## Repeat instances -"The same finding type may appear multiple times in one exam (e.g., multiple kidney stones). Each instance gets a separate entry with its own `observationId`."[^ipl-main-claude] Count is therefore expressed by the number of observations, not by a count attribute, at the Exam Finding List level. The problem list re-collapses them: several observations of one finding from one exam display as a single dated row. +"The same finding type may appear multiple times in one exam (e.g., multiple kidney stones). Each instance gets a separate entry with its own `observationId`."[^ipl-main-claude] The number of observations gives the count at the Exam Finding List level. The problem list displays repeated observations of one finding from one exam as a single dated row. # The extraction-time observation -The extraction platform on the development branch has its own Pydantic model for a finding as language models produce it, before any code assignment. It is a different object from the Exam Finding List entry and is worth reading as the pre-coding form of the same thing.[^extract-models] +The extraction platform on the development branch has its own Pydantic model for language model output before code assignment.[^extract-models] It is a different object from the Exam Finding List entry and is worth reading as the pre-coding form of the same thing. | Model | Fields | |---|---| @@ -102,17 +102,17 @@ The extraction platform on the development branch has its own Pydantic model for | `FindingAttribute` | `key`, `value` | | `ExtractedReportFindings` | `exam_info`, `findings[]`, `non_finding_text[]` | -Three differences from the coded form matter. +It differs from the coded form in three ways. -**The presence vocabulary is wider.** `presence` is a `Literal["present", "absent", "indeterminate", "possible"]`, and the design note explains the fourth value: "'possible' covers hedged language like 'raising the possibility of', 'suggestive of', 'cannot exclude'."[^extract-models][^extract-plan] There is no `possible` in the finding model presence value set, so the extra value has no coded counterpart and must be resolved before an Exam Finding List entry can be written. +**Presence has a fourth value.** `presence` is a `Literal["present", "absent", "indeterminate", "possible"]`. The design note explains that "'possible' covers hedged language like 'raising the possibility of', 'suggestive of', 'cannot exclude'."[^extract-models][^extract-plan] Finding models have no presence code for `possible`, so it must be resolved before writing an Exam Finding List entry. -**Location is free text, not a code.** `FindingLocation` carries a constrained `body_region`, a free-text `specific_anatomy`, and a laterality. Turning that into a `locationId` is the job of the separate post-extraction coding pass, governed by [the anatomic location assignment rules](/data-structures/anatomic-location-assignment-rules.md). +**Location is free text, not a code.** `FindingLocation` carries a constrained `body_region`, a free-text `specific_anatomy`, and a laterality. A separate coding pass assigns a `locationId` using [the anatomic location assignment rules](/data-structures/anatomic-location-assignment-rules.md). -**Attributes are loose key-value pairs.** `FindingAttribute` is a `key` and a `value` as strings, with standard keys named in the model docstring: size, acuity, change from prior, severity, count, and morphology.[^extract-models] Those are not OIFMA codes and carry no value codes. +**Attributes have no codes.** `FindingAttribute` holds `key` and `value` strings. The model docstring names size, acuity, change from prior, severity, count, and morphology as standard keys.[^extract-models] -The model is also where coding results land. A `FindingCodingBundle` on each finding records the chosen OIFM identifier, the method used (`fast-path`, `llm`, or `unresolved`), a reason when unresolved, and the candidate codes that were considered. The pipeline that produces it is described in [finding and location coding](/applications/finding-and-location-coding.md). +A `FindingCodingBundle` on each finding records the chosen OIFM identifier, the method used (`fast-path`, `llm`, or `unresolved`), a reason when unresolved, and candidate codes considered. See [finding and location coding](/applications/finding-and-location-coding.md) for the pipeline. -The mapping from the extraction model to the Exam Finding List entry is therefore not a rename. It is a coding step: +Converting extraction output to an Exam Finding List entry requires these coding steps: | Extraction field | Exam Finding List field | How | |---|---|---| @@ -124,13 +124,13 @@ The mapping from the extraction model to the Exam Finding List entry is therefor # The FHIR precedent -The 2024 reference implementation modelled an observation as a FHIR `Observation` directly. Its docstring states "the Observation class is the model for FHIR Observation objects," and the class carries `resourceType`, `code`, `status`, `subject`, `bodySite` as a `CodeableConcept`, `derivedFrom` as references, and `component` as a discriminated union of codeable-concept, string, integer, and boolean variants keyed on the FHIR `value[x]` naming convention.[^lineage-obs] +The 2024 reference implementation used FHIR `Observation` directly. Its docstring states "the Observation class is the model for FHIR Observation objects," with `resourceType`, `code`, `status`, and `subject` fields. It also carries `bodySite` as a `CodeableConcept` and `derivedFrom` as references. Its `component` is a discriminated union of codeable-concept, string, integer, and boolean variants keyed on FHIR `value[x]` names.[^lineage-obs] -The lung cancer screening samples show the same shape with real codes. A finding observation's `code` is a [CDE set](/glossary/cde-set.md) identifier under `https://radelement.org`, each `component` code is a [CDE element](/glossary/cde-element.md) identifier, and each component's `valueCodeableConcept` gives the chosen value code.[^fhir-sample] In those samples `status` is what separates a machine-produced finding from a radiologist-confirmed one: `preliminary` for the AI observation, `final` for the radiologist's, with no change to the resource type. That distinction is the closest thing in the project's history to a [provenance](/glossary/provenance.md) marker on an observation, and it is what the Exam Finding List specification asks for when it says "each Observation should include some kind of provenance marker." See [FHIR mapping](/data-structures/fhir-mapping.md). +The lung cancer screening samples use [CDE set](/glossary/cde-set.md) identifiers under `https://radelement.org` for each finding's `code`. Each `component` uses a [CDE element](/glossary/cde-element.md) code, with the chosen value in `valueCodeableConcept`.[^fhir-sample] Both machine-produced and radiologist-confirmed findings use the same resource type. Their `status` differs, with `preliminary` for the AI observation and `final` for the radiologist's. This provides a precedent for the [provenance](/glossary/provenance.md) marker requested by the Exam Finding List specification: "each Observation should include some kind of provenance marker." See [FHIR mapping](/data-structures/fhir-mapping.md). # What is missing -There is no Observation model, no JSON Schema, and no standalone record. Issue 1 in the extraction repository states the gap and the intended shape: Pydantic models for Observation, with a possible "Extracted Observation" subtype, alongside the Exam Finding List and Imaging Problem List, with "extensive annotation to generate JSON schemas" and camelCase aliases on export against snake_case attributes internally.[^issue1] The issue is open and unclaimed by any current plan. The Extracted Observation subtype it floats is exactly the distinction described above, between what a language model produced and what has been coded. +There is no standalone Observation model or JSON Schema. Issue 1 in the extraction repository states the gap and the intended shape: Pydantic models for Observation, Exam Finding List, and Imaging Problem List, with "extensive annotation to generate JSON schemas" and camelCase export aliases for snake_case attributes. It also proposes a possible "Extracted Observation" subtype to distinguish language model output from coded findings.[^issue1] The issue is open and unclaimed by any current plan. [^deck]: "Open Imaging Data Model 2026 Status Update", January 2026 [^ipl-main-claude]: imaging-problem-list domain model notes, main branch diff --git a/knowledge/data-structures/sample-data.md b/archive/knowledge/data-structures/sample-data.md similarity index 76% rename from knowledge/data-structures/sample-data.md rename to archive/knowledge/data-structures/sample-data.md index c11da97..9f39b04 100644 --- a/knowledge/data-structures/sample-data.md +++ b/archive/knowledge/data-structures/sample-data.md @@ -1,10 +1,10 @@ --- type: Reference title: Sample data -description: The synthetic patients, exams, and findings that make up the Exam Finding List and Imaging Problem List sample data, what changes between branches, where the viewer bundles live, and how to regenerate everything. +description: Synthetic exam and problem list samples, branch differences, viewer bundles, and generation scripts. tags: [data-structures, sample-data, reference, synthetic] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: ipl-main-claude resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/CLAUDE.md @@ -34,7 +34,7 @@ sources: # All of it is synthetic -Every patient, identifier, date, and line of report text in this data set is written test data. The names, medical record numbers, and birth dates do not belong to real people, and the reports were authored for the project. Counts below were read at commit `06f64a7` on `main` and `36fa30c` on `dev`. +Every patient, identifier, date, and line of report text in this data set is written test data. The names, medical record numbers, and birth dates do not belong to real people, and the reports were authored for the project. Counts were read at `06f64a7` on `main` and `36fa30c` on `dev`. # The three sample sets @@ -65,7 +65,7 @@ Findings per exam range from 9 on the shoulder radiograph to 48 on the January 2 # What the branches differ on -The two branches hold the same exams and very nearly the same findings. What changed on `dev` is the anatomic enrichment and the regrouping that followed it. +The branches contain the same exams and nearly the same findings. The development branch adds anatomic enrichment and regroups findings. | Measure | `main` | `dev` | |---|---|---| @@ -75,7 +75,7 @@ The two branches hold the same exams and very nearly the same findings. What cha | Entries in the `example2` Imaging Problem List | 98 | 123 | | Observations in that problem list | 275 | 276 | -The two observations without a location are in the brain magnetic resonance study, where the assignment rules require leaving a structure unassigned rather than forcing a wrong code when the ontology has no entry for it. The jump from 98 entries to 123 is the grouping key change, from finding code alone to finding code and location identifier; the observations did not multiply, the groups got finer. Both are explained in [Imaging Problem List](/data-structures/imaging-problem-list.md). +Two observations in the brain magnetic resonance study lack locations because the ontology has no matching structures. Assignment rules leave them unassigned. Grouping by finding code and location instead of code alone increases the problem list from 98 to 123 entries. See [Imaging Problem List](/data-structures/imaging-problem-list.md). # File layout @@ -98,7 +98,7 @@ sample_data/ example3/ 31 .txt reports, dev branch only ``` -The viewer reads a different, nested layout, documented in the repository's own notes.[^ipl-main-claude] +The repository notes document the viewer's nested layout.[^ipl-main-claude] ``` viewer/data/ @@ -113,13 +113,13 @@ viewer/data/ finding_region_mappings.json 98 finding-to-body-region entries ``` -Its bundle covers both synthetic patients: `patient-mrn0000001` with 10 exams and 98 problem list entries, and `patient-mrn0000002` with 2 exams and 10 entries. **This bundle was not re-enriched.** On both `main` and `dev` it holds the 98-entry, location-free problem list, so the deployed first-generation [viewer](/applications/imaging-problem-list-viewer.md) shows the pre-anatomy data. +The bundle covers `patient-mrn0000001` with 10 exams and 98 problem list entries, and `patient-mrn0000002` with 2 exams and 10 entries. It was not re-enriched on either branch. The deployed first-generation [viewer](/applications/imaging-problem-list-viewer.md) therefore shows the 98-entry, location-free list. The second-generation viewer has its own bundle under `viewer_v2/public/data/`, built from `sample_data/example2`, carrying the 123-entry anatomy-grouped list for the one patient, with reports as markdown rather than text and extra files for the anatomy index, anatomy clusters, and finding display information. Its manifest is stamped `viewer-v2-data.1` and records its own source paths and warnings. ## The spreadsheet -`findings_with_oifm_ids.xlsx` is the human working sheet the whole `example2` set was built from. It is one row per finding per exam, with columns Exam Date, Exam Type, Exam Code, Finding, OIDM Finding Model Name, OIDM FMID, Presence OIFMA_ID, Present/Absent, and Text.[^excel-script] It records where the [finding model](/glossary/finding-model.md) and [attribute](/glossary/attribute.md) identifiers were assigned by hand, which is why it is worth keeping alongside the generated JSON. +`findings_with_oifm_ids.xlsx` is the working sheet for `example2`, with one row per finding per exam. Its columns are Exam Date, Exam Type, Exam Code, Finding, OIDM Finding Model Name, OIDM FMID, Presence OIFMA_ID, Present/Absent, and Text.[^excel-script] It records where the [finding model](/glossary/finding-model.md) and [attribute](/glossary/attribute.md) identifiers were assigned by hand, which is why it is worth keeping alongside the generated JSON. # Regenerating @@ -132,7 +132,7 @@ Four scripts produce the data, in this order. | `generate_ipl_from_efls.py` | reads a directory of `*_efl.json` files sorted by filename, groups by finding code and location identifier, writes one `ipl.json`[^ipl-script] | | `build_viewer_v2_data.py` | builds the static JSON bundle the second-generation viewer serves[^viewer2-script] | -Only the enrichment step calls a language model. The other three are deterministic. Exact invocation belongs with the code and is not repeated here; see the repository. +Only enrichment calls a language model. The other three scripts are deterministic. See the repository for commands. [^ipl-main-claude]: imaging-problem-list domain model notes, main branch [^excel-script]: generate_efl_from_excel.py diff --git a/knowledge/data-structures/technical-imaging-findings.md b/archive/knowledge/data-structures/technical-imaging-findings.md similarity index 91% rename from knowledge/data-structures/technical-imaging-findings.md rename to archive/knowledge/data-structures/technical-imaging-findings.md index 97ab55a..a2930c8 100644 --- a/knowledge/data-structures/technical-imaging-findings.md +++ b/archive/knowledge/data-structures/technical-imaging-findings.md @@ -1,10 +1,10 @@ --- type: Reference title: Technical imaging findings -description: A draft catalog of modality-specific technical finding language, CT attenuation, MR signal, enhancement, ultrasound echogenicity, and nuclear medicine, with the search-term guidance for coding each, migrated from the imaging-problem-list development branch. +description: A migrated draft catalog of modality-specific findings and search terms for coding them. tags: [data-structures, technical-finding, coding, terminology, migrated, draft] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: tech resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/technical-imaging-findings.md @@ -13,7 +13,7 @@ sources: # About this document -This is a near-verbatim migration of `docs/technical-imaging-findings.md` from the `imaging-problem-list` repository's development branch.[^tech] The source marks itself **Draft, needs review against the finding ontology for coverage assessment**, and that status carries over: the catalog has not been audited against the finding model corpus, and its closing questions are open. Wording and structure follow the original. +Migrated near-verbatim from `docs/technical-imaging-findings.md` on `imaging-problem-list`'s development branch.[^tech] Wording and structure follow the original. The source is marked **Draft, needs review against the finding ontology for coverage assessment**. The catalog remains unaudited against the finding model corpus, and its closing questions remain open. Radiology reports frequently describe findings using modality-specific technical language rather than clinical diagnoses. These [technical findings](/glossary/technical-finding.md) describe what the radiologist observes on the images, including signal characteristics, density, enhancement patterns, and echogenicity, without necessarily committing to an underlying pathological process. diff --git a/knowledge/glossary/anatomic-location.md b/archive/knowledge/glossary/anatomic-location.md similarity index 81% rename from knowledge/glossary/anatomic-location.md rename to archive/knowledge/glossary/anatomic-location.md index a83765a..a936d9f 100644 --- a/knowledge/glossary/anatomic-location.md +++ b/archive/knowledge/glossary/anatomic-location.md @@ -4,7 +4,7 @@ title: Anatomic location description: A curated anatomic concept identified by a RadLex identifier, placed in containment and part-of hierarchies with laterality variants and cross-ontology codes. tags: [glossary, semantic-foundation, anatomy] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: al-site resource: https://github.com/talkasab/anatomiclocations.org/blob/1f39fa45f621cef947a3f3ef1f869334cfa5c841/docs/index.markdown @@ -22,7 +22,7 @@ sources: # Anatomic location -A discrete anatomic concept, curated for imaging use and identified by a [RadLex identifier](/glossary/radlex-id.md). The rationale is stated plainly on the project site: "standard identifiers for discrete anatomic locations would enable numerous levels of interoperability if applied broadly," but existing ontologies are unsuited to that job because they lack needed terms, carry too many degenerate ones, and have limited anatomic organization. The response is curation: "we are curating a subset of anatomic concepts from existing ontologies and shaping them into a usable collection of anatomic identifiers for informatics interoperability."[^al-site] +A discrete anatomic concept, curated for imaging use and identified by a [RadLex identifier](/glossary/radlex-id.md). The project site states that "standard identifiers for discrete anatomic locations would enable numerous levels of interoperability if applied broadly," but existing ontologies are unsuited to that job because they lack needed terms, carry too many degenerate ones, and have limited anatomic organization. The project describes its approach: "we are curating a subset of anatomic concepts from existing ontologies and shaping them into a usable collection of anatomic identifiers for informatics interoperability."[^al-site] Two implementations exist. The original curated set holds 2,890 nodes with `radlexId`, `description`, `containedById`, `partOfId`, a `leftId`/`rightId`/`unsidedId` triad, `sexSpecific`, `synonyms`, and `codes` into [SNOMED CT](/glossary/snomed-ct.md), [FMA](/glossary/fma.md), [UMLS](/glossary/umls.md), and MeSH.[^al-code] The newer `anatomic-locations` package normalizes the same material into a richer model with `region`, `location_type`, `body_system`, `structure_type`, a `laterality` enum, precomputed containment and part-of paths, and `left_variant`/`right_variant`/`generic_variant` references.[^al-package] @@ -44,7 +44,7 @@ A [RadLex RID](/glossary/radlex-id.md). Sided variants in the original set use c ## Conflicts -The two lineages are not synchronized and differ in count, field names, and laterality representation; see [Lineage and current implementation](/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md). The next-generation CDE vocabulary defines AnatomicLocation as "an anatomical reference identified by its RadLex identity, with temporary sided-variant exceptions governed by the anatomy track," treating sided variants as provisional, whereas the OIDM sets make them first-class nodes.[^cde-context] +The two lineages differ in count, field names, and laterality representation and are not synchronized. See [Lineage and current implementation](/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md). The next-generation CDE vocabulary defines AnatomicLocation as "an anatomical reference identified by its RadLex identity, with temporary sided-variant exceptions governed by the anatomy track," treating sided variants as provisional, whereas the OIDM sets make them first-class nodes.[^cde-context] [^al-site]: anatomiclocations.org site homepage [^al-code]: anatomiclocations.org data file description diff --git a/knowledge/glossary/anatomic-scope.md b/archive/knowledge/glossary/anatomic-scope.md similarity index 94% rename from knowledge/glossary/anatomic-scope.md rename to archive/knowledge/glossary/anatomic-scope.md index bc54aef..8c7adf6 100644 --- a/knowledge/glossary/anatomic-scope.md +++ b/archive/knowledge/glossary/anatomic-scope.md @@ -4,7 +4,7 @@ title: Anatomic scope description: The eligible anatomical places, tissue types, or structure types for a definition, stated as a constraint rather than as a location. tags: [glossary, semantic-foundation, anatomy, next-generation] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: cde-context resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/CONTEXT.md @@ -42,7 +42,7 @@ None. Scope is expressed as a list of concepts or scope specifiers. ## Conflicts -There is no scope field in the OIFM format. Scope exists only as authoring guidance and as reviewer judgment, so it cannot be checked mechanically the way a typed scope specifier could. +There is no scope field in the OIFM format. Scope exists only as authoring guidance and as reviewer judgment, without the automated checks a typed scope specifier could support. [^cde-context]: CDE vocabulary, next-generation working glossary [^overview]: "Finding Models: Overview" diff --git a/knowledge/glossary/assessment-scheme.md b/archive/knowledge/glossary/assessment-scheme.md similarity index 72% rename from knowledge/glossary/assessment-scheme.md rename to archive/knowledge/glossary/assessment-scheme.md index fbef425..901bcb5 100644 --- a/knowledge/glossary/assessment-scheme.md +++ b/archive/knowledge/glossary/assessment-scheme.md @@ -4,7 +4,7 @@ title: AssessmentScheme description: A definition of a standardized assessment system whose dimensions are ordinary data elements, distinct from the findings it assesses. tags: [glossary, semantic-foundation, cde, next-generation] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: cde-context resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/CONTEXT.md @@ -24,9 +24,9 @@ sources: "A distinct definition of an assessment system whose descriptive dimensions are ordinary DataElements linked to the scheme, analogous to a FindingClass with its descriptors. The scheme is distinct from any one dimension or that dimension's permissible values."[^cde-context] Lung-RADS, TI-RADS, BI-RADS, and LI-RADS are the systems this node type covers. -The key structural claim is separation. The scheme is not the category value it produces, and it is not the finding it assesses. OIDM's authoring guidance reaches the same conclusion from the finding model side: "scoring systems and structured assessments are valid findings, but should be modeled separately from the observations they assess," because "different radiologists might describe the same nodule but assign different risk categories, and systems need to reason about both independently."[^overview] +The scheme is distinct from its category values and the findings it assesses. OIDM's authoring guidance states: "scoring systems and structured assessments are valid findings, but should be modeled separately from the observations they assess," because "different radiologists might describe the same nodule but assign different risk categories, and systems need to reason about both independently."[^overview] -The lineage FHIR samples already implement that shape. A Lung-RADS Observation carries the category as a component and is `derivedFrom` both the imaging study and the radiologist's pulmonary nodule Observation.[^fhir-sample] IHE IDR calls the pattern a Summary or Derived Observation, with the value in the parent and the children referenced by `derivedFrom`.[^idr] +The lineage FHIR samples implement this separation. A Lung-RADS Observation carries the category as a component and is `derivedFrom` both the imaging study and the radiologist's pulmonary nodule Observation.[^fhir-sample] IHE IDR calls the pattern a Summary or Derived Observation, with the value in the parent and the children referenced by `derivedFrom`.[^idr] ## Synonyms and near-synonyms diff --git a/knowledge/glossary/attribute-value.md b/archive/knowledge/glossary/attribute-value.md similarity index 97% rename from knowledge/glossary/attribute-value.md rename to archive/knowledge/glossary/attribute-value.md index c8f6354..e5f4687 100644 --- a/knowledge/glossary/attribute-value.md +++ b/archive/knowledge/glossary/attribute-value.md @@ -4,7 +4,7 @@ title: Attribute value description: One permissible option of a choice attribute, carrying its own dot-suffixed value code and optional ontology codes. tags: [glossary, semantic-foundation, finding-models] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fm-py resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/finding_model.py diff --git a/knowledge/glossary/attribute.md b/archive/knowledge/glossary/attribute.md similarity index 86% rename from knowledge/glossary/attribute.md rename to archive/knowledge/glossary/attribute.md index 9363681..0269f1e 100644 --- a/knowledge/glossary/attribute.md +++ b/archive/knowledge/glossary/attribute.md @@ -4,7 +4,7 @@ title: Attribute description: A property a radiologist uses to characterize a finding, either a choice attribute with an enumerated value set or a numeric attribute with a range and unit. tags: [glossary, semantic-foundation, finding-models] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fm-py resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/finding_model.py @@ -22,7 +22,7 @@ sources: # Attribute -"An attribute that a radiologist would use to characterize a particular finding in a radiology report."[^fm-py] Every [finding model](/glossary/finding-model.md) carries at least one. There are exactly two kinds. +"An attribute that a radiologist would use to characterize a particular finding in a radiology report."[^fm-py] Every [finding model](/glossary/finding-model.md) has at least one attribute. There are exactly two kinds. | Kind | Discriminator | Distinctive fields | |---|---|---| @@ -48,7 +48,7 @@ Both kinds carry `oifma_id`, `name`, an optional `description`, a `required` fla ## Conflicts -The committee record shows an unresolved naming debate between "element," "attribute," and "data element" for this concept, and the next-generation working glossary settles on DataElement while OIFM keeps "attribute."[^siim][^cde-context] The two words denote nearly the same thing with different scoping rules, so a document must say which vocabulary it is using. +Committee discussions left the choice between "element," "attribute," and "data element" unresolved. The next-generation glossary uses DataElement, while OIFM uses "attribute."[^siim][^cde-context] The two words denote nearly the same thing with different scoping rules, so a document must say which vocabulary it is using. [^fm-py]: ChoiceAttribute and NumericAttribute in the findingmodel package [^fm-schema]: Finding model schema, prose mirror diff --git a/knowledge/glossary/body-region.md b/archive/knowledge/glossary/body-region.md similarity index 90% rename from knowledge/glossary/body-region.md rename to archive/knowledge/glossary/body-region.md index c21795a..0839894 100644 --- a/knowledge/glossary/body-region.md +++ b/archive/knowledge/glossary/body-region.md @@ -4,7 +4,7 @@ title: Body region description: A coarse anatomic grouping such as thorax or abdomen, used to scope findings, to fall back when no structure is named, and to filter views. tags: [glossary, semantic-foundation, anatomy] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: al-enums resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/anatomic-locations/src/anatomic_locations/models/enums.py @@ -22,7 +22,7 @@ sources: # Body region -A coarse anatomic grouping, one level above an organ and well above a named structure. Regions do three jobs in OIDM: they classify an [anatomic location](/glossary/anatomic-location.md), they carry broad anatomy on a [finding model](/glossary/finding-model.md) where precise anatomy belongs in `anatomic_locations`, and they provide the fallback when a finding names no anatomic structure at all. +A coarse anatomic grouping, one level above an organ and well above a named structure. Regions classify [anatomic locations](/glossary/anatomic-location.md), describe broad anatomy on [finding models](/glossary/finding-model.md), and provide a fallback when a finding names no structure. Precise anatomy belongs in a finding model's `anatomic_locations`. The fallback rule is explicit. Only findings with no anatomic noun, such as "soft tissue mass" or "generalized osteoporosis," fall back to the exam-scoped coarse region, and the grain is "the coarse region, not a soft-tissue substructure." The rule also states that "organ always wins over exam-region" whenever the finding has a real target organ, including edge-of-exam cases: "no consolidation" on an abdominal CT codes to lung, never to abdomen.[^rules] diff --git a/knowledge/glossary/cde-element.md b/archive/knowledge/glossary/cde-element.md similarity index 77% rename from knowledge/glossary/cde-element.md rename to archive/knowledge/glossary/cde-element.md index bff71ae..25dbc48 100644 --- a/knowledge/glossary/cde-element.md +++ b/archive/knowledge/glossary/cde-element.md @@ -4,7 +4,7 @@ title: CDE element description: One property inside a published CDE set, carrying an RDE identifier and a value set, integer, or float definition. tags: [glossary, semantic-foundation, cde] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: schema-diff resource: https://github.com/openimagingdata/openimagingdata.org/blob/fb431dfc81801a5a8e621c0e8bf2acd20f74be7d/schemas/schema_differences.md @@ -22,9 +22,9 @@ sources: # CDE element -One property inside a published [CDE set](/glossary/cde-set.md): the unit that carries a question and its permissible answers. An element definition names the property, declares its value type, and lists the values where the type is categorical. The schema distinguishes a `value_set` from `integer_values` and `float_values`, the latter carrying bounds and a step.[^schema-diff] +A property in a published [CDE set](/glossary/cde-set.md), with a question and permissible answers. Its definition names the property, declares its value type, and lists categorical values. The schema distinguishes a `value_set` from `integer_values` and `float_values`, the latter carrying bounds and a step.[^schema-diff] -In a [CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md), elements are what the components carry: the parent Observation's `code` is the set, each component's `code` is an element, and the component value is the chosen value code, for example element `RDE1717` with value `RDE1717.1`.[^fhir-sample] The dot-suffixed value code convention is the same one OIFM uses for [attribute values](/glossary/attribute-value.md). +In a [CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md), the parent's `code` identifies the set. Each component's `code` identifies an element, and its value identifies the chosen answer, such as `RDE1717.1` for element `RDE1717`.[^fhir-sample] The dot-suffixed value code convention is the same one OIFM uses for [attribute values](/glossary/attribute-value.md). ## Synonyms and near-synonyms diff --git a/knowledge/glossary/cde-labeled-fhir-observation.md b/archive/knowledge/glossary/cde-labeled-fhir-observation.md similarity index 81% rename from knowledge/glossary/cde-labeled-fhir-observation.md rename to archive/knowledge/glossary/cde-labeled-fhir-observation.md index 45a6400..215319b 100644 --- a/knowledge/glossary/cde-labeled-fhir-observation.md +++ b/archive/knowledge/glossary/cde-labeled-fhir-observation.md @@ -4,7 +4,7 @@ title: CDE-labeled FHIR Observation description: The project's founding representation of a finding, a FHIR Observation whose code and component codes are drawn from a published CDE set. tags: [glossary, data-structures, fhir, cde] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: site-findings resource: https://www.openimagingdata.org/findings-cdes-and-observations/ @@ -27,7 +27,7 @@ sources: The project's founding representation of a radiology finding: a FHIR `Observation` whose `code` identifies a [CDE set](/glossary/cde-set.md) and whose `component` codes identify the [CDE elements](/glossary/cde-element.md) inside it, each component's value giving the chosen value code. The phrase comes from the project's own About page and from the 2023 post that set the direction.[^site-about][^site-findings] -The lung screening sample implements it exactly. A radiologist's finding Observation carries `code` as `RDES195 Pulmonary Nodule` with `system` `https://radelement.org`, references the imaging study through `derivedFrom`, and carries element codes such as `RDE1717` in components with `valueCodeableConcept` giving codes such as `RDE1717.1`.[^fhir-sample] The reference implementation models the same shape in Python, with a discriminated union of codeable-concept, string, integer, and boolean components.[^lineage-obs] +In the lung screening sample, a radiologist's finding Observation carries `code` as `RDES195 Pulmonary Nodule` with `system` `https://radelement.org`, references the imaging study through `derivedFrom`, and carries element codes such as `RDE1717` in components with `valueCodeableConcept` giving codes such as `RDE1717.1`.[^fhir-sample] The reference implementation models the same shape in Python, with a discriminated union of codeable-concept, string, integer, and boolean components.[^lineage-obs] ## Synonyms and near-synonyms @@ -45,7 +45,7 @@ None of its own. The identifiers are those of the coding systems used, most ofte ## Conflicts -IHE IDR rejects the component pattern for this purpose. It states that `Observation.component` "is not used," because FHIR limits components to values "not useful on their own" and using it "has the potential to significantly complicate queries," preferring a root Observation with `hasMember` references to associated observations.[^idr] Every OIDM artifact that encodes attributes today, including the documented Exam Finding List mapping and these lineage samples, uses components. Reconciling the two is an open item; see [IHE IDR alignment](/data-structures/ihe-idr-alignment.md). +IHE IDR rejects the component pattern for this purpose. It states that `Observation.component` "is not used," because FHIR limits components to values "not useful on their own" and using it "has the potential to significantly complicate queries," preferring a root Observation with `hasMember` references to associated observations.[^idr] Every OIDM artifact that encodes attributes today, including the documented Exam Finding List mapping and these lineage samples, uses components. Reconciliation remains open. See [IHE IDR alignment](/data-structures/ihe-idr-alignment.md). [^site-findings]: "Findings, CDEs, and Observations", 2023-06-24 [^site-about]: openimagingdata.org About page diff --git a/knowledge/glossary/cde-set.md b/archive/knowledge/glossary/cde-set.md similarity index 82% rename from knowledge/glossary/cde-set.md rename to archive/knowledge/glossary/cde-set.md index 1d75aa8..f3f4d96 100644 --- a/knowledge/glossary/cde-set.md +++ b/archive/knowledge/glossary/cde-set.md @@ -4,7 +4,7 @@ title: CDE set description: A published RadElement definition of one finding, grouping the elements that characterize it under an RDES identifier. tags: [glossary, semantic-foundation, cde] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: cde-repo resource: https://github.com/openimagingdata/common_data_elements/blob/35536d8c858bcd33e730a00c919edaef2e310a0b/README.md @@ -22,11 +22,11 @@ sources: # CDE set -A published definition of one radiology finding on [RadElement](/glossary/radelement.md), grouping the [CDE elements](/glossary/cde-element.md) that characterize it. Roughly 140 sets are listed in the OIDM mirror of the published definitions, from Acute Aortic Syndrome to TI-RADS, each named and carrying an `RDES` identifier.[^cde-repo] +A published definition of one radiology finding on [RadElement](/glossary/radelement.md), grouping the [CDE elements](/glossary/cde-element.md) that characterize it. The OIDM mirror lists roughly 140 published sets, from Acute Aortic Syndrome to TI-RADS, each with a name and `RDES` identifier.[^cde-repo] A set carries identity and governance fields as well as its elements: `id`, `name`, `description`, `set_version`, `schema_version`, `current_status`, and `status_history`. The mirror notes that its definitions conform to version 1.0 of the CDE schema rather than the newer version 1.1.[^cde-repo] -In FHIR encodings the set identifier is what labels the parent Observation. The lineage sample codes a radiologist's finding as `RDES195 Pulmonary Nodule` with `system` `https://radelement.org`, and IHE IDR states that "when encoding CDE Sets from radelement.org, it is preferred to use the CDE Set code here."[^fhir-sample][^idr] +In FHIR encodings, the set identifier labels the parent Observation. The lineage sample codes a radiologist's finding as `RDES195 Pulmonary Nodule` with `system` `https://radelement.org`, and IHE IDR states that "when encoding CDE Sets from radelement.org, it is preferred to use the CDE Set code here."[^fhir-sample][^idr] ## Synonyms and near-synonyms diff --git a/knowledge/glossary/cde.md b/archive/knowledge/glossary/cde.md similarity index 93% rename from knowledge/glossary/cde.md rename to archive/knowledge/glossary/cde.md index 50382d2..d6ef2ab 100644 --- a/knowledge/glossary/cde.md +++ b/archive/knowledge/glossary/cde.md @@ -4,7 +4,7 @@ title: CDE description: Common data element, the ACR and RSNA programme of governed, balloted definitions for radiology findings and their elements. tags: [glossary, semantic-foundation, cde] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: cde-repo resource: https://github.com/openimagingdata/common_data_elements/blob/35536d8c858bcd33e730a00c919edaef2e310a0b/README.md @@ -24,7 +24,7 @@ sources: **Common data element (CDE).** A governed definition of a radiology finding or of one property of a finding, authored and balloted through the ACR and RSNA Common Data Elements programme and published on [RadElement](/glossary/radelement.md).[^cde-repo] The word is used at two granularities, which is the main source of confusion: a [CDE set](/glossary/cde-set.md) is a whole finding, a [CDE element](/glossary/cde-element.md) is one property inside it. -CDEs are the standards-track counterpart of OIDM's own [finding models](/glossary/finding-model.md). The 2026 status deck describes finding models as a "CDE workbench": a proving ground where LLM-assisted authoring produces content quickly, and the best of it graduates into the formal CDE process.[^deck] The 2023 site post that set the project's direction describes findings as FHIR Observations "semantically labeled with ACR/RSNA Common Data Element identifiers."[^site-findings] +CDEs are the standards-track counterpart of OIDM's own [finding models](/glossary/finding-model.md). The 2026 status deck describes finding models as a "CDE workbench": a proving ground where LLM-assisted authoring produces content quickly, and the best of it graduates into the formal CDE process.[^deck] The 2023 site post describes findings as FHIR Observations "semantically labeled with ACR/RSNA Common Data Element identifiers."[^site-findings] Between informal authoring and publication sits a staging step: `CDEStaging` holds candidate definitions "prior to their entering the review pipeline."[^staging] diff --git a/knowledge/glossary/change-from-prior.md b/archive/knowledge/glossary/change-from-prior.md similarity index 81% rename from knowledge/glossary/change-from-prior.md rename to archive/knowledge/glossary/change-from-prior.md index b1dae0c..e6eec1f 100644 --- a/knowledge/glossary/change-from-prior.md +++ b/archive/knowledge/glossary/change-from-prior.md @@ -4,7 +4,7 @@ title: Change from prior description: The companion second attribute of a finding model, recording how the finding compares with the previous exam. tags: [glossary, semantic-foundation, data-structures] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: overview resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/notes/oifm-overview.md @@ -24,7 +24,7 @@ sources: How a finding compares with the previous exam. Authoring guidance makes it the required second [attribute](/glossary/attribute.md) of a [finding model](/glossary/finding-model.md), after [presence](/glossary/presence.md), with values "unchanged, stable, new, resolved, plus clinically appropriate direction-of-change pairs (larger/smaller for masses, worsened/improved for diseases, increased/decreased for quantities)."[^overview] -The attribute exists because a finding is not a snapshot. "Findings exist in time. A radiology report is a snapshot, but the findings it describes persist across exams. A pleural effusion seen today may be the same one from last week, now larger."[^overview] Change from prior is what carries that comparison at the level of a single [observation](/glossary/observation.md), and the documented FHIR mapping singles it out along with presence as the components an Observation should carry.[^ipl-readme] +The attribute exists because a finding is not a snapshot. "Findings exist in time. A radiology report is a snapshot, but the findings it describes persist across exams. A pleural effusion seen today may be the same one from last week, now larger."[^overview] Change from prior records this comparison on a single [observation](/glossary/observation.md). The documented FHIR mapping calls for both change-from-prior and presence components.[^ipl-readme] Change from prior is also what distinguishes an attribute value from a finding. "Stable cardiac silhouette" is not a finding, because "stable" is a change-from-prior value on a cardiac finding.[^overview] @@ -45,7 +45,7 @@ An ordinary `OIFMA_[A-Z]{3,4}_[0-9]{6}` attribute identifier with dot-suffixed v ## Conflicts -The attribute is required by guidance but missing from part of the corpus. The open definition cleanup plan lists adding missing standard attributes, presence and change from prior, to 124 existing models as an ordered step, so consuming code cannot assume the attribute is present.[^cleanup] Value sets also vary by design, since direction-of-change pairs are chosen per finding, so no single enumeration covers the corpus. +Guidance requires this attribute, but some models lack it. The open cleanup plan schedules addition of missing presence and change-from-prior attributes to 124 models. Consumers cannot assume the attribute exists.[^cleanup] Direction-of-change pairs vary by finding, so no single enumeration covers the corpus. [^overview]: "Finding Models: Overview" [^ipl-readme]: imaging-problem-list README diff --git a/knowledge/glossary/coding.md b/archive/knowledge/glossary/coding.md similarity index 85% rename from knowledge/glossary/coding.md rename to archive/knowledge/glossary/coding.md index 2120dd0..9b543fa 100644 --- a/knowledge/glossary/coding.md +++ b/archive/knowledge/glossary/coding.md @@ -4,7 +4,7 @@ title: Coding description: Assigning finding identifiers and anatomic location identifiers to extracted findings, as a job distinct from extraction. tags: [glossary, applications, extraction] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: coding-design resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/coding-agent-design.md @@ -24,7 +24,7 @@ sources: Assigning an [OIFM](/glossary/oifm.md) finding identifier and an [anatomic location](/glossary/anatomic-location.md) identifier to a finding that [extraction](/glossary/extraction.md) produced as free text. "Coding assigns OIFM finding codes and anatomic location codes to extracted findings. It is an independent job, fully decoupled from extraction. Extraction output persists without codes; coding is triggered separately and can be re-run with different models or settings."[^coding-design] -The documented pipeline has five phases, two of which call a language model. A fast path resolves exact and synonym matches by index lookup for both axes, and findings that resolve skip the model entirely. For the rest, two parallel agents generate two or three diverse search terms each for finding and for location; batched index search returns candidates; per-finding selector agents choose a finding code and a location code in parallel; and assembly merges fast-path and model results. Each phase degrades independently, and one finding's failure does not block the others.[^coding-design] +The documented pipeline has five phases, two of which call a language model. Index lookup resolves exact and synonym matches for findings and locations without model calls. For unresolved findings, separate finding and location agents each generate two or three diverse search terms in parallel. Batched index search returns candidates. Selector agents choose finding and location codes in parallel, and assembly combines the results with index matches. Each phase degrades independently, and one finding's failure does not block the others.[^coding-design] Location coding follows its own precedence rules rather than free judgment; see [anatomic location assignment rules](/data-structures/anatomic-location-assignment-rules.md). Codes are resolved by direct lookup rather than semantic search for a named target organ, "to avoid retrieval misses," and "every assigned `locationId` must exist in the ontology," with unrepresented structures left unassigned rather than forced onto a wrong code.[^rules] diff --git a/knowledge/glossary/contained-by-and-part-of.md b/archive/knowledge/glossary/contained-by-and-part-of.md similarity index 86% rename from knowledge/glossary/contained-by-and-part-of.md rename to archive/knowledge/glossary/contained-by-and-part-of.md index 9ea7b61..992eaa0 100644 --- a/knowledge/glossary/contained-by-and-part-of.md +++ b/archive/knowledge/glossary/contained-by-and-part-of.md @@ -4,7 +4,7 @@ title: Contained by and part of description: The two orthogonal hierarchies over anatomic locations, one physical containment and one structural or functional membership. tags: [glossary, semantic-foundation, anatomy] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: al-code resource: https://github.com/talkasab/anatomiclocations.org/blob/1f39fa45f621cef947a3f3ef1f869334cfa5c841/docs/code.markdown @@ -27,9 +27,9 @@ The two hierarchies over [anatomic locations](/glossary/anatomic-location.md), k - **Contained by**: "physically contained in (e.g., kidney in the retroperitoneum)."[^al-code] - **Part of**: "a component of a larger structure/system (e.g., adnexa part of female genital system)."[^al-code] -Containment is the primary axis. The curated set is "organized into a directed, rooted tree hierarchy, starting from the whole body and ramifying through body regions," and containment is required on every node while part-of is optional.[^al-site] Part-of carries organ-system membership, which is why a structure can be contained in one region and part of a system that spans several. +Containment is the primary axis. The curated set is "organized into a directed, rooted tree hierarchy, starting from the whole body and ramifying through body regions," and containment is required on every node while part-of is optional.[^al-site] Part-of records organ-system membership. A structure can be contained in one region and belong to a system spanning several. -The newer package materializes both. A location carries `containment_path`, `containment_parent`, `containment_depth`, and `containment_children`, and the same four fields again with a `partof_` prefix, so ancestors and descendants on either axis resolve without walking the graph at query time.[^al-package] +The newer package precomputes both hierarchies. Each location has `containment_path`, `containment_parent`, `containment_depth`, and `containment_children`, and the same four fields again with a `partof_` prefix, so ancestors and descendants on either axis resolve without walking the graph at query time.[^al-package] ## Synonyms and near-synonyms diff --git a/knowledge/glossary/contributor.md b/archive/knowledge/glossary/contributor.md similarity index 84% rename from knowledge/glossary/contributor.md rename to archive/knowledge/glossary/contributor.md index 306bbfd..ac0aea0 100644 --- a/knowledge/glossary/contributor.md +++ b/archive/knowledge/glossary/contributor.md @@ -4,7 +4,7 @@ title: Contributor description: A person or organization credited on a finding model definition, recorded in a registry keyed by GitHub username or organization code. tags: [glossary, semantic-foundation, finding-models] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: contributor-py resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/contributor.py @@ -26,7 +26,7 @@ A person or organization credited on a [finding model](/glossary/finding-model.m | Organization | `name` (at least 5 characters), `code` matching `^[A-Z]{3,4}$` | `url` | | Person | `github_username`, `email`, `name`, `organization_code` | `url` | -Both types register themselves as they are validated, organizations by `code` and people by `github_username`, and both can be loaded from or saved to a JSONL file.[^contributor-py] Seven organizations ship in the base registry: Microsoft, MassGeneral Brigham, Radiology Gamuts Ontology, the Radiological Society of North America, the American College of Radiology, the ACR/RSNA Common Data Elements Project, and the Open Imaging Data Model itself.[^orgs] +Validation registers organizations by `code` and people by `github_username`. Both types support JSONL import and export.[^contributor-py] Seven organizations ship in the base registry: Microsoft, MassGeneral Brigham, Radiology Gamuts Ontology, the Radiological Society of North America, the American College of Radiology, the ACR/RSNA Common Data Elements Project, and the Open Imaging Data Model.[^orgs] In the published corpus of 2,382 definitions, organization contributors appear on Gamuts-derived, OIDM-authored, and CDE-derived models, while MassGeneral Brigham and Microsoft contributions are credited to individual people carrying those organization codes. diff --git a/knowledge/glossary/data-element.md b/archive/knowledge/glossary/data-element.md similarity index 67% rename from knowledge/glossary/data-element.md rename to archive/knowledge/glossary/data-element.md index e17bd9f..c98fc66 100644 --- a/knowledge/glossary/data-element.md +++ b/archive/knowledge/glossary/data-element.md @@ -4,7 +4,7 @@ title: DataElement description: In the next-generation CDE vocabulary, a shared categorical descriptor whose permissible values may be ordered, reused across definitions through element bindings. tags: [glossary, semantic-foundation, cde, next-generation] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: cde-context resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/CONTEXT.md @@ -19,11 +19,11 @@ sources: # DataElement -In the next-generation CDE vocabulary, "a categorical descriptor whose permissible values may be ordered or unordered. Semantic ordering is explicit and is distinct from display order."[^cde-context] A DataElement is a node in the definition graph, not a field inside a finding definition, and it is shared: a [FindingClass](/glossary/finding-class.md) reaches it through an [element binding](/glossary/element-binding.md), which may restrict the use to a subset of the element's permissible values without changing the element or its other bindings.[^cde-context] +In the next-generation CDE vocabulary, "a categorical descriptor whose permissible values may be ordered or unordered. Semantic ordering is explicit and is distinct from display order."[^cde-context] A DataElement is a shared node in the definition graph. A [FindingClass](/glossary/finding-class.md) references it through an [element binding](/glossary/element-binding.md), which may restrict permissible values for that use without changing the element or its other bindings.[^cde-context] -Three companion rules shape how a DataElement is used. Selection cardinality states "the permitted number of values selected for a DataElement in a use, such as single or multiple selection," explicitly distinct from value ordering and from whether a report mentions the element. Modality applicability separates a descriptor's intrinsic modality limit from its binding-specific applicability. Categorization requires that a distinction be expressed either by a categorizing DataElement or by a taxonomy of named classes, not both for the same distinction in the same model.[^cde-context] +Selection cardinality states "the permitted number of values selected for a DataElement in a use, such as single or multiple selection," explicitly distinct from value ordering and from whether a report mentions the element. Modality applicability separates a descriptor's intrinsic modality limit from its binding-specific applicability. Categorization requires that a distinction be expressed either by a categorizing DataElement or by a taxonomy of named classes, not both for the same distinction in the same model.[^cde-context] -This vocabulary is a working draft on the `next-gen-2026` branch. It is not a settled integration model, and its own preamble says so.[^cde-context] +The `next-gen-2026` vocabulary is a working draft. Its preamble states that the integration model is unsettled.[^cde-context] ## Synonyms and near-synonyms diff --git a/knowledge/glossary/element-binding.md b/archive/knowledge/glossary/element-binding.md similarity index 78% rename from knowledge/glossary/element-binding.md rename to archive/knowledge/glossary/element-binding.md index 88a88d9..cae3e50 100644 --- a/knowledge/glossary/element-binding.md +++ b/archive/knowledge/glossary/element-binding.md @@ -4,7 +4,7 @@ title: Element binding description: The relationship in the next-generation CDE vocabulary that connects a definition to a DataElement it uses, optionally restricting the values in that use. tags: [glossary, semantic-foundation, cde, next-generation] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: cde-context resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/CONTEXT.md @@ -21,15 +21,15 @@ sources: "A relationship connecting a definition to a DataElement it uses. A FindingClass's binding may restrict that use to a subset of the element's permissible values without changing the shared element or its other bindings."[^cde-context] -The binding is what makes element reuse safe. One severity element can serve many [FindingClasses](/glossary/finding-class.md); each binding narrows the permissible values, sets [selection cardinality](/glossary/data-element.md), and carries its own modality applicability, which the vocabulary states "is distinct from its binding-specific applicability and is not an overridable default."[^cde-context] +The binding is what makes element reuse safe. One severity element can serve many [FindingClasses](/glossary/finding-class.md). A binding can narrow permissible values, set [selection cardinality](/glossary/data-element.md), and record modality applicability. The descriptor's intrinsic modality limit "is distinct from its binding-specific applicability and is not an overridable default."[^cde-context] -An [AnatomicLocation](/glossary/anatomic-location.md) can also bind directly to DataElements and Measurements "describing that anatomy without requiring a FindingClass," so a property of a normal structure does not need a finding to hang from.[^cde-context] IHE IDR already supports that shape, encoding an observation whose target is an anatomic entity with a property as its code.[^idr] +An [AnatomicLocation](/glossary/anatomic-location.md) can also bind directly to DataElements and Measurements "describing that anatomy without requiring a FindingClass," so normal anatomy can have properties without a finding.[^cde-context] IHE IDR already supports that shape, encoding an observation whose target is an anatomic entity with a property as its code.[^idr] ## Synonyms and near-synonyms - **`HAS_ELEMENT`** is the edge name used in the working definition graph. - **Binding** alone usually means this, but "value binding" and "terminology binding" mean something different in FHIR. -- OIFM has no equivalent. An [attribute](/glossary/attribute.md) is a member of exactly one [finding model](/glossary/finding-model.md) and cannot be shared or restricted, which is the structural difference between the two vocabularies.[^fm-py] +- OIFM has no equivalent. An [attribute](/glossary/attribute.md) is a member of exactly one [finding model](/glossary/finding-model.md) and cannot be shared or restricted.[^fm-py] ## Identifier form diff --git a/knowledge/glossary/exam-finding-list.md b/archive/knowledge/glossary/exam-finding-list.md similarity index 92% rename from knowledge/glossary/exam-finding-list.md rename to archive/knowledge/glossary/exam-finding-list.md index caa9816..c41bdda 100644 --- a/knowledge/glossary/exam-finding-list.md +++ b/archive/knowledge/glossary/exam-finding-list.md @@ -4,7 +4,7 @@ title: Exam Finding List description: All findings declared present or absent on one imaging exam, in one queryable structure keyed to the exam and the report. tags: [glossary, data-structures, ipl] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: ipl-readme resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/README.md @@ -26,7 +26,7 @@ sources: The structure carries a `diagnosticReportId`, `patientInfo`, an `examInfo` block holding the study identifier, date and time, [LOINC](/glossary/loinc.md) code and description, and a `findings` array. Each finding carries an `observationId`, a `findingCode` in `OIFM_XXXX_######` form with its description, an `attributes` array of [attribute](/glossary/attribute.md) code, value code, and display text, an optional `anatomicLocation` of `locationId` and `locationDisplay`, and an optional verbatim `reportText`.[^ipl-claude][^efl-sample] -Two properties are stated as requirements rather than conveniences. The exam header must be keyed by a curated list of LOINC codes. And "the same finding type may be declared present multiple times; each time is a separate entry."[^ipl-readme] The deck adds that an Exam Finding List can be "sourced from dictation, AI tools, interpretation-time interfaces," which is why each observation should carry a [provenance](/glossary/provenance.md) marker.[^deck] +Two properties are stated as requirements rather than conveniences. The exam header must be keyed by a curated list of LOINC codes, and "the same finding type may be declared present multiple times; each time is a separate entry."[^ipl-readme] The deck adds that an Exam Finding List can be "sourced from dictation, AI tools, interpretation-time interfaces," so each observation should carry a [provenance](/glossary/provenance.md) marker.[^deck] ## Synonyms and near-synonyms diff --git a/knowledge/glossary/exam-type.md b/archive/knowledge/glossary/exam-type.md similarity index 62% rename from knowledge/glossary/exam-type.md rename to archive/knowledge/glossary/exam-type.md index ac9a2f3..3da3eba 100644 --- a/knowledge/glossary/exam-type.md +++ b/archive/knowledge/glossary/exam-type.md @@ -4,7 +4,7 @@ title: Exam type description: The kind of imaging study an exam is, coded with LOINC today and intended to become a first-class OIDM artifact linking modality, technique, and included body parts. tags: [glossary, semantic-foundation, exam-types] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: ipl-claude resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/CLAUDE.md @@ -25,11 +25,11 @@ sources: # Exam type -The kind of imaging study an exam is: modality, body part, technique, and contrast, taken together. Today OIDM identifies an exam type by a [LOINC](/glossary/loinc.md) code carried on the exam header, for example `72133-2` for CT Abdomen and Pelvis Without Contrast.[^ipl-claude] +An imaging study type defined by modality, body part, technique, and contrast. Today OIDM identifies an exam type by a [LOINC](/glossary/loinc.md) code carried on the exam header, for example `72133-2` for CT Abdomen and Pelvis Without Contrast.[^ipl-claude] -Exam type does more work than labelling. It bounds what a finding model should cover: "the scope of a finding model should match what's assessable on a given exam type, not broader."[^overview] It supplies the fallback [body region](/glossary/body-region.md) when a finding names no anatomic structure, and it supplies laterality when the exam itself is sided.[^rules] +Exam type bounds a finding model's scope: "the scope of a finding model should match what's assessable on a given exam type, not broader."[^overview] It supplies the fallback [body region](/glossary/body-region.md) when a finding names no anatomic structure, and it supplies laterality when the exam itself is sided.[^rules] -The stated goal is a first-class exam-type artifact rather than a bare code. The earliest written form asks for "a companion for exam types based on LOINC/RadLex Playbook exam definitions that specify all included body parts for the exam."[^al-site] The most developed written design sits in the terminology tool's roadmap, which makes the [LOINC/RSNA Radiology Playbook](/glossary/loinc-rsna-radiology-playbook.md) first-class in a default radiology profile and plans Playbook-weighted ranking for orderable queries.[^roadmap] +The stated goal is a first-class exam-type artifact rather than a bare code. The earliest proposal asks for "a companion for exam types based on LOINC/RadLex Playbook exam definitions that specify all included body parts for the exam."[^al-site] The terminology tool's roadmap contains the most developed design. It proposes making the [LOINC/RSNA Radiology Playbook](/glossary/loinc-rsna-radiology-playbook.md) first-class in a default radiology profile and plans Playbook-weighted ranking for orderable queries.[^roadmap] ## Synonyms and near-synonyms @@ -48,7 +48,7 @@ A LOINC code today. Legacy `RPID` Playbook identifiers are recognized in the pla ## Conflicts -No exam-type artifact exists. There is no published curated LOINC list, no exam-to-body-parts mapping, and no code that parses modality or body-part axes, even though three separate documents across three repositories describe the same goal. The area is documented as goals plus existing building blocks; see [Exam types](/roadmap/exam-types.md). +Three documents in three repositories describe the goal, but no exam-type artifact exists: no published curated LOINC list, exam-to-body-parts mapping, or code to parse modality or body-part axes. See goals and existing building blocks under [Exam types](/roadmap/exam-types.md). [^ipl-claude]: imaging-problem-list domain model, dev branch [^al-site]: anatomiclocations.org roadmap diff --git a/knowledge/glossary/extraction.md b/archive/knowledge/glossary/extraction.md similarity index 57% rename from knowledge/glossary/extraction.md rename to archive/knowledge/glossary/extraction.md index 057b87e..8ab4bc1 100644 --- a/knowledge/glossary/extraction.md +++ b/archive/knowledge/glossary/extraction.md @@ -4,7 +4,7 @@ title: Extraction description: Deriving structured findings from narrative radiology report text with a language model, producing findings, presence, location, and attributes with verbatim quotes. tags: [glossary, applications, extraction] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: extraction-plan resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/initial-extraction-plan.md @@ -25,11 +25,11 @@ sources: # Extraction -Deriving structured findings from narrative radiology report text using a language model. It is the first half of the pipeline that turns a report into OIDM data: "an agent reads a radiology report and identifies findings mentioned in the text," and each finding is then matched to a definition.[^overview] The deck states the goal that an [Imaging Problem List](/glossary/imaging-problem-list.md) be "automatically extractable from narrative report text via LLMs."[^deck] +Deriving structured findings from narrative radiology report text using a language model. The authoring overview states: "an agent reads a radiology report and identifies findings mentioned in the text," and each finding is then matched to a definition.[^overview] The deck states the goal that an [Imaging Problem List](/glossary/imaging-problem-list.md) be "automatically extractable from narrative report text via LLMs."[^deck] -Extraction output is uncoded. Its schema names a finding, asserts presence as one of `present`, `absent`, `indeterminate`, or `possible`, gives an optional location of body region, specific anatomy, and laterality, a list of key-value attributes with standard keys such as size, acuity, change from prior, severity, count, and morphology, and a verbatim `report_text` quote.[^extraction-plan] Assigning identifiers is a separate job; see [coding](/glossary/coding.md). +The uncoded output contains a finding name, presence as `present`, `absent`, `indeterminate`, or `possible`, and a verbatim `report_text` quote. An optional location records body region, specific anatomy, and laterality. Key-value attributes use standard keys such as size, acuity, change from prior, severity, count, and morphology.[^extraction-plan] Assigning identifiers is a separate job; see [coding](/glossary/coding.md). -Three design decisions shape the current implementation: long reports are semantically chunked and extracted concurrently then merged and deduplicated, output quotes are validated as verbatim both during and after generation, and a reviewer sub-agent can flag issues and trigger targeted re-extraction of specific chunks.[^ipl-claude] +The implementation splits long reports into semantic chunks, extracts them concurrently, and merges and deduplicates the results. It validates verbatim quotes during and after generation. A reviewer sub-agent can flag issues and trigger re-extraction of specific chunks.[^ipl-claude] ## Synonyms and near-synonyms @@ -48,7 +48,7 @@ None. Extracted findings carry names and quotes, not identifiers, until coding r ## Conflicts -The failure modes extraction guards against are stated in the validator prompt and are worth naming as part of the definition: content unsupported by the chunk text, which is hallucination; report text describing a finding that no structure represents, which is a missed finding; a finding described as present when it is not, or absent when it is possible; a finding name more specific than the text supports; incorrect representation of a blanket negative; and wrong, too specific, or too general location information.[^validator] Separately, the extraction presence value set does not match the finding model one; see [presence](/glossary/presence.md). +The failure modes extraction guards against are stated in the validator prompt: hallucinated content unsupported by the chunk, report text describing a finding that no structure represents, which is a missed finding, a finding described as present when it is not, or absent when it is possible, overly specific finding names, misrepresented blanket negatives, and incorrect or overly specific or general locations.[^validator] Separately, the extraction presence value set does not match the finding model one; see [presence](/glossary/presence.md). [^extraction-plan]: Initial extraction plan [^ipl-claude]: imaging-problem-list architecture notes diff --git a/knowledge/glossary/fhir-condition.md b/archive/knowledge/glossary/fhir-condition.md similarity index 54% rename from knowledge/glossary/fhir-condition.md rename to archive/knowledge/glossary/fhir-condition.md index 1a9d7ee..e41b3de 100644 --- a/knowledge/glossary/fhir-condition.md +++ b/archive/knowledge/glossary/fhir-condition.md @@ -4,7 +4,7 @@ title: FHIR Condition description: The HL7 FHIR resource for a clinical condition, used in the documented Imaging Problem List mapping as the container for one finding type. tags: [glossary, data-structures, fhir] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fhir resource: https://www.hl7.org/fhir/condition.html @@ -19,9 +19,9 @@ sources: # FHIR Condition -The HL7 FHIR resource for a clinical condition, problem, or diagnosis.[^fhir] In OIDM it appears in one place: the documented FHIR mapping of an [Imaging Problem List](/glossary/imaging-problem-list.md), which is "a Report containing a list of Condition objects (labeled with the finding identifier), where each Condition object also contains a list of Observation objects which document which exams (DiagnosticReports) the finding type has been documented on, including the exam date and exam type."[^ipl-readme] +The HL7 FHIR resource for a clinical condition, problem, or diagnosis.[^fhir] Its only OIDM use is the documented FHIR mapping of an [Imaging Problem List](/glossary/imaging-problem-list.md), which is "a Report containing a list of Condition objects (labeled with the finding identifier), where each Condition object also contains a list of Observation objects which document which exams (DiagnosticReports) the finding type has been documented on, including the exam date and exam type."[^ipl-readme] -One Condition therefore stands for one finding type in one patient, and the [observations](/glossary/observation.md) under it are the evidence trail across exams. +One Condition represents a finding type in one patient. Its [observations](/glossary/observation.md) record evidence across exams. ## Synonyms and near-synonyms @@ -40,7 +40,7 @@ A FHIR resource identifier. The mapping labels each Condition with the finding i ## Conflicts -Three points are unsettled. The mapping is documented and implemented nowhere; no Condition resource is produced by any OIDM code. Its container is called a "Report," which is not a FHIR resource name. And IHE IDR takes a different position on when Condition is appropriate, routing positive clinical findings to Condition and negative ones to Observation, against an OIDM working default that "every assertion in a radiology report, diagnoses included, is encoded as an Observation," partly because "'consistent with pneumonia' is a radiologist's assertion, not an established clinical condition."[^idr-extract] If that default holds, `Condition.evidence` as the finding-to-diagnosis link has nowhere to attach and an Observation-to-Observation equivalent is needed. +Three points are unsettled. The mapping is documented but unimplemented. No OIDM code produces Condition resources. Its container is called a "Report," which is not a FHIR resource name. IHE IDR routes positive clinical findings to Condition and negative ones to Observation, against an OIDM working default that "every assertion in a radiology report, diagnoses included, is encoded as an Observation," partly because "'consistent with pneumonia' is a radiologist's assertion, not an established clinical condition."[^idr-extract] If that default holds, `Condition.evidence` as the finding-to-diagnosis link has nowhere to attach and an Observation-to-Observation equivalent is needed. [^fhir]: Condition resource, HL7 FHIR [^ipl-readme]: imaging-problem-list README diff --git a/knowledge/glossary/fhir-diagnostic-report.md b/archive/knowledge/glossary/fhir-diagnostic-report.md similarity index 78% rename from knowledge/glossary/fhir-diagnostic-report.md rename to archive/knowledge/glossary/fhir-diagnostic-report.md index 9338687..6eee898 100644 --- a/knowledge/glossary/fhir-diagnostic-report.md +++ b/archive/knowledge/glossary/fhir-diagnostic-report.md @@ -4,7 +4,7 @@ title: FHIR DiagnosticReport description: The HL7 FHIR resource for a diagnostic report, used in OIDM as the interchange encoding of an Exam Finding List. tags: [glossary, data-structures, fhir] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fhir resource: https://www.hl7.org/fhir/diagnosticreport.html @@ -22,9 +22,9 @@ sources: # FHIR DiagnosticReport -The HL7 FHIR resource for a diagnostic report.[^fhir] In OIDM it is the documented interchange encoding of an [Exam Finding List](/glossary/exam-finding-list.md): "DiagnosticReport containing a list of Observation objects, with a finding code on each Observation and a list of components with attribute codes and values (especially present/absent and change from prior)."[^ipl-readme] In an [Imaging Problem List](/glossary/imaging-problem-list.md), DiagnosticReports are what each observation points back to, giving the exam date and [LOINC](/glossary/loinc.md) exam type.[^ipl-readme] +The HL7 FHIR resource for a diagnostic report.[^fhir] In OIDM it is the documented interchange encoding of an [Exam Finding List](/glossary/exam-finding-list.md): "DiagnosticReport containing a list of Observation objects, with a finding code on each Observation and a list of components with attribute codes and values (especially present/absent and change from prior)."[^ipl-readme] In an [Imaging Problem List](/glossary/imaging-problem-list.md), each observation references a DiagnosticReport for its exam date and [LOINC](/glossary/loinc.md) exam type.[^ipl-readme] -The lineage sample shows the shape with real data: a report with a SNOMED and HL7 category, a LOINC `code`, an inline `ImagingStudy`, `result` references to three [Observations](/glossary/observation.md), and `conclusion` with SNOMED `conclusionCode`.[^fhir-sample] +The lineage sample contains a SNOMED and HL7 category, a LOINC `code`, an inline `ImagingStudy`, `result` references to three [Observations](/glossary/observation.md), and `conclusion` with SNOMED `conclusionCode`.[^fhir-sample] ## Synonyms and near-synonyms @@ -43,7 +43,7 @@ A FHIR resource identifier. The Exam Finding List samples use UUID strings. ## Conflicts -The mapping is documented and unimplemented. No FHIR resource classes exist in the extraction platform's source, and a search for DiagnosticReport across it returns nothing; the only real FHIR documents in the repository are two input samples. Those input samples also do not use the presence and change-from-prior component pattern the specification prescribes, because that pattern describes the output.[^ipl-readme] Separately, IHE IDR would encode the attribute layer with `hasMember` rather than components.[^idr-extract] +The mapping is documented but unimplemented. No FHIR resource classes exist in the extraction platform's source, and a search for DiagnosticReport across it returns nothing; the only real FHIR documents in the repository are two input samples. Those input samples also do not use the presence and change-from-prior component pattern the imaging-problem-list README prescribes, because that pattern describes the output.[^ipl-readme] Separately, IHE IDR would encode the attribute layer with `hasMember` rather than components.[^idr-extract] [^fhir]: DiagnosticReport resource, HL7 FHIR [^ipl-readme]: imaging-problem-list README diff --git a/knowledge/glossary/finding-class.md b/archive/knowledge/glossary/finding-class.md similarity index 80% rename from knowledge/glossary/finding-class.md rename to archive/knowledge/glossary/finding-class.md index cd3216c..131d3f6 100644 --- a/knowledge/glossary/finding-class.md +++ b/archive/knowledge/glossary/finding-class.md @@ -4,7 +4,7 @@ title: FindingClass description: The next-generation CDE vocabulary's node type for a finding definition, separated from diagnosis and carrying bindings to shared elements. tags: [glossary, semantic-foundation, cde, next-generation] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: cde-context resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/CONTEXT.md @@ -19,7 +19,7 @@ sources: # FindingClass -The next-generation CDE vocabulary's node type for a finding definition: the counterpart of a [CDE set](/glossary/cde-set.md) or an OIDM [finding model](/glossary/finding-model.md) in a graph-shaped model. A FindingClass is described by ordinary [DataElements](/glossary/data-element.md) reached through [element bindings](/glossary/element-binding.md), sits in an is-a taxonomy of subtypes, and carries [standard clinical metadata](/glossary/tag.md): "the seven facts on a class: modality, body region, subspecialty, sex, age, time course, and etiology."[^cde-context] +The next-generation CDE vocabulary's node type for a finding definition: the counterpart of a [CDE set](/glossary/cde-set.md) or an OIDM [finding model](/glossary/finding-model.md) in a graph model. A FindingClass is described by ordinary [DataElements](/glossary/data-element.md) reached through [element bindings](/glossary/element-binding.md), sits in an is-a taxonomy of subtypes, and carries [standard clinical metadata](/glossary/tag.md): "the seven facts on a class: modality, body region, subspecialty, sex, age, time course, and etiology."[^cde-context] Two scoping mechanisms are defined on classes. [Anatomic scope](/glossary/anatomic-scope.md) states the eligible anatomical places, tissue types, or structure types for the definition. Component-of scope states "the finding classes a component class belongs inside, stated on the component class"; a class with a component-of scope "is never reported on its own," and component classes are specific to a lesion family.[^cde-context] @@ -43,7 +43,7 @@ Working identifiers in the alpha implementation take the form `FC-######`, for e ## Conflicts -IHE IDR uses "finding" for the presence-or-absence determination rather than for the named entity, and routes positive clinical findings to [FHIR Condition](/glossary/fhir-condition.md) while negative ones become Observations. The next-generation notes record a different working default, that every assertion in a radiology report including diagnoses is encoded as an Observation, and list the divergence as an item to raise with IHE.[^idr] +IHE IDR uses "finding" for the presence-or-absence determination rather than for the named entity, and routes positive clinical findings to [FHIR Condition](/glossary/fhir-condition.md) while negative ones become Observations. The next-generation working default encodes every radiology report assertion, including diagnoses, as an Observation. The notes list this difference for discussion with IHE.[^idr] [^cde-context]: CDE vocabulary, next-generation working glossary [^idr]: IHE IDR Phase II extract diff --git a/knowledge/glossary/finding-model.md b/archive/knowledge/glossary/finding-model.md similarity index 100% rename from knowledge/glossary/finding-model.md rename to archive/knowledge/glossary/finding-model.md diff --git a/knowledge/glossary/finding-taxonomy.md b/archive/knowledge/glossary/finding-taxonomy.md similarity index 75% rename from knowledge/glossary/finding-taxonomy.md rename to archive/knowledge/glossary/finding-taxonomy.md index c94e505..a0f1f58 100644 --- a/knowledge/glossary/finding-taxonomy.md +++ b/archive/knowledge/glossary/finding-taxonomy.md @@ -4,7 +4,7 @@ title: Finding taxonomy description: One of the six MGB exam-oriented sub-taxonomies, per-modality hierarchies of finding names exported as CSV, that now set the content direction for the finding model corpus. tags: [glossary, semantic-foundation, finding-models, content] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: lists-readme resource: https://github.com/openimagingdata/findingmodels/blob/a30c3c95fa3943e7340ce87575f4b1b926987eb8/lists/README.md @@ -15,7 +15,7 @@ sources: One of the six MGB exam-oriented sub-taxonomies: per-modality hierarchies of radiology finding names, contributed by Mass General Brigham and exported to the `findingmodels` repository on 2026-08-15 as comma-separated files.[^lists-readme] They replaced the repository's earlier `lists/` content wholesale. -Each file is one hierarchy with the columns `name`, `category`, `parent`, `synonyms`, `finding_type`, `finding_cluster`, and `oifm_id`. Rows are keyed by `name`, unique within a file, so there is no separate identifier column. The `parent` column holds the parent row's `name`; blank means top level. "Parents are generic, children add specificity," the worked example being `lung_abnormality` then `airspace_opacity` then `air_bronchogram`. Two columns are deliberately outside the hierarchy: `category` is "an independent anatomic grouping, not part of the hierarchy," and `finding_type` "separates an observation from a diagnosis."[^lists-readme] +Each file is one hierarchy with the columns `name`, `category`, `parent`, `synonyms`, `finding_type`, `finding_cluster`, and `oifm_id`. The unique `name` keys each row within a file. There is no separate row identifier. `parent` holds the parent row's `name`, or is blank for a top-level row. "Parents are generic, children add specificity," as in `lung_abnormality`, then `airspace_opacity`, then `air_bronchogram`. Two columns are outside the hierarchy: `category` is "an independent anatomic grouping, not part of the hierarchy," and `finding_type` "separates an observation from a diagnosis."[^lists-readme] | File | Rows | OIFM IDs filled | |---|---:|---:| @@ -32,7 +32,7 @@ No identifiers were minted during the export. Filled rows were matched by exact ## Synonyms and near-synonyms - **Finding list** is the older name for the files these replaced. -- **[Finding taxonomy](/glossary/finding-taxonomy.md) is not a [finding model](/glossary/finding-model.md).** A taxonomy row is a name and a place in a hierarchy; a finding model is a full definition with attributes. +- A [finding taxonomy](/glossary/finding-taxonomy.md) row gives a name and hierarchy position. A [finding model](/glossary/finding-model.md) defines the finding and its attributes. - The taxonomies are named after their author in the source repository. This knowledgebase calls them the MGB exam-oriented sub-taxonomies. ## Identifier form diff --git a/knowledge/glossary/fma.md b/archive/knowledge/glossary/fma.md similarity index 78% rename from knowledge/glossary/fma.md rename to archive/knowledge/glossary/fma.md index 67af5fb..d79e1c4 100644 --- a/knowledge/glossary/fma.md +++ b/archive/knowledge/glossary/fma.md @@ -4,7 +4,7 @@ title: FMA description: The Foundational Model of Anatomy, used in OIDM as a cross-reference on anatomic locations and as the basis of the location type classification. tags: [glossary, semantic-foundation, terminologies, anatomy] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fma resource: https://bioportal.bioontology.org/ontologies/FMA @@ -22,7 +22,7 @@ sources: # FMA -The Foundational Model of Anatomy, a reference ontology of human anatomy.[^fma] OIDM uses it in two ways. It is a cross-reference code on [anatomic locations](/glossary/anatomic-location.md), present on 1,643 of the 2,890 records in the curated set, the second most common system after [SNOMED CT](/glossary/snomed-ct.md).[^al-code] And its top-level organization supplies the shape of the `location_type` classification in the newer package, which maps material anatomical entity to structure, immaterial anatomical entity to space and region, body part to body_part, organ system to system, and set or collection to group.[^al-enums] +The Foundational Model of Anatomy, a reference ontology of human anatomy.[^fma] FMA cross-references appear on [anatomic locations](/glossary/anatomic-location.md), covering 1,643 of the 2,890 curated records. It is the second most common system after [SNOMED CT](/glossary/snomed-ct.md).[^al-code] Its top-level organization supplies the shape of the `location_type` classification in the newer package, which maps material anatomical entity to structure, immaterial anatomical entity to space and region, body part to body_part, organ system to system, and set or collection to group.[^al-enums] FMA is also one of the five terminologies the `molu` lookup tool spans, and the planned `anatomy` profile names FMA first, with the note that "RadLex imaging anatomy sits next to FMA."[^molu] diff --git a/knowledge/glossary/gamuts.md b/archive/knowledge/glossary/gamuts.md similarity index 79% rename from knowledge/glossary/gamuts.md rename to archive/knowledge/glossary/gamuts.md index 09272f9..4580311 100644 --- a/knowledge/glossary/gamuts.md +++ b/archive/knowledge/glossary/gamuts.md @@ -4,7 +4,7 @@ title: Gamuts description: The Radiology Gamuts Ontology, source of the largest single block of finding model definitions and of the GMTS organization code. tags: [glossary, semantic-foundation, terminologies, content] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: gamuts resource: https://gamuts.net/ @@ -22,7 +22,7 @@ sources: # Gamuts -The Radiology Gamuts Ontology, a published ontology of radiologic differential diagnosis lists, at `gamuts.net`.[^gamuts] In OIDM it plays two roles at once: it is an [index code](/glossary/index-code.md) system listed alongside SNOMED and RadLex as one of the common ontologies,[^claude-md] and it is a registered contributing organization whose [organization code](/glossary/oidm-organization-code.md) is `GMTS`.[^orgs] +The Radiology Gamuts Ontology, a published ontology of radiologic differential diagnosis lists, at `gamuts.net`.[^gamuts] OIDM uses it as an [index code](/glossary/index-code.md) system listed alongside SNOMED and RadLex as one of the common ontologies,[^claude-md] and it is a registered contributing organization whose [organization code](/glossary/oidm-organization-code.md) is `GMTS`.[^orgs] Gamuts is the single largest source of finding model content. Of the 2,382 definitions published in the corpus, 1,933 carry an `OIFM_GMTS_` identifier, credited to the Radiology Gamuts Ontology as the contributing organization. They were produced by a script that scrapes gamut pages, structures the terms with a language model, and emits them for conversion into definitions. @@ -31,7 +31,7 @@ Gamuts is the single largest source of finding model content. Of the 2,382 defin - **Radiology Gamuts Ontology** is the full name; **gamut** alone means one differential list. - **`GMTS`** is the organization code and the middle segment of the identifiers. - **`GAMUTS`** is the `system` string when a gamut concept is cited as an index code. -- **[Finding taxonomy](/glossary/finding-taxonomy.md)** is the newer content spine that is expected to supersede much of this material. +- **[Finding taxonomy](/glossary/finding-taxonomy.md)** is the newer content source expected to supersede much of this material. ## Identifier form @@ -43,7 +43,7 @@ Finding models derived from this source carry `OIFM_GMTS_######`, for example `O ## Conflicts -A gamut is a differential diagnosis list, so Gamuts-derived definitions carry diagnoses where OIDM's authoring guidance would sometimes want an observation. The open definition cleanup plan lists "gamuts reclassification" as a step that "needs design discussion," and it is the one step in that plan with no agreed approach.[^cleanup] The content roadmap expects the MGB exam-oriented sub-taxonomies to replace many of these definitions. +A gamut is a differential diagnosis list, so Gamuts-derived definitions carry diagnoses where OIDM's authoring guidance would sometimes want an observation. The open definition cleanup plan lists "gamuts reclassification" as a step that "needs design discussion," the only step without an agreed approach.[^cleanup] The content roadmap expects the MGB exam-oriented sub-taxonomies to replace many of these definitions. [^gamuts]: Radiology Gamuts Ontology [^orgs]: Base organization registry diff --git a/knowledge/glossary/imaging-diagnostic-report.md b/archive/knowledge/glossary/imaging-diagnostic-report.md similarity index 74% rename from knowledge/glossary/imaging-diagnostic-report.md rename to archive/knowledge/glossary/imaging-diagnostic-report.md index 64899cc..0112915 100644 --- a/knowledge/glossary/imaging-diagnostic-report.md +++ b/archive/knowledge/glossary/imaging-diagnostic-report.md @@ -4,7 +4,7 @@ title: Imaging Diagnostic Report (IHE IDR) description: The IHE Radiology profile that specifies how a diagnostic imaging report and its findings are encoded as FHIR resources. tags: [glossary, data-structures, fhir, ihe] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: idr-supplement resource: https://www.ihe.net/uploadedFiles/Documents/Radiology/IHE_RAD_Suppl_IDR_PhII_Rev1-2_PC_2026-03-04.pdf @@ -19,7 +19,7 @@ sources: # Imaging Diagnostic Report (IHE IDR) -The Integrating the Healthcare Enterprise Radiology profile that specifies how a diagnostic imaging report and the findings inside it are encoded as FHIR resources. Phase II went out for public comment in March 2026.[^idr-supplement] The deck names the connection from the OIDM side: an [Exam Finding List](/glossary/exam-finding-list.md) "connects to IHE Imaging Diagnostic Report (IDR) FHIR representation."[^deck] +The Integrating the Healthcare Enterprise Radiology profile that specifies how a diagnostic imaging report and the findings inside it are encoded as FHIR resources. Phase II went out for public comment in March 2026.[^idr-supplement] The deck states that an [Exam Finding List](/glossary/exam-finding-list.md) "connects to IHE Imaging Diagnostic Report (IDR) FHIR representation."[^deck] IDR's information model, "heavily influenced by modelling in SNOMED and DICOM," defines three terms OIDM must map onto. Body Structure "encompasses both anatomical structures and morphologic abnormalities (like a lesion, cyst, inflammation, aneurysm, fracture or abscess)." An imaging observation is "a feature or characteristic that is visible in an image," encoded as a FHIR `Observation`. A clinical finding is "the determination that a clinical entity is present or absent," with positive findings encoded as [FHIR Condition](/glossary/fhir-condition.md) and negative ones as Observations.[^idr-extract] @@ -41,7 +41,7 @@ None. IDR constrains FHIR resources and reuses their identifiers. ## Conflicts -IDR and OIDM disagree in three recorded places, all listed as items to raise during public comment. IDR's "finding" is the presence determination, not the named entity, and its "observation" is closer to what the next-generation vocabulary calls a data element value. IDR routes positive diagnoses to Condition while the OIDM working default is that every assertion in a report is an Observation. And IDR states that `Observation.component` "is not used," which is the mechanism every current OIDM encoding relies on.[^idr-extract] The IDR profile is not mentioned anywhere in the `imaging-problem-list` repository, so the alignment exists in the vocabulary work and the deck, not in code. +Three differences are recorded for public comment. IDR's "finding" is the presence determination, not the named entity, and its "observation" is closer to what the next-generation vocabulary calls a data element value. IDR routes positive diagnoses to Condition while the OIDM working default is that every assertion in a report is an Observation. IDR states that `Observation.component` "is not used," which is the mechanism every current OIDM encoding relies on.[^idr-extract] The `imaging-problem-list` repository does not mention IDR. Alignment is documented only in the vocabulary work and the deck. [^idr-supplement]: IHE Imaging Diagnostic Report Phase II public comment draft [^idr-extract]: IHE IDR Phase II extract diff --git a/knowledge/glossary/imaging-persona.md b/archive/knowledge/glossary/imaging-persona.md similarity index 100% rename from knowledge/glossary/imaging-persona.md rename to archive/knowledge/glossary/imaging-persona.md diff --git a/knowledge/glossary/imaging-problem-list.md b/archive/knowledge/glossary/imaging-problem-list.md similarity index 100% rename from knowledge/glossary/imaging-problem-list.md rename to archive/knowledge/glossary/imaging-problem-list.md diff --git a/knowledge/glossary/index-code.md b/archive/knowledge/glossary/index-code.md similarity index 67% rename from knowledge/glossary/index-code.md rename to archive/knowledge/glossary/index-code.md index 94eebfb..63271d1 100644 --- a/knowledge/glossary/index-code.md +++ b/archive/knowledge/glossary/index-code.md @@ -4,7 +4,7 @@ title: Index code description: A reference from a finding model, attribute, or value to a concept in a standard ontology, carried as system, code, and optional display. tags: [glossary, semantic-foundation, terminologies] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: index-code resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/oidm-common/src/oidm_common/models/index_code.py @@ -22,9 +22,9 @@ sources: # Index code -A "code representing an entry in a standard ontology, e.g., SNOMED or RadLex, which can be applied to a finding or attribute. This is used to standardize the representation of findings and attributes across different systems and to facilitate interoperability between different systems."[^index-code] The shape is three fields: `system`, `code`, and an optional `display`. +A "code representing an entry in a standard ontology, e.g., SNOMED or RadLex, which can be applied to a finding or attribute. This is used to standardize the representation of findings and attributes across different systems and to facilitate interoperability between different systems."[^index-code] The fields are `system`, `code`, and an optional `display`. -Index codes may sit on a [finding model](/glossary/finding-model.md), on an [attribute](/glossary/attribute.md), or on an individual [attribute value](/glossary/attribute-value.md). The same type also carries a finding model's `anatomic_locations`, so an [anatomic location](/glossary/anatomic-location.md) reference on a finding model is structurally an index code, usually with `system: "RADLEX"`.[^index-code] +Index codes may appear on a [finding model](/glossary/finding-model.md), on an [attribute](/glossary/attribute.md), or on an individual [attribute value](/glossary/attribute-value.md). The same type also carries a finding model's `anatomic_locations`, so an [anatomic location](/glossary/anatomic-location.md) reference on a finding model is structurally an index code, usually with `system: "RADLEX"`.[^index-code] The systems most used in the corpus are [SNOMED CT](/glossary/snomed-ct.md), [RadLex](/glossary/radlex.md), and [Gamuts](/glossary/gamuts.md), with [RadElement](/glossary/radelement.md) appearing on CDE-derived definitions.[^claude-md] @@ -32,7 +32,7 @@ The systems most used in the corpus are [SNOMED CT](/glossary/snomed-ct.md), [Ra - **Coding** in FHIR is the analogous triple of `system`, `code`, and `display`. The [CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md) pattern uses it the same way. - **Standard code** and **ontology code** are informal names for the same thing. -- **[Index code](/glossary/index-code.md) is not a crosswalk.** It asserts a reference, not an equivalence class across systems. +- An [index code](/glossary/index-code.md) references a concept without defining a crosswalk or asserting equivalence across systems. ## Identifier form @@ -44,7 +44,7 @@ None of its own. The code inside follows the referenced system, for example `RID ## Conflicts -The in-flight metadata rewrite tightens the rule to say that a canonical `index_codes` entry "must be an exact match or a clinically substitutable near-equivalent for the full model concept," sending broader, narrower, and complication-specific codes to a separate review artifact instead.[^metadata-rewrite] The corpus on `main` predates that rule. Separately, the CDE Set schema constrains an index code `system` to `RADLEX`, `SNOMEDCT`, or `LOINC` while the RadElement API does not appear to constrain it, and OIFM does not constrain it either.[^cde-set-schema] +The metadata rewrite in progress requires that an `index_codes` entry "must be an exact match or a clinically substitutable near-equivalent for the full model concept," sending broader, narrower, and complication-specific codes to a separate review artifact instead.[^metadata-rewrite] The corpus on `main` predates that rule. Separately, the CDE Set schema constrains an index code `system` to `RADLEX`, `SNOMEDCT`, or `LOINC` while the RadElement API does not appear to constrain it, and OIFM does not constrain it either.[^cde-set-schema] [^index-code]: IndexCode model in the oidm-common package [^claude-md]: findingmodels repository conventions diff --git a/knowledge/glossary/index.md b/archive/knowledge/glossary/index.md similarity index 97% rename from knowledge/glossary/index.md rename to archive/knowledge/glossary/index.md index 702284d..d0d34d6 100644 --- a/knowledge/glossary/index.md +++ b/archive/knowledge/glossary/index.md @@ -1,6 +1,6 @@ # Glossary -One term per file: a precise definition drawn from the sources, its synonyms and near-synonyms, its identifier form where it has one, where it is used, and any conflict between sources. Terms are listed alphabetically. +Each term has a sourced definition, synonyms and near-synonyms, any identifier format, usage links, and source disagreements. Entries are alphabetical, one term per file. * [Anatomic location](./anatomic-location.md) - A curated anatomic concept identified by a RadLex identifier, placed in containment and part-of hierarchies with laterality variants and cross-ontology codes. * [Anatomic scope](./anatomic-scope.md) - The eligible anatomical places, tissue types, or structure types for a definition, stated as a constraint rather than as a location. diff --git a/knowledge/glossary/laterality.md b/archive/knowledge/glossary/laterality.md similarity index 79% rename from knowledge/glossary/laterality.md rename to archive/knowledge/glossary/laterality.md index cbe160b..c557f0a 100644 --- a/knowledge/glossary/laterality.md +++ b/archive/knowledge/glossary/laterality.md @@ -4,7 +4,7 @@ title: Laterality description: The left, right, or unsided character of an anatomic structure or a finding, represented as a triad of linked variants and resolved by a stated precedence. tags: [glossary, semantic-foundation, anatomy] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: bpi resource: https://github.com/talkasab/BodyPartIndex.py/blob/388ae0a6da92f5ee4cf36620bbeeabe223938471/README.md @@ -24,7 +24,7 @@ sources: Whether a structure or a finding is left, right, or neither. The curated anatomic sets represent it structurally: "for cases where the body part is sided, the index contains three versions: an unsided version, a left-sided version, and a right-sided version. All of these are aware of each other."[^bpi] The newer package makes this an enumeration with four values, `generic` for a structure that has left and right variants, `left`, `right`, and `nonlateral` for a structure with no sides at all such as the heart or the spine.[^al-package] -Assigning laterality to a finding follows a stated precedence: explicit side in the text beats a sided exam, which beats the generic unsided structure. A non-sided exam never introduces a side. Laterality must be resolved "against the full report section, not the finding's isolated quote," because the side is often stated only in a section header or an earlier sentence, and a side is never inferred across exams or from clinical priors.[^rules] +Laterality assignment follows this precedence: explicit side in the text beats a sided exam, which beats the generic unsided structure. A non-sided exam never introduces a side. Laterality must be resolved "against the full report section, not the finding's isolated quote," because the side is often stated only in a section header or an earlier sentence, and a side is never inferred across exams or from clinical priors.[^rules] Bilateral findings split or do not split by a stated test. Discrete independent lesions per side become two findings; a single diffuse entity named as one, an "-osis" or a "disease," stays generic and unsided. The operational test is whether "you could meaningfully say the left one is larger or newer."[^rules] @@ -44,7 +44,7 @@ In the original set, sided variants take composite identifiers such as `RID294_R ## Conflicts -IHE IDR keeps laterality in a separate FHIR field on an unsided structure code, while OIDM's anatomy nodes are themselves sided. The IDR extract lists a stated mapping between the two, sided identifier and unsided identifier plus laterality, as something still needed so both directions round-trip.[^idr] A known defect in the `findingmodel` issue list also reports that laterality is assigned backwards during one database build step. +IHE IDR keeps laterality in a separate FHIR field on an unsided structure code, while OIDM's anatomy nodes are themselves sided. The IDR extract records a reversible mapping between sided identifiers and unsided identifiers with a laterality field as something still needed so both directions round-trip.[^idr] A known defect in the `findingmodel` issue list also reports that laterality is assigned backwards during one database build step. [^bpi]: BodyPartIndex.py README [^al-package]: Laterality enumeration in the anatomic-locations package diff --git a/knowledge/glossary/loinc-rsna-radiology-playbook.md b/archive/knowledge/glossary/loinc-rsna-radiology-playbook.md similarity index 82% rename from knowledge/glossary/loinc-rsna-radiology-playbook.md rename to archive/knowledge/glossary/loinc-rsna-radiology-playbook.md index edf7ddf..0256ef4 100644 --- a/knowledge/glossary/loinc-rsna-radiology-playbook.md +++ b/archive/knowledge/glossary/loinc-rsna-radiology-playbook.md @@ -4,7 +4,7 @@ title: LOINC/RSNA Radiology Playbook description: The jointly governed radiology part of LOINC that names imaging orderables along modality, anatomy, and technique axes. tags: [glossary, semantic-foundation, terminologies, exam-types] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: playbook resource: https://loinc.org/committee/radiology/ @@ -24,7 +24,7 @@ sources: The radiology portion of [LOINC](/glossary/loinc.md), developed jointly by the Regenstrief Institute and the RSNA, which names imaging orderables along axes such as modality, anatomy, and technique.[^playbook] It is "actively governed, and freely licensed for commercial and non-commercial use. The Playbook ships twice yearly under joint Regenstrief/RSNA governance, but it is a separate artifact" from [RadLex](/glossary/radlex.md).[^current-understanding] -In OIDM the Playbook is the stated basis for [exam types](/glossary/exam-type.md), and it appears only in design documents. The terminology tool's roadmap makes it "first-class in `radiology`," planning to detect both LOINC codes and legacy `RPID` Playbook identifiers, to rank orderable queries toward Playbook terms while ranking finding and anatomy queries toward RadLex, and to carry Playbook, RPID, and RadLex anatomy correspondences in crosswalk provenance.[^roadmap] The earliest statement of the idea is older still: the curated anatomic location set's roadmap calls for "a companion for exam types based on LOINC/RadLex Playbook exam definitions that specify all included body parts for the exam."[^al-site] +In OIDM the Playbook is the stated basis for [exam types](/glossary/exam-type.md), and it appears only in design documents. The terminology roadmap makes it "first-class in `radiology`," with planned detection of LOINC codes and legacy `RPID` Playbook identifiers. Orderable queries would favor Playbook terms, and finding and anatomy queries would favor RadLex. Crosswalk provenance would record Playbook, RPID, and RadLex anatomy correspondences.[^roadmap] The earliest statement of the idea is older still: the curated anatomic location set's roadmap calls for "a companion for exam types based on LOINC/RadLex Playbook exam definitions that specify all included body parts for the exam."[^al-site] ## Synonyms and near-synonyms diff --git a/knowledge/glossary/loinc.md b/archive/knowledge/glossary/loinc.md similarity index 84% rename from knowledge/glossary/loinc.md rename to archive/knowledge/glossary/loinc.md index 16e5988..e1baf15 100644 --- a/knowledge/glossary/loinc.md +++ b/archive/knowledge/glossary/loinc.md @@ -27,7 +27,7 @@ sources: Logical Observation Identifiers Names and Codes, the Regenstrief Institute's system for identifying laboratory and clinical observations, including imaging procedures.[^loinc] In OIDM, LOINC's job is [exam type](/glossary/exam-type.md) identification, not finding identification. -An [Exam Finding List](/glossary/exam-finding-list.md) carries the exam's LOINC code as `examInfo.studyLoincCode`, described as "LOINC codes: Used for exam type identification (e.g., '72133-2' = CT Abdomen and Pelvis Without Contrast)."[^ipl-claude] The specification says the structure "must also have basic information (keyed by a curated list of LOINC codes) about what exam this is."[^ipl-readme] The lineage FHIR sample codes its DiagnosticReport the same way, with `87279-6 CT Chest for Screening`.[^fhir-sample] +An [Exam Finding List](/glossary/exam-finding-list.md) carries the exam's LOINC code as `examInfo.studyLoincCode`, described as "LOINC codes: Used for exam type identification (e.g., '72133-2' = CT Abdomen and Pelvis Without Contrast)."[^ipl-claude] The imaging-problem-list README says the structure "must also have basic information (keyed by a curated list of LOINC codes) about what exam this is."[^ipl-readme] The lineage FHIR sample codes its DiagnosticReport the same way, with `87279-6 CT Chest for Screening`.[^fhir-sample] The division of labour is stated in the terminology roadmap: radiology "orderables live in the LOINC/RSNA Radiology Playbook, while findings, anatomy, and report language live in RadLex."[^roadmap] @@ -48,7 +48,7 @@ Digits, a hyphen, and a check digit, for example `72133-2`. The lookup tooling a ## Conflicts -The "curated list of LOINC codes" the specification refers to does not exist as an artifact. Exam types are documented as goals and as building blocks, not as a published catalog; see [Exam types](/roadmap/exam-types.md). +The "curated list of LOINC codes" that exam-type documents refer to does not exist as an artifact. Exam types are documented as goals and as building blocks, not as a published catalog; see [Exam types](/roadmap/exam-types.md). [^loinc]: LOINC, Regenstrief Institute [^ipl-claude]: imaging-problem-list domain model, dev branch diff --git a/knowledge/glossary/measurement.md b/archive/knowledge/glossary/measurement.md similarity index 100% rename from knowledge/glossary/measurement.md rename to archive/knowledge/glossary/measurement.md diff --git a/knowledge/glossary/observation.md b/archive/knowledge/glossary/observation.md similarity index 91% rename from knowledge/glossary/observation.md rename to archive/knowledge/glossary/observation.md index ea59576..21ed990 100644 --- a/knowledge/glossary/observation.md +++ b/archive/knowledge/glossary/observation.md @@ -4,7 +4,7 @@ title: Observation description: The atomic unit of OIDM data, one finding seen or excluded on one exam, with its anatomic location and attribute values. tags: [glossary, data-structures] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: deck resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal @@ -29,7 +29,7 @@ The atomic unit of OIDM data: one finding, seen or explicitly excluded, on one e In the current [Exam Finding List](/glossary/exam-finding-list.md) format an observation is one entry in the `findings` array, with an `observationId`, a `findingCode` and description, an `attributes` array, an optional `anatomicLocation`, and an optional verbatim `reportText`. Repeat instances are separate observations: "the same finding type may appear multiple times, for example multiple kidney stones, and each gets its own entry with a unique `observationId`."[^ipl-claude] -The FHIR lineage is direct. The 2024 reference implementation's `Observation` class states "the Observation class is the model for FHIR Observation objects," with `code`, `status`, `subject`, `bodySite`, `derivedFrom`, and a discriminated union of components.[^lineage-obs] +The 2024 reference implementation's `Observation` class states "the Observation class is the model for FHIR Observation objects," with `code`, `status`, `subject`, `bodySite`, `derivedFrom`, and a discriminated union of components.[^lineage-obs] ## Synonyms and near-synonyms diff --git a/knowledge/glossary/oidm-organization-code.md b/archive/knowledge/glossary/oidm-organization-code.md similarity index 100% rename from knowledge/glossary/oidm-organization-code.md rename to archive/knowledge/glossary/oidm-organization-code.md diff --git a/knowledge/glossary/oidm.md b/archive/knowledge/glossary/oidm.md similarity index 100% rename from knowledge/glossary/oidm.md rename to archive/knowledge/glossary/oidm.md diff --git a/knowledge/glossary/oifm.md b/archive/knowledge/glossary/oifm.md similarity index 92% rename from knowledge/glossary/oifm.md rename to archive/knowledge/glossary/oifm.md index 093dac8..6ba2bb8 100644 --- a/knowledge/glossary/oifm.md +++ b/archive/knowledge/glossary/oifm.md @@ -4,7 +4,7 @@ title: OIFM description: Open Imaging Finding Model, the OIDM specification for a finding definition, and the prefix of the identifiers that specification mints. tags: [glossary, semantic-foundation, finding-models] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fm-py resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/finding_model.py @@ -19,7 +19,7 @@ sources: # OIFM -**Open Imaging Finding Model (OIFM).** The OIDM specification that defines what a [finding model](/glossary/finding-model.md) is: a named radiology finding with a description, optional synonyms and tags, a list of characterizing [attributes](/glossary/attribute.md), optional [anatomic locations](/glossary/anatomic-location.md) and [index codes](/glossary/index-code.md), and optional [contributors](/glossary/contributor.md).[^fm-schema] The canonical statement of the format is the `FindingModelFull` Pydantic model in the `findingmodel` package.[^fm-py] +**Open Imaging Finding Model (OIFM).** The OIDM specification that defines what a [finding model](/glossary/finding-model.md) is: a named radiology finding with a description, optional synonyms and tags, a list of characterizing [attributes](/glossary/attribute.md), optional [anatomic locations](/glossary/anatomic-location.md) and [index codes](/glossary/index-code.md), and optional [contributors](/glossary/contributor.md).[^fm-schema] The format is defined by the `FindingModelFull` Pydantic model in the `findingmodel` package.[^fm-py] "OIFM" is also the literal prefix of the identifier minted for each finding model, which is why the two senses are easy to confuse. The deck expands the acronym as "Open Imaging Finding Models" when counting content, reporting nearly 3,000 definitions.[^deck] diff --git a/knowledge/glossary/open-imaging-reporting-sdk.md b/archive/knowledge/glossary/open-imaging-reporting-sdk.md similarity index 100% rename from knowledge/glossary/open-imaging-reporting-sdk.md rename to archive/knowledge/glossary/open-imaging-reporting-sdk.md diff --git a/knowledge/glossary/presence.md b/archive/knowledge/glossary/presence.md similarity index 78% rename from knowledge/glossary/presence.md rename to archive/knowledge/glossary/presence.md index 7f72679..57a1535 100644 --- a/knowledge/glossary/presence.md +++ b/archive/knowledge/glossary/presence.md @@ -4,7 +4,7 @@ title: Presence description: The near-universal first attribute of a finding model, asserting that a finding was present, absent, indeterminate, or unknown, and the dot-code convention that encodes those values. tags: [glossary, semantic-foundation, data-structures] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: overview resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/notes/oifm-overview.md @@ -27,15 +27,15 @@ sources: The assertion that a finding was present, absent, indeterminate, or unknown on an exam. Authoring guidance makes it the required first [attribute](/glossary/attribute.md) of every [finding model](/glossary/finding-model.md), with the value set "absent, present, indeterminate, unknown."[^overview] -Presence carries the project's central claim that a negative is data. "Findings must cover negative assertions. When a radiologist states 'no fracture' or 'upper abdomen is unremarkable,' that's an active observation of absence that needs to be captured... The `presence: absent` observation is clinically meaningful, it means the finding was looked for and not found."[^overview] +"Findings must cover negative assertions. When a radiologist states 'no fracture' or 'upper abdomen is unremarkable,' that's an active observation of absence that needs to be captured... The `presence: absent` observation is clinically meaningful, it means the finding was looked for and not found."[^overview] -**The dot-code convention.** Value codes are generated by appending a dot and the value's zero-based position in the attribute's value list.[^fm-py] Where the standard ordering is used, that gives `.0` absent, `.1` present, `.2` indeterminate, `.3` unknown, which is the convention consuming code documents: "`.1` = present, `.0` = absent."[^ipl-claude] Across the 2,382 published definitions, 2,261 carry a presence attribute and 2,223 of those use that ordering. +Value codes append a dot and the value's zero-based position in the attribute's value list.[^fm-py] The standard ordering gives `.0` absent, `.1` present, `.2` indeterminate, `.3` unknown, which is the convention consuming code documents: "`.1` = present, `.0` = absent."[^ipl-claude] Across the 2,382 published definitions, 2,261 carry a presence attribute and 2,223 of those use that ordering. ## Synonyms and near-synonyms - **Presence or absence** and **present/absent** describe the same attribute. - **Detected** is IHE IDR's value for the same assertion.[^idr] -- **Indeterminate** means the exam could not determine it; **unknown** means it was not assessed. The two are distinct values, not spellings of one. +- **Indeterminate** means the exam could not determine it; **unknown** means it was not assessed. These are distinct values. - **[Change from prior](/glossary/change-from-prior.md)** is the companion second attribute, not part of presence. ## Identifier form diff --git a/knowledge/glossary/provenance.md b/archive/knowledge/glossary/provenance.md similarity index 71% rename from knowledge/glossary/provenance.md rename to archive/knowledge/glossary/provenance.md index ff4e10f..5fe5ecf 100644 --- a/knowledge/glossary/provenance.md +++ b/archive/knowledge/glossary/provenance.md @@ -4,7 +4,7 @@ title: Provenance description: The record of where an observation came from, a radiologist, an AI tool, or a language model reading a report, and of the text that supports it. tags: [glossary, data-structures, extraction] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: ipl-readme resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/README.md @@ -25,15 +25,15 @@ sources: # Provenance -The record of where an [observation](/glossary/observation.md) came from. The [Exam Finding List](/glossary/exam-finding-list.md) specification states the requirement directly: findings "can be generated by LLM from an existing report or generated live during exam time; each Observation should include some kind of provenance marker."[^ipl-readme] +The record of where an [observation](/glossary/observation.md) came from. The [Exam Finding List](/glossary/exam-finding-list.md) specification requires a marker: findings "can be generated by LLM from an existing report or generated live during exam time; each Observation should include some kind of provenance marker."[^ipl-readme] -Provenance matters because sources are not interchangeable. The deck lists AI validation as a stated ACR priority, specifically the ability to "correlate AI vs radiologist Observations," which is only possible if the two are distinguishable.[^deck] The lineage FHIR samples make the distinction with FHIR `status`: an AI-generated finding carries `preliminary` and the radiologist's carries `final`, with otherwise identical structure.[^fhir-ai] +The deck lists AI validation as a stated ACR priority, requiring the ability to "correlate AI vs radiologist Observations," which is only possible if the two are distinguishable.[^deck] The lineage FHIR samples make the distinction with FHIR `status`: an AI-generated finding carries `preliminary` and the radiologist's carries `final`, with otherwise identical structure.[^fhir-ai] -A second kind of provenance operates within a report. Every extracted finding carries a verbatim `report_text` quote, validated as verbatim rather than paraphrased, so a consumer can see the sentence the structure came from.[^extraction-plan] That quote is what the reviewing tools display and what the evaluation design keys on. +A second kind of provenance operates within a report. Each extracted finding carries a `report_text` quote, validated as verbatim rather than paraphrased, identifying its source sentence.[^extraction-plan] That quote is what the reviewing tools display and what the evaluation design keys on. ## Synonyms and near-synonyms -- **Provenance marker** is the specification's phrase for the field. +- **Provenance marker** is the imaging-problem-list README's phrase for the field. - **Source** and **attribution** are informal equivalents. - **`status`** in FHIR is the field the lineage samples use to carry it. - **Verbatim quote** is evidence for one observation, not a statement of who produced it. diff --git a/knowledge/glossary/radelement.md b/archive/knowledge/glossary/radelement.md similarity index 82% rename from knowledge/glossary/radelement.md rename to archive/knowledge/glossary/radelement.md index 99af3ed..35f1b23 100644 --- a/knowledge/glossary/radelement.md +++ b/archive/knowledge/glossary/radelement.md @@ -4,7 +4,7 @@ title: RadElement description: The ACR and RSNA registry that publishes common data element sets and elements, and the coding system their identifiers belong to. tags: [glossary, semantic-foundation, terminologies, cde] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: cde-repo resource: https://github.com/openimagingdata/common_data_elements/blob/35536d8c858bcd33e730a00c919edaef2e310a0b/README.md @@ -42,7 +42,7 @@ RadElement is downstream of authoring. Informal definitions are drafted in `CDES ## Conflicts -There is no settled coding-system URI for RadElement codes. IHE IDR asks the question directly, "What is the Coding System identifier for Radelement codes?", and the next-generation work records it as open.[^idr] In practice three forms are in use: `https://radelement.org` in FHIR samples, `RADELEMENT` as an index code system string, and a proposed `RDE2` base in the next-generation notes. Separately, RadElement coverage was found missing from ontology search during metadata enrichment, which made `index_codes` the weakest-scoring field in that pipeline.[^index-fixture] +There is no settled coding-system URI for RadElement codes. IHE IDR asks, "What is the Coding System identifier for Radelement codes?", and the next-generation work records it as open.[^idr] Three forms appear in the sources: `https://radelement.org` in FHIR samples, `RADELEMENT` as an index code system string, and a proposed `RDE2` base in the next-generation notes. Separately, RadElement coverage was found missing from ontology search during metadata enrichment, which made `index_codes` the weakest-scoring field in that pipeline.[^index-fixture] [^cde-repo]: common_data_elements repository README [^radelement]: RadElement registry diff --git a/knowledge/glossary/radlex-id.md b/archive/knowledge/glossary/radlex-id.md similarity index 100% rename from knowledge/glossary/radlex-id.md rename to archive/knowledge/glossary/radlex-id.md diff --git a/knowledge/glossary/radlex.md b/archive/knowledge/glossary/radlex.md similarity index 97% rename from knowledge/glossary/radlex.md rename to archive/knowledge/glossary/radlex.md index 5aa5bfb..f03ab33 100644 --- a/knowledge/glossary/radlex.md +++ b/archive/knowledge/glossary/radlex.md @@ -4,7 +4,7 @@ title: RadLex description: The RSNA radiology lexicon, the ontology that supplies identifiers for anatomic locations and many finding and attribute codes. tags: [glossary, semantic-foundation, terminologies] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: radlex resource: https://radlex.org/ diff --git a/knowledge/glossary/snomed-ct.md b/archive/knowledge/glossary/snomed-ct.md similarity index 68% rename from knowledge/glossary/snomed-ct.md rename to archive/knowledge/glossary/snomed-ct.md index 21aded2..198fa42 100644 --- a/knowledge/glossary/snomed-ct.md +++ b/archive/knowledge/glossary/snomed-ct.md @@ -4,7 +4,7 @@ title: SNOMED CT description: The clinical terminology used in OIDM as a secondary coding system on findings, attribute values, and anatomic locations. tags: [glossary, semantic-foundation, terminologies] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: snomed resource: https://www.snomed.org/ @@ -25,7 +25,7 @@ sources: # SNOMED CT -The Systematized Nomenclature of Medicine Clinical Terms, the general clinical terminology.[^snomed] In OIDM it is a secondary coding system rather than a primary key. It appears in three places: as an [index code](/glossary/index-code.md) system on [finding models](/glossary/finding-model.md), [attributes](/glossary/attribute.md), and [attribute values](/glossary/attribute-value.md), listed alongside [RadLex](/glossary/radlex.md) and [Gamuts](/glossary/gamuts.md) as one of the common ontologies;[^claude-md] as a cross-reference on [anatomic locations](/glossary/anatomic-location.md), present on 1,732 of the 2,890 records in the curated set;[^al-code] and in the FHIR lineage samples as the code system for a report's category and its conclusion codes.[^fhir-sample] +The Systematized Nomenclature of Medicine Clinical Terms, the general clinical terminology.[^snomed] OIDM uses it for secondary codes, not primary keys. Alongside [RadLex](/glossary/radlex.md) and [Gamuts](/glossary/gamuts.md), it supplies [index codes](/glossary/index-code.md) on [finding models](/glossary/finding-model.md), [attributes](/glossary/attribute.md), and [attribute values](/glossary/attribute-value.md).[^claude-md] SNOMED CT cross-references appear on 1,732 of 2,890 curated [anatomic locations](/glossary/anatomic-location.md).[^al-code] FHIR lineage samples use it for report category and conclusion codes.[^fhir-sample] Typical use on a value is the qualifier hierarchy, for example `52101004` for Present and `2667000` for Absent, paired with the equivalent RadLex codes on the same value. @@ -45,7 +45,7 @@ A numeric concept identifier, for example `23043003` for uterine adnexa. ## Conflicts -SNOMED CT is licensed, which is why the lookup tooling treats it as a credentialed backend and keeps licensed behavior opt-in and visible rather than implicit.[^molu] The curated anatomic set also records "complete SNOMED identification" as unfinished roadmap work, so SNOMED coverage of anatomic locations is partial by design. +SNOMED CT requires a licence. The lookup tooling keeps licensed behavior opt-in and visible rather than implicit.[^molu] The curated anatomic set also records "complete SNOMED identification" as unfinished roadmap work, so SNOMED coverage of anatomic locations is partial by design. [^snomed]: SNOMED International [^molu]: med-ontology-lookup README diff --git a/knowledge/glossary/tag.md b/archive/knowledge/glossary/tag.md similarity index 84% rename from knowledge/glossary/tag.md rename to archive/knowledge/glossary/tag.md index c79c5dc..0a09e80 100644 --- a/knowledge/glossary/tag.md +++ b/archive/knowledge/glossary/tag.md @@ -4,7 +4,7 @@ title: Tag description: A free-text clinical category attached to a finding model for organization and retrieval, distinct from the typed structured metadata that is replacing it. tags: [glossary, semantic-foundation, finding-models] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fm-py resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/finding_model.py @@ -21,7 +21,7 @@ sources: "Tags that might be used to categorize the finding among other findings."[^fm-py] Tags are an optional list of free-text strings on a [finding model](/glossary/finding-model.md). The authoring guidance describes them as "clinical categories (anatomy, modality, etiology) for organization and retrieval."[^overview] -Tags are untyped and unvalidated. A real definition in the corpus carries `["ultrasound", "CT", "US", "XR", "urological", "obstructive", "diagnosis"]` on one finding, mixing modality abbreviations, two spellings of the same modality, a subspecialty, an etiology, and an entity type in one list. That looseness is the reason the metadata rewrite exists. +Tags are untyped and unvalidated. A real definition in the corpus carries `["ultrasound", "CT", "US", "XR", "urological", "obstructive", "diagnosis"]`, mixing modality abbreviations, two spellings of the same modality, a subspecialty, an etiology, and an entity type in one list. The metadata rewrite addresses this inconsistency. ## Synonyms and near-synonyms @@ -40,7 +40,7 @@ None. Tags are bare strings with no registry. ## Conflicts -Tags and the new typed fields overlap: a tag reading `CT` and an `applicable_modalities` entry of `CT` say the same thing in two places. The rewrite states that structured metadata should be canonical model state rather than sidecar output, but it does not say what becomes of the existing tag lists, and nothing has landed on `main`.[^metadata-rewrite] +Tags and the new typed fields overlap: a tag reading `CT` and an `applicable_modalities` entry of `CT` say the same thing in two places. The rewrite specifies that structured metadata should be the model's own state rather than sidecar output, but it does not say what becomes of the existing tag lists, and nothing has landed on `main`.[^metadata-rewrite] [^fm-py]: TagSequence field description in the findingmodel package [^overview]: "Finding Models: Overview" diff --git a/knowledge/glossary/technical-finding.md b/archive/knowledge/glossary/technical-finding.md similarity index 91% rename from knowledge/glossary/technical-finding.md rename to archive/knowledge/glossary/technical-finding.md index c383e5a..e319c2e 100644 --- a/knowledge/glossary/technical-finding.md +++ b/archive/knowledge/glossary/technical-finding.md @@ -4,7 +4,7 @@ title: Technical finding description: A finding stated in modality-specific imaging language, such as a density or signal characteristic, without committing to an underlying diagnosis. tags: [glossary, semantic-foundation, data-structures] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: tech resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/technical-imaging-findings.md @@ -23,7 +23,7 @@ A finding stated in modality-specific imaging language. "'Technical findings' de The reference catalogs them by modality: CT attenuation terms such as hypodense lesion and ground-glass opacity; MR signal terms such as diffusion restriction and marrow signal abnormality; enhancement and opacification patterns such as washout and filling defect; ultrasound echogenicity terms such as anechoic structure and posterior acoustic shadowing; and nuclear medicine terms such as FDG-avid lesion and photopenic area.[^tech] -The distinction that matters for coding is observation versus interpretation. "The same imaging appearance can be described at observation level ('T2 hyperintense marrow signal') or interpretation level ('marrow edema'). Reports often mix both. The ontology needs entries at the observation level to capture what's actually seen." The guidance is specific: "marrow signal abnormality should be searched as exactly that, it is the observation. Do NOT reinterpret as 'bone marrow edema' or 'marrow infiltration', which are specific diagnoses that may or may not be the cause."[^tech] +The distinction that matters for coding is observation versus interpretation. "The same imaging appearance can be described at observation level ('T2 hyperintense marrow signal') or interpretation level ('marrow edema'). Reports often mix both. The ontology needs entries at the observation level to capture what's actually seen." The guidance states: "marrow signal abnormality should be searched as exactly that, it is the observation. Do NOT reinterpret as 'bone marrow edema' or 'marrow infiltration', which are specific diagnoses that may or may not be the cause."[^tech] ## Synonyms and near-synonyms diff --git a/knowledge/glossary/umls.md b/archive/knowledge/glossary/umls.md similarity index 60% rename from knowledge/glossary/umls.md rename to archive/knowledge/glossary/umls.md index eca1749..beb6621 100644 --- a/knowledge/glossary/umls.md +++ b/archive/knowledge/glossary/umls.md @@ -4,7 +4,7 @@ title: UMLS description: The Unified Medical Language System, used in OIDM as the hub for translating identifiers between terminologies and as a cross-reference on anatomic locations. tags: [glossary, semantic-foundation, terminologies] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: umls resource: https://www.nlm.nih.gov/research/umls/index.html @@ -22,11 +22,11 @@ sources: # UMLS -The Unified Medical Language System, the US National Library of Medicine's integration of many source vocabularies under shared concept identifiers.[^umls] OIDM uses it for two jobs. +The Unified Medical Language System, the US National Library of Medicine's integration of many source vocabularies under shared concept identifiers.[^umls] -The first is crosswalking. The `molu` lookup tool resolves a concept to its UMLS concept unique identifiers and then translates to codes in other vocabularies; the stated design makes "UMLS as the CUI hub" part of the default radiology profile, alongside [RadLex](/glossary/radlex.md), [LOINC](/glossary/loinc.md), [SNOMED CT](/glossary/snomed-ct.md), and [FMA](/glossary/fma.md).[^roadmap][^molu] The roadmap is explicit that a crosswalk is not an equivalence: translation should preserve "mapping direction, scope, provenance, and strength instead of treating every cross-reference as equivalence."[^roadmap] +The `molu` lookup tool resolves a concept to its UMLS concept unique identifiers and then translates to codes in other vocabularies. The design makes "UMLS as the CUI hub" part of the default radiology profile, alongside [RadLex](/glossary/radlex.md), [LOINC](/glossary/loinc.md), [SNOMED CT](/glossary/snomed-ct.md), and [FMA](/glossary/fma.md).[^roadmap][^molu] The roadmap is explicit that a crosswalk is not an equivalence: translation should preserve "mapping direction, scope, provenance, and strength instead of treating every cross-reference as equivalence."[^roadmap] -The second is as a cross-reference code on [anatomic locations](/glossary/anatomic-location.md), present on 578 of the 2,890 records in the curated set.[^al-code] +UMLS also supplies cross-reference codes on [anatomic locations](/glossary/anatomic-location.md), present on 578 of the 2,890 records in the curated set.[^al-code] ## Synonyms and near-synonyms @@ -45,7 +45,7 @@ The second is as a cross-reference code on [anatomic locations](/glossary/anatom ## Conflicts -UMLS requires a licence and an API key, so any OIDM tooling that depends on it fails differently for an unlicensed caller. The lookup tool's typed failure model exists partly for this reason, separating absence of a result from authentication, authorization, and licensing failures so the latter stay visible rather than silently falling back.[^molu] A tracked issue also records that co-occurring CUI ambiguity is currently lost rather than preserved. +UMLS requires a licence and API key. The lookup tool's typed failure model exists partly for this reason, separating absence of a result from authentication, authorization, and licensing failures, so the latter stay visible rather than silently falling back.[^molu] A tracked issue also records that co-occurring CUI ambiguity is currently lost rather than preserved. [^umls]: Unified Medical Language System [^molu]: med-ontology-lookup README diff --git a/knowledge/guides/authoring-guide.md b/archive/knowledge/guides/authoring-guide.md similarity index 70% rename from knowledge/guides/authoring-guide.md rename to archive/knowledge/guides/authoring-guide.md index ce8e093..53f6a1b 100644 --- a/knowledge/guides/authoring-guide.md +++ b/archive/knowledge/guides/authoring-guide.md @@ -4,7 +4,7 @@ title: Authoring guide description: How humans and agents write, migrate, link, and verify documents in this knowledgebase. tags: [meta, conventions, okf] status: draft -generated: { by: claude-fable-5-1/claude-code, at: 2026-09-21T01:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: okf-spec resource: https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/SPEC.md @@ -16,9 +16,9 @@ sources: # Overview -This repository is the canonical, public, high-level documentation for the Open Imaging Data Model (OIDM). It is an Open Knowledge Format (OKF) 0.2 bundle: a directory of markdown files with YAML frontmatter that both people and agents read.[^okf-spec] The bundle lives in `knowledge/`. Everything in this guide is enforced by two checkers described at the end. +This repository holds the canonical public, high-level documentation for the Open Imaging Data Model (OIDM). The `knowledge/` directory is an Open Knowledge Format (OKF) 0.2 bundle of Markdown files with YAML frontmatter for people and agents.[^okf-spec] Everything in this guide is enforced by two checkers described at the end. -Three rules summarize the house style: +Follow these rules: 1. One concept per file, typed, described, and sourced. 2. Facts and stated goals only. Proposals belong to a separate workstream and are marked as such when they arrive. @@ -40,13 +40,14 @@ Three rules summarize the house style: | `guides/` | This guide and the migration ledger | | `plans/` | Plans for building and maintaining the knowledgebase | -Every directory has an `index.md` that lists each concept and subdirectory in it, one entry per line. Index entries use the `./` prefix so the site's absolute link resolution keeps them inside the directory: +Each directory needs an `index.md` listing its concepts and subdirectories, one per line. Index entries use the `./` prefix so the site's absolute link resolution keeps them inside the directory: ```markdown * [Title](./file.md) - description * [Subdirectory](./subdir/) - description ``` - Index files carry no frontmatter, except the bundle root, which declares `okf_version`. `log.md` at the root records changes newest first under `## YYYY-MM-DD` headings. + +Only the bundle-root index has frontmatter, declaring `okf_version`. Record changes newest first in root `log.md`, under `## YYYY-MM-DD` headings. # Frontmatter @@ -69,7 +70,7 @@ sources: --- ``` -**Types.** `type` must be one of the controlled vocabulary below. Inherited from the ACR-RSNA-CDEs next-gen bundle:[^cde-checker] Reference, Analysis, Decision Record, Proposal, Playbook, Worked Example, Presentation Extract, Meeting Notes, Draft Specification, Gap Log, Exploration. Added here: Overview, Concept, Glossary Term, Format Specification, Data Structure, Project Profile, Guide, Roadmap, History, Source Extract, Plan. Proposal and Decision Record are reserved for the proposals workstream. +**Types.** `type` must be one of the controlled vocabulary below. It inherits Reference, Analysis, Decision Record, Proposal, Playbook, Worked Example, Presentation Extract, Meeting Notes, Draft Specification, Gap Log, and Exploration from the ACR-RSNA-CDEs next-gen bundle.[^cde-checker] This repository adds Overview, Concept, Glossary Term, Format Specification, Data Structure, Project Profile, Guide, Roadmap, History, Source Extract, and Plan. Reserve Proposal and Decision Record for the proposals workstream. | Type | Use it for | |---|---| @@ -88,11 +89,11 @@ sources: | Worked Example | A concrete example with real data | | Plan | A plan for work on this knowledgebase | -**Status and trust.** Every agent-written document starts as `status: draft`. When the project lead reviews it, they add `verified: [{ by: human:talkasab, at: }]` and set `status: stable`. Deprecated documents keep their content, get `status: deprecated`, and are noted in `log.md`. Project profiles and roadmap documents set `stale_after` one year out. +**Status and trust.** Start agent-written documents as `status: draft`. After review, the project lead adds `verified: [{ by: human:talkasab, at: }]` and sets `status: stable`. Keep deprecated content, set `status: deprecated`, and note it in `log.md`. Set `stale_after` one year out for project profiles and roadmaps. -**Actors.** `generated.by` and `verified[].by` follow the OKF actor convention: `/` for an agent or tool, `human:` for a person, `process:` for automation. Use the `human:` prefix whenever a person authored or signed off; trust tiers key off it. +**Actors.** Use `/` for agents or tools, `human:` for people, and `process:` for automation in `generated.by` and `verified[].by`, following the OKF actor convention. Use the `human:` prefix whenever a person authored or signed off; trust tiers key off it. `generated` names the actor that last wrote the document's text; a copy-editing pass by another actor updates it, and the pass is recorded once in log.md. -**Sources.** List what you actually read. Pin GitHub links to a commit, not a branch, so a reader can tell what version was read. Attribute a specific claim in the body with a footnote whose label matches a source `id`, as this guide does. A migrated document lists its origin as its first source. +**Sources.** List what you actually read. Pin GitHub links to a commit, not a branch, so a reader can tell what version was read. Attribute claims with footnote labels matching source `id` values. Put a migrated document's origin first. # Writing @@ -108,11 +109,11 @@ sources: ``` - Link to code and content by commit-pinned GitHub URL, and to deployed tools by their live URL. -- Prefer short sections, tables for field lists, and fenced code for examples. No empty headings. Balanced code fences. +- Use short sections, tables for field lists, and fenced code for examples. Keep headings nonempty and code fences balanced. # Migrating a document from a working repository -1. Confirm it is documentation, not code documentation. Install steps, API usage, and developer workflow stay with the code. +1. Confirm it is documentation, not code documentation. Keep installation, API usage, and developer workflow with the code. 2. Copy the substance. Rewrite only for consistency of terms and to remove individual names. Keep the author's structure. 3. Add frontmatter: an appropriate type, `status: draft`, `generated` naming you, and `sources` whose first entry is the origin file at its commit. 4. Add glossary links on first use of terms, and links to related concepts. @@ -129,7 +130,7 @@ uv run .agents/skills/validate/scripts/okf_validate.py knowledge --strict uv run tools/check_bundle.py ``` -The first checks OKF 0.2 conformance and warns on legacy fields. The second enforces this guide: type vocabulary, required frontmatter, trust-signal shape, index coverage for every directory, link resolution, balanced fences, and a sweep for email addresses and denylisted names. The denylist lives at `tools/denylist.txt`, is ignored by git, and is maintained by the project lead. Continuous integration runs both on every push and pull request. +The first checks OKF 0.2 conformance and warns on legacy fields. The second enforces this guide: type vocabulary, required frontmatter, trust-signal shape, index coverage for every directory, link resolution, balanced fences, and a sweep for email addresses and denylisted names. The project lead maintains the gitignored `tools/denylist.txt`. Continuous integration runs both on pushes to `main` and pull requests. [^okf-spec]: Open Knowledge Format specification, version 0.2 [^cde-checker]: Bundle checker from the ACR-RSNA-CDEs next-gen-2026 branch diff --git a/knowledge/guides/index.md b/archive/knowledge/guides/index.md similarity index 100% rename from knowledge/guides/index.md rename to archive/knowledge/guides/index.md diff --git a/knowledge/guides/migration-ledger.md b/archive/knowledge/guides/migration-ledger.md similarity index 99% rename from knowledge/guides/migration-ledger.md rename to archive/knowledge/guides/migration-ledger.md index c927d5e..b83a7af 100644 --- a/knowledge/guides/migration-ledger.md +++ b/archive/knowledge/guides/migration-ledger.md @@ -4,12 +4,12 @@ title: Migration ledger description: Every document migrated into this knowledgebase from a working repository, with its origin, commit, and destination. tags: [meta, migration] status: draft -generated: { by: claude-fable-5-1/claude-code, at: 2026-09-21T01:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } --- # Overview -This ledger records each document whose substance moved from a working repository into this bundle. It is the work list for the follow-up round in which working repositories replace migrated documents with links here. Add a row whenever you migrate, per the [authoring guide](/guides/authoring-guide.md). +This ledger records each document whose substance moved from a working repository into this bundle. It is the work list for the follow-up round in which working repositories replace migrated documents with links here. Add a row whenever you migrate, following the [authoring guide](/guides/authoring-guide.md). # Ledger diff --git a/knowledge/history/cde-template-rendering.md b/archive/knowledge/history/cde-template-rendering.md similarity index 50% rename from knowledge/history/cde-template-rendering.md rename to archive/knowledge/history/cde-template-rendering.md index 34ddd47..5774619 100644 --- a/knowledge/history/cde-template-rendering.md +++ b/archive/knowledge/history/cde-template-rendering.md @@ -1,10 +1,10 @@ --- type: History title: CDE template rendering -description: The 2023 CDETemplateDemo pattern that turned a CDE-labeled Observation into report prose through a Mustache template, and how it relates to the reporting SDK direction. +description: How CDETemplateDemo rendered structured Observations through Mustache templates in 2023, a precedent for the proposed reporting SDK. tags: [history, reporting, observation, cde] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T02:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: mapper resource: https://github.com/openimagingdata/CDETemplateDemo/blob/e09f2553de58b2c88ea26e9673750a62803d4dbc/mapper-logic/src/mappers/obsToMustache.ts @@ -28,15 +28,15 @@ sources: # What it was -`CDETemplateDemo` is a small full-stack demonstration written in 2023 and untouched since 2023-06-15. Its whole purpose was to show that structured data can be turned back into readable report prose without a language model, by rendering a [CDE](/glossary/cde.md)-labeled [Observation](/glossary/observation.md) through a text template. The demonstration was shown at SIIM in June 2023 as a web service that takes a radiology finding encoded as a FHIR Observation with CDE tags and generates customizable prose.[^siim-post] +`CDETemplateDemo` renders a [CDE](/glossary/cde.md)-labeled [Observation](/glossary/observation.md) as report prose through a text template, without a language model. Written in 2023 and unchanged since 2023-06-15, it was shown at SIIM in June 2023 as a web service that generated customizable prose from FHIR Observations.[^siim-post] -It is the earliest working structured-data-to-text generator in the project, and the direction it points is still live. See [lineage repositories](/history/lineage-repositories.md) for the repository's status. +It is the project's earliest working generator of prose from structured data. See [lineage repositories](/history/lineage-repositories.md) for the repository's status. # The pattern in three parts **The Observation.** A sample pneumothorax Observation carries `code` as the RadElement set code RDES44, a `bodySite` coded against the anatomic locations system, and a `component` array whose entries each carry an element code and a value.[^observation] Six components appear: lung tissue collapse, presence, pleural separation in millimeters, side, size, and associated chest tube, plus signs of tension. -**The mapper.** `obsToMustache.ts` flattens the Observation's components into a plain key-value dictionary keyed by the component display name.[^mapper] It then does two things that matter. It sets a boolean flag named after each value, so that any value can drive a template section. And it special-cases the presence component, deriving two explicit flags, one true when presence is present and one true when it is absent. +**The mapper.** `obsToMustache.ts` flattens the Observation's components into a plain key-value dictionary keyed by the component display name.[^mapper] It sets a boolean flag for each value to control template sections. Presence has two flags, one for present and one for absent. ```typescript if (key.toLowerCase() === "presence") { @@ -64,21 +64,21 @@ There is no pneumothorax. {{/presence_absent}} ``` -Rendered against the sample Observation, that produces: "There is a Medium Left pneumothorax (pleural separation: 28 mm). There is Partial lung tissue collapse." The absent branch would instead produce a single sentence, which is exactly the shape the negative-statement problem in [extraction approaches](/history/extraction-approaches.md) works in the opposite direction. +Rendered against the sample Observation, that produces: "There is a Medium Left pneumothorax (pleural separation: 28 mm). There is Partial lung tissue collapse." The absent branch produces a single sentence. [Extraction approaches](/history/extraction-approaches.md) describes the reverse task of extracting explicit negatives. # What the demo also shows by accident -The Observation class inside this repository is not the FHIR one. It models components as a flat record of string keys to string values, with the code and body site reduced to an identifier and a display string.[^obs-model] The sample JSON files on disk are full FHIR Observations, so the demo reads FHIR and works internally with something simpler. That divergence is why the repository is recorded as superseded: the FHIR-faithful Observation in `OpenImagingDataModel.py` became the reference shape, and the flat variant did not carry forward. The flattening itself did, as a rendering step rather than a data model. +The internal Observation class stores components as string key-value pairs and reduces the code and body site to an identifier and display string.[^obs-model] The sample files contain full FHIR Observations. The FHIR-based Observation in `OpenImagingDataModel.py` superseded this simplified internal model. Flattening remained a rendering step. -The mapper also has a hardcoded assumption worth naming: presence is the only attribute given special treatment, and every other value becomes a bare boolean flag. That works because presence is the one attribute the current [finding model format](/semantic-foundation/finding-models/finding-model-format.md) also treats as near-universal. +Only presence receives special treatment. Other values become boolean flags. Presence is also near-universal in the current [finding model format](/semantic-foundation/finding-models/finding-model-format.md). # Relation to the reporting SDK idea -The January 2026 status deck names an Open Imaging Reporting SDK under its applications pillar, positioned as the vendor-facing surface through which reporting tools consume OIDM structures.[^deck] No such artifact exists; see [the reporting SDK](/applications/reporting-sdk.md), which records it as stated direction only. +The January 2026 status deck names an Open Imaging Reporting SDK under its applications pillar, for vendors to use OIDM structures in reporting tools.[^deck] No such artifact exists; see [the reporting SDK](/applications/reporting-sdk.md), which records it as stated direction only. -This demonstration is the concrete precedent for one half of what such an SDK would have to do. The 2023 next-generation reporting assistance framework post described the other half, a plugin container in which assistance scripts run against standardized report context and can insert generated text into a report. Rendering a structured observation into a sentence is the operation those scripts would call; see [site articles](/history/site-articles.md) for that post. +The 2023 next-generation reporting assistance framework post described a plugin container whose scripts inspect standard report context and insert generated text. This demo provides a precedent for that rendering operation. See [site articles](/history/site-articles.md). -Three facts about the demonstration are worth carrying into any later work on this. The template is per-CDE-set, stored under a directory named for the set identifier, so the unit of authoring is the finding rather than the report. The template is data, not code, so a site can maintain its own phrasing without touching the renderer. And the renderer itself is about thirty lines, because all the semantics live in the codes. +Each CDE set has a template in a directory named for its identifier, so templates describe findings rather than reports. A site can change template wording without modifying the renderer. The renderer is about thirty lines because the codes carry the semantics. [^mapper]: CDETemplateDemo obsToMustache.ts, the Observation to template-data mapper [^template]: CDETemplateDemo pneumothorax Mustache template diff --git a/knowledge/history/extraction-approaches.md b/archive/knowledge/history/extraction-approaches.md similarity index 58% rename from knowledge/history/extraction-approaches.md rename to archive/knowledge/history/extraction-approaches.md index 22c81c0..b661180 100644 --- a/knowledge/history/extraction-approaches.md +++ b/archive/knowledge/history/extraction-approaches.md @@ -1,10 +1,10 @@ --- type: History title: Extraction approaches -description: Seven successive attempts at turning radiology report text into structured findings, from a template corpus in 2024 to the current extraction platform, and what each one established. +description: Seven report-extraction approaches, their results, and their contributions to the current platform. tags: [history, extraction, llm, findings] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T02:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: template-extraction resource: https://github.com/openimagingdata/template_extraction/tree/4d947d7e7557ce6187a35d592f8199d3abb673cf/templates @@ -43,31 +43,31 @@ sources: # The problem, restated each time -Every one of these efforts answers the same question: given free-text radiology report language, produce structured findings that carry standard codes. They differ in what they start from, what they produce, and how they judge whether the result is right. Read in order, they converge on three conclusions that the current platform treats as settled: extraction must work on real report prose rather than templates, it must produce explicit negatives as well as positives, and it must be judged on faithfulness to the source text rather than on how many findings it emits. +These efforts seek structured, coded findings from radiology reports. Their inputs, outputs, and evaluation methods differ. The current platform extracts from real report prose, includes explicit negatives, and evaluates faithfulness to the source text. # 1. Templates as the source, 2024 to 2025 **SARTemplatesToCDEs** (2023-12 to 2024-03) proposed using a language model to convert Society of Abdominal Radiology reporting templates into CDE sets, starting with rectal cancer staging.[^sar] No conversion code was committed. Its lasting artifact is a hand-written itemized redesign of one CDE set; see [lineage repositories](/history/lineage-repositories.md). -**template_extraction** (2025-06) took the same starting point at scale, accumulating 281 RadReport templates spanning CT, MR, ultrasound, nuclear medicine, and fluoroscopy across neuro, musculoskeletal, chest, abdomen, genitourinary, and cardiac imaging, plus oncology staging templates and two CDE-labeled test templates.[^template-extraction] The script that reads them is short. The corpus is the asset, and it remains unused: it is a candidate vocabulary for [exam types](/semantic-foundation/exam-types/overview.md) as much as for findings, since the templates are named by exam. +**template_extraction** (2025-06) took the same starting point at scale, accumulating 281 RadReport templates spanning CT, MR, ultrasound, nuclear medicine, and fluoroscopy across neuro, musculoskeletal, chest, abdomen, genitourinary, and cardiac imaging, plus oncology staging templates and two CDE-labeled test templates.[^template-extraction] A short script reads the corpus. It remains unused but could supply vocabulary for findings and [exam types](/semantic-foundation/exam-types/overview.md), since templates are named by exam. -What this line established: templates give you the vocabulary a specialty society has agreed on, but not the language radiologists actually dictate, and not the negatives. +Templates supply agreed specialty vocabulary but omit dictated language and negatives. # 2. Ontologies as the source, 2025 **get_ontology_findings** (2025-03) inverted the direction: look through an existing ontology for entries that correspond to imaging findings, so they can be turned into finding models or common data elements.[^get-ontology] It carries a RadLex export and a minimal script. Its narrow single-ontology scope was widened by `med-ontology-lookup`; see [that project profile](/semantic-foundation/terminologies/med-ontology-lookup.md). -What this line established: an ontology tells you what a finding could be called, not which findings are worth modeling or how they are characterized. +Ontologies supply finding names without deciding which findings to model or how to characterize them. # 3. Real report text, retrieval-augmented, 2024 to 2025 -**ReportFindingRefiner** (2024-12 to 2025-05) is the first pipeline built on real report prose.[^refiner] Its architecture is a privacy-first, locally hosted chain: ingest plain-text reports, split each into header, findings, and impression sections, embed the sections with sentence transformers, store them in a local vector database, expose basic, vector, and hybrid search over them, then use a locally run language model to generate a finding model outline from retrieved context and persist it. It separates a search service, a report service, a language model service, and a finding model service, and exposes each step as its own command-line script, including one that generates a finding outline with retrieved context and one without, so the two can be compared. +**ReportFindingRefiner** (2024-12 to 2025-05) is the first pipeline built on real report prose.[^refiner] It processes reports locally for privacy. The pipeline splits plain text into header, findings, and impression sections, embeds them with sentence transformers, and stores them in a local vector database. Basic, vector, and hybrid searches retrieve context for a local language model to generate and store finding model outlines. Separate search, report, language model, and finding model services have command-line scripts for each step. Two scripts compare outline generation with and without retrieved context. -What this line established: retrieval over a real corpus produces better finding definitions than a template does, local models make report text usable without sending it anywhere, and generating a definition is a different task from extracting an instance. +Retrieval over real reports produced better definitions than templates, while local models kept report text private. Definition generation remained distinct from instance extraction. # 4. Manual extraction as a specification, 2024 to 2025 -`CDEStaging` approached extraction by doing it by hand and writing down what broke. Its process document works a chest CT pulmonary angiogram report into a list of what it calls mini-observations, each a finding name with attribute and value lines.[^cde-process] The worked example is short enough to quote: +`CDEStaging` documented manual extraction and its failures. Its process document works a chest CT pulmonary angiogram report into a list of what it calls mini-observations, each a finding name with attribute and value lines.[^cde-process] Its worked example is: ```markdown - pulmonary embolism @@ -87,11 +87,11 @@ What this line established: retrieval over a real corpus produces better finding - side: left ``` -Two observations in that document set the agenda for everything after it. Finding, attribute, and value names have to match the project's own finding definitions, or the extraction is not interoperable. And blanket negative statements such as "Lungs are clear" need their own catalog, because they assert many findings absent in one phrase. +Interoperability requires finding, attribute, and value names to match the project's definitions. Blanket negatives such as "Lungs are clear" need a catalog because one phrase asserts several findings absent. -Three companion files hold the results of that cataloging work. `negative_statements.md` collects composite and negative report statements by organ system. `uncovered_findings.md` lists findings pulled out of reports with no matching definition, a standing gap log.[^cde-uncovered] `missing_attributes.md` collects report phrases whose attributes existing definitions cannot capture.[^cde-negatives] Fifty-seven worked structured extractions sit alongside them. The two branches that carried most of this work, a report-representation branch of 37 commits and a content branch of 33, were never merged; see the work edge section of [the repository map](/repositories/repository-map.md). +Three companion files catalog these gaps. `negative_statements.md` collects composite and negative report statements by organ system. `uncovered_findings.md` lists findings pulled out of reports with no matching definition, a standing gap log.[^cde-uncovered] `missing_attributes.md` collects report phrases whose attributes existing definitions cannot capture.[^cde-negatives] Fifty-seven worked structured extractions sit alongside them. The two branches that carried most of this work, a report-representation branch of 37 commits and a content branch of 33, were never merged; see the work edge section of [the repository map](/repositories/repository-map.md). -What this line established: the hard part is not parsing, it is the vocabulary gap and the treatment of negation. Both are still open; the extraction platform's own validator prompt names "incorrect representation of a blanket negative" as a re-extraction trigger. +Vocabulary gaps and negation remain unresolved. The extraction validator names "incorrect representation of a blanket negative" as a re-extraction trigger. # 5. Embedding similarity, 2025 @@ -104,23 +104,23 @@ What this line established: the hard part is not parsing, it is the vocabulary g | `step2_map.py` | Each extracted finding is embedded and matched by cosine similarity against 15 curated neuro finding models, with a configurable threshold of 0.70 | | `step3_review_mappings.py` | Produces a human-readable mapping summary | -Its output schema is deliberately thin: a finding is a name and a boolean presence; a mapping adds the matched finding model identifier, a confidence score, and a status. It produces neither an [Exam Finding List](/data-structures/exam-finding-list.md) nor an [Imaging Problem List](/data-structures/imaging-problem-list.md), and no FHIR output; the README lists FHIR Observation generation as a future enhancement. Evaluation was informal: 8 sample reports, 87 extracted findings, 5 matched above threshold, 82 flagged for manual review. +A finding contains a name and boolean presence. A mapping adds the matched model identifier, confidence score, and status. It produces neither an [Exam Finding List](/data-structures/exam-finding-list.md) nor an [Imaging Problem List](/data-structures/imaging-problem-list.md), and no FHIR output; the README lists FHIR Observation generation as a future enhancement. Evaluation was informal: 8 sample reports, 87 extracted findings, 5 matched above threshold, 82 flagged for manual review. Three single-commit branches pushed in one week extend it without merging: a longitudinal multiple sclerosis report series, a port of the extraction step to a typed agent framework, and a design note proposing persistent FHIR Observations with stable identifiers, so that repeat mentions of the same lesion across reports attach to one tracked entity with its own timeline. That note adds no code and names two modules that were never written. See [the project profile](/applications/ipl-mvp-extraction.md). -What this line established: a 6 percent match rate at a 0.70 similarity threshold against 15 models is not a mapping strategy. Code assignment needs the index, not the embedding alone. +The 6 percent match rate at a 0.70 threshold against 15 models shows the limits of embedding-only matching and the need for index-based code assignment. # 6. The current extraction platform, 2026 The `dev` branch of `imaging-problem-list` is a full extraction, coding, persistence, and review platform; see [the project profile](/applications/report-extraction-platform.md). Four things distinguish it from everything above. -**A richer extraction schema.** The original plan defines an extracted finding as a finding name, a presence drawn from present, absent, indeterminate, and possible, an optional location with body region, specific anatomy, and laterality, a list of attribute key and value pairs with standard keys for size, acuity, change from prior, severity, count, and morphology, and the verbatim report text it came from.[^ipl-initial-plan] The `possible` value exists specifically to hold hedged language such as "raising the possibility of" or "cannot exclude". +**A richer extraction schema.** The original plan records a finding name, presence, optional location, attributes, and verbatim report text.[^ipl-initial-plan] Presence can be present, absent, indeterminate, or possible. Location includes body region, specific anatomy, and laterality. Attribute key-value pairs use standard keys for size, acuity, change from prior, severity, count, and morphology. `possible` covers hedged language such as "raising the possibility of" or "cannot exclude". **Coding as a separate stage.** Assigning a finding code and a location code happens after extraction, not during it: a deterministic index lookup first, and only where that fails, three language model calls per report chunk to generate search terms, select a finding code from candidates, and select a location code from candidates. See [finding and location coding](/applications/finding-and-location-coding.md). **Verbatim-quote validation.** A validator reviews each extraction against the chunk it came from and names the failure modes that justify re-extraction: content unsupported by the chunk text, report text describing a finding that no structure represents, a finding marked present when it is not or absent when it is possible, a finding name more specific than the text supports, incorrect representation of a blanket negative, and wrong or misscoped location information.[^ipl-validator] -**Evaluation redesigned for honesty.** After three and a half months without implementation, the evaluation plan was cut down rather than abandoned. Version one scores quote-first matching and attribute values rather than raw finding yield, uses frozen run configurations with per-case artifacts, and adjudicates a ten-case gold set through the human review tool. The stated motivation is a specific failure: a model-selection round chose a local default on throughput and yield alone, which cannot distinguish a real recall gain from a fabricated finding.[^ipl-evals] +**Evaluation redesign.** After three and a half months without implementation, the evaluation plan was reduced. Version one scores quote-first matching and attribute values rather than raw finding yield, uses frozen run configurations with per-case artifacts, and adjudicates a ten-case gold set through the human review tool. The stated motivation is a specific failure: a model-selection round chose a local default on throughput and yield alone, which cannot distinguish a real recall gain from a fabricated finding.[^ipl-evals] # What carried forward diff --git a/archive/knowledge/history/index.md b/archive/knowledge/history/index.md new file mode 100644 index 0000000..80610c0 --- /dev/null +++ b/archive/knowledge/history/index.md @@ -0,0 +1,10 @@ +# History + +Earlier OIDM work, its surviving contributions, and its successors. + +* [Timeline](./timeline.md) - Dated OIDM milestones from anatomic-location work in 2022 through current work in September 2026. +* [Lineage repositories](./lineage-repositories.md) - Twelve early OIDM repositories, their surviving contributions, and their successors. +* [Extraction approaches](./extraction-approaches.md) - Seven report-extraction approaches, their results, and their contributions to the current platform. +* [CDE template rendering](./cde-template-rendering.md) - How CDETemplateDemo rendered structured Observations through Mustache templates in 2023, a precedent for the proposed reporting SDK. +* [Use case catalog](./use-cases.md) - The roughly 25 ideas from UseCases, with source attribution and six value-category tags. +* [Site articles](./site-articles.md) - The fifteen posts published on openimagingdata.org between June 2023 and March 2025, plus the About page, each dated, linked, and summarized. diff --git a/knowledge/history/lineage-repositories.md b/archive/knowledge/history/lineage-repositories.md similarity index 58% rename from knowledge/history/lineage-repositories.md rename to archive/knowledge/history/lineage-repositories.md index 935ec80..515fa08 100644 --- a/knowledge/history/lineage-repositories.md +++ b/archive/knowledge/history/lineage-repositories.md @@ -1,10 +1,10 @@ --- type: History title: Lineage repositories -description: The twelve early OIDM repositories, what each one was for, what survives into current work, and what superseded the rest. +description: Twelve early OIDM repositories, their surviving contributions, and their successors. tags: [history, lineage, repositories, fhir] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T02:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: oidm-py-observation resource: https://github.com/openimagingdata/OpenImagingDataModel.py/blob/455e5b68d730ea8829083d73064de0ec316d3c4a/openimagingdatamodel/observation/observation.py @@ -49,7 +49,7 @@ sources: # Why this document exists -Twelve repositories came before the current three-layer structure. Some are dead ends; several hold the precedent that later work still follows, and one of them is still the project's CDE data source. Each section below gives the repository's purpose, its dates, its status in [the repository map](/repositories/repository-map.md), what survives, and what replaced it. +Twelve repositories preceded the current three-layer structure. Several informed later work, and one remains the CDE data source. Their dates, status, and successors are described below and in [the repository map](/repositories/repository-map.md). # The FHIR precedent @@ -66,21 +66,21 @@ Two lineage repositories together define how OIDM maps onto FHIR, and nothing si | `example1OIDMradiologist.json` | Observation | The same finding as confirmed by a radiologist, distinguished only by `status` `final` | | `example1OIDMradiologistLungRADS.json` | Observation | A second-order assessment Observation that is `derivedFrom` both the ImagingStudy and the radiologist's finding Observation, carrying the Lung-RADS category (RDES267) as a component | -Three patterns come out of this example and recur throughout later work. The chain from CDE set code to element code to value code, all through the `radelement.org` coding system, is what "CDE-labeled FHIR Observation" means concretely. The `status` field is what separates a machine-produced finding from a radiologist-confirmed one, rather than a separate resource type. And the second-order observation, an assessment built on top of a finding observation, is the precedent for any later assessment or category modeling, including the reporting-system categories the [2026 roadmap](/roadmap/roadmap-2026.md) names. +A "CDE-labeled FHIR Observation" uses set, element, and value codes from `radelement.org`. Its `status` distinguishes machine-produced from radiologist-confirmed findings within the same resource type. The second-order observation, an assessment built on top of a finding observation, is the precedent for any later assessment or category modeling, including the reporting-system categories [the 2026 roadmap](/roadmap/roadmap-2026.md) names. -Two absences in the example are worth recording. No `bodySite` coding appears on the finding Observations, because the anatomy was implicit in the CDE definition rather than explicit on the resource. No Patient resource is included; `subject.reference` is a bare reference. Both gaps are addressed in the current model, where [anatomic location](/glossary/anatomic-location.md) is an explicit field. See [FHIR mapping](/data-structures/fhir-mapping.md) for where the current structures stand. +The finding Observations omit `bodySite` because anatomy is implicit in the CDE definition. There is no Patient resource, only `subject.reference`. Both gaps are addressed in the current model, where [anatomic location](/glossary/anatomic-location.md) is an explicit field. See [FHIR mapping](/data-structures/fhir-mapping.md) for where the current structures stand. # Reference implementations -**OpenImagingDataModel.ts.** Created 2023-04-27, last commit on the default branch 2023-10-22. A pnpm monorepo with `cde_set`, `observation`, and `findingModel` packages, all typed with Zod schemas.[^oidm-ts] Status: lineage. Its Zod schemas are the cleanest early type definitions for a CDE set, a CDE element, an index code, and a body part, and they are the only place the project expressed these shapes in TypeScript. No FHIR imports appear anywhere in it. Superseded by the Python implementation and then by the `findingmodel` package. +**OpenImagingDataModel.ts.** Created 2023-04-27, last commit on the default branch 2023-10-22. A pnpm monorepo with `cde_set`, `observation`, and `findingModel` packages, all typed with Zod schemas.[^oidm-ts] Status: lineage. Its Zod schemas define CDE sets, CDE elements, index codes, and body parts, the project's only TypeScript definitions of these shapes. No FHIR imports appear anywhere in it. Superseded by the Python implementation and then by the `findingmodel` package. -**OpenImagingDataModel.py.** Created 2024-03-08, last commit on the default branch 2024-11-13. Subpackages `cde_set` and `observation`.[^oidm-py-observation] Status: lineage, but the most consequential of the group. Besides the Observation class above, `finding_model.py` defines a `FindingModel` with a name, a description, and a list of attributes discriminated into choice and numeric types with a `required` flag.[^oidm-py-fm] Its docstring, "The definition of a radiology finding ... along with definitions of the relevant attributes that a radiologist might use to characterize the finding in a radiology report", survives verbatim into the current [finding model](/glossary/finding-model.md) class in `findingmodel`. What the early class lacked is exactly what the current format adds: identifiers, synonyms, tags, index codes, anatomic locations, and contributors. Its `set.py`, `element.py`, and `set_factory.py` mirror the RadElement schema. Superseded by the `findingmodel` package. +**OpenImagingDataModel.py.** Created 2024-03-08, last commit on the default branch 2024-11-13. Subpackages `cde_set` and `observation`.[^oidm-py-observation] Status: lineage. Besides the Observation class above, `finding_model.py` defines a `FindingModel` with a name, a description, and a list of attributes discriminated into choice and numeric types with a `required` flag.[^oidm-py-fm] Its docstring, "The definition of a radiology finding ... along with definitions of the relevant attributes that a radiologist might use to characterize the finding in a radiology report", survives verbatim into the current [finding model](/glossary/finding-model.md) class in `findingmodel`. The current format adds identifiers, synonyms, tags, index codes, anatomic locations, and contributors. Its `set.py`, `element.py`, and `set_factory.py` mirror the RadElement schema. Superseded by the `findingmodel` package. # Content and schema -**openimagingdata.org (the repository).** Created 2023-02-20, last commit 2023-06-27. Status: lineage. This is where the project's first name lives: the README titles it "Open Radiology Data Model" and points at a domain that no longer serves the project. Two things survive. The `schemas/` directory holds the CDE Set JSON schema and minimal sample CDE set and Observation fixtures. More useful is `schema_differences.md`, a precise gap analysis between the CDE Set RelaxNG schema and the RadElement JSON API: array wrapper naming, `index_code.url` against the API's `href`, a `system` enum constrained in the schema but not the API, authors interleaved in the schema but split into arrays in the API, and presence mismatches on modality, biological sex, age range, parent set, question, references, and body parts.[^old-site-schema] That analysis is still live; the same gap is the subject of an open issue in `CDEStaging`. See [CDEs and RadElement](/semantic-foundation/common-data-elements/cdes-and-radelement.md). Superseded as a site by the hosted blog at the same domain. +**openimagingdata.org (the repository).** Created 2023-02-20, last commit 2023-06-27. Status: lineage. The README titles it "Open Radiology Data Model" and points to a former project domain. `schemas/` holds the CDE Set JSON schema and sample CDE set and Observation fixtures. `schema_differences.md` compares the CDE Set RelaxNG schema with the RadElement JSON API: array wrapper naming, `index_code.url` against the API's `href`, a `system` enum constrained in the schema but not the API, authors interleaved in the schema but split into arrays in the API, and presence mismatches on modality, biological sex, age range, parent set, question, references, and body parts.[^old-site-schema] An open `CDEStaging` issue tracks the same gap. See [CDEs and RadElement](/semantic-foundation/common-data-elements/cdes-and-radelement.md). Superseded as a site by the hosted blog at the same domain. -**common_data_elements.** Created 2025-02-21, content uploaded 2025-06-04. Status: maintenance, not lineage: this is still the project's local snapshot of RadElement. It holds roughly 140 CDE set definitions as JSON, indexed in the README by clinical topic with their set identifiers, and the canonical `cde.schema-1.0.json` and `cde.schema-1.1.json` files that the gap analysis above refers to.[^cde-data] The README notes that the definitions conform to schema version 1.0, not the newer 1.1. +**common_data_elements.** Created 2025-02-21, content uploaded 2025-06-04. Status: maintenance. This remains the project's local RadElement snapshot. It holds roughly 140 CDE set definitions as JSON, indexed in the README by clinical topic with their set identifiers, and the canonical `cde.schema-1.0.json` and `cde.schema-1.1.json` files that the gap analysis above refers to.[^cde-data] The README notes that the definitions conform to schema version 1.0, not the newer 1.1. **UseCases.** Created 2023-09-07, last commit 2024-02-26. Status: lineage as an activity, but its content is the fullest enumeration of application ideas anywhere in the project.[^usecases] It holds roughly 25 one-line use case ideas and the six value categories used to tag them. Migrated in full to [the use case catalog](/history/use-cases.md). Nothing superseded it; the deck's application pillar and the [2026 roadmap](/roadmap/roadmap-2026.md) cover related ground without reusing the catalog. @@ -88,15 +88,15 @@ Two absences in the example are worth recording. No `bodySite` coding appears on **CDETemplateDemo.** Created 2023-05-31, last commit 2023-06-15, default branch `master`. Status: lineage. A working proof of concept that renders a structured Observation into report prose through a Mustache template.[^template-demo] The pattern survives as the clearest precedent for the reporting SDK direction; see [CDE template rendering](/history/cde-template-rendering.md). Its own Observation shape, a flat string-keyed record of components, is looser than the FHIR-based one and is superseded by it. -**SARTemplatesToCDEs.** Created 2023-12-30, last commit 2024-03-11. Status: lineage, stalled at proof of concept. The stated goal was to use a language model to convert Society of Abdominal Radiology reporting templates into CDE sets, starting with rectal cancer staging. No conversion code was committed. What survives is `Changes_vs_RDES11.md`, an itemized redesign of the MR rectal tumor imaging CDE set against a 2016 baseline: an element renamed, mucinous composition given finer granularity, T-category and sphincter-invasion elements restructured, and new elements for tumor morphology, extramural vascular invasion, tumor deposits, and lymph nodes, each with explicit value sets.[^sar] It remains the most detailed single-CDE-set redesign in the lineage. The conversion approach itself is superseded by the batch content pipelines in `findingmodels`; see [extraction approaches](/history/extraction-approaches.md). +**SARTemplatesToCDEs.** Created 2023-12-30, last commit 2024-03-11. Status: lineage, stalled at proof of concept. The stated goal was to use a language model to convert Society of Abdominal Radiology reporting templates into CDE sets, starting with rectal cancer staging. No conversion code was committed. `Changes_vs_RDES11.md` records an itemized redesign of the MR rectal tumor imaging CDE set against a 2016 baseline: an element renamed, mucinous composition given finer granularity, T-category and sphincter-invasion elements restructured, and new elements for tumor morphology, extramural vascular invasion, tumor deposits, and lymph nodes, each with explicit value sets.[^sar] It remains the most detailed single-CDE-set redesign in the lineage. The conversion approach itself is superseded by the batch content pipelines in `findingmodels`; see [extraction approaches](/history/extraction-approaches.md). # Extraction and ontology tooling These four are covered in depth in [extraction approaches](/history/extraction-approaches.md); their status is summarized here. -**template_extraction.** Created 2025-06-05, last commit 2025-06-16. Status: lineage. A short script and a corpus of 281 RadReport templates spanning CT, MR, ultrasound, nuclear medicine, and fluoroscopy across most body regions, plus two CDE-labeled test templates.[^template-extraction] The corpus is the asset; it is an unused candidate vocabulary for exam types as well as findings. Not superseded by anything; the work simply moved to converting CDE staging content instead. +**template_extraction.** Created 2025-06-05, last commit 2025-06-16. Status: lineage. A short script and a corpus of 281 RadReport templates spanning CT, MR, ultrasound, nuclear medicine, and fluoroscopy across most body regions, plus two CDE-labeled test templates.[^template-extraction] The corpus remains unused but could supply exam-type and finding vocabulary. No successor replaced it. Work moved to converting CDE staging content. -**ReportFindingRefiner.** Created 2024-12-31, last commit 2025-05-23. Status: lineage, and the most mature of the extraction family. A privacy-first pipeline running local models: ingest report text, split into header, findings, and impression sections, embed with sentence transformers, store in a vector database, search, then generate finding model outlines with a local language model and persist them.[^refiner] Superseded by the extraction platform on the `imaging-problem-list` `dev` branch, which pursues the same goal with a chunked, multi-provider, verbatim-quote-validated design. +**ReportFindingRefiner.** Created 2024-12-31, last commit 2025-05-23. Status: lineage. Its local-model pipeline protects report privacy: ingest report text, split into header, findings, and impression sections, embed with sentence transformers, store in a vector database, search, then generate finding model outlines with a local language model and persist them.[^refiner] Superseded by the extraction platform on the `imaging-problem-list` `dev` branch, which pursues the same goal with a chunked, multi-provider, verbatim-quote-validated design. **get_ontology_findings.** Created 2025-03-27, last commit 2025-03-27. Status: lineage. A minimal script plus a RadLex export, aimed at finding ontology entries that correspond to imaging findings so they can become finding models or CDEs.[^get-ontology] Superseded in scope by `med-ontology-lookup`. @@ -104,7 +104,7 @@ These four are covered in depth in [extraction approaches](/history/extraction-a # What connects them -No lineage repository depends on another as a package, and none names another by URL. The connective tissue is shared external vocabulary: RadElement set and element codes thread through the CDE snapshot, the FHIR samples, the TypeScript sample data, the template demo's sample observations, and the SAR redesign. The CDE set and element shape is independently re-implemented three times. The finding model concept is defined independently in the TypeScript and Python implementations with near-identical shape and reimplemented a third time inside `ReportFindingRefiner`. That independent convergence is why the current format could consolidate them without contradiction. +No lineage repository imports another as a package or names another by URL. They share RadElement set and element codes across the CDE snapshot, FHIR samples, TypeScript samples, template demo, and SAR redesign. Three implementations independently define the CDE set and element shape. That independent convergence is why the current format could consolidate them without contradiction. TypeScript and Python define near-identical finding model shapes; `ReportFindingRefiner` implements the concept separately. [^oidm-py-observation]: OpenImagingDataModel.py observation.py, the FHIR Observation model [^oidm-py-fm]: OpenImagingDataModel.py finding_model.py, the earliest FindingModel class diff --git a/knowledge/history/site-articles.md b/archive/knowledge/history/site-articles.md similarity index 80% rename from knowledge/history/site-articles.md rename to archive/knowledge/history/site-articles.md index abd9373..4c047eb 100644 --- a/knowledge/history/site-articles.md +++ b/archive/knowledge/history/site-articles.md @@ -4,7 +4,7 @@ title: Site articles description: The fifteen posts published on openimagingdata.org between June 2023 and March 2025, plus the About page, each dated, linked, and summarized. tags: [history, site, articles, reference] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T02:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: rss resource: https://openimagingdata.org/rss/ @@ -16,7 +16,7 @@ sources: # What the site is -`openimagingdata.org` is the project's public site, a hosted blog whose title is "Open Imaging Data Model" and whose tagline is "Defining unified data structures to integrate new functionality into imaging informatics platforms." It carries two static pages, Home and About, and fifteen posts running from 2023-06-24 to 2025-03-10.[^rss] Every date and summary below was verified against the live page on 2026-09-21. Individual authors are not named here, per the conventions in [the authoring guide](/guides/authoring-guide.md). +`openimagingdata.org` is the project's public site, a hosted blog whose title is "Open Imaging Data Model" and whose tagline is "Defining unified data structures to integrate new functionality into imaging informatics platforms." It has Home and About pages and fifteen posts dated 2023-06-24 to 2025-03-10.[^rss] Every date and summary below was verified against the live page on 2026-09-21. Individual authors are not named here, per the conventions in [the authoring guide](/guides/authoring-guide.md). The site replaced the earlier repository-hosted site in June 2023; see [lineage repositories](/history/lineage-repositories.md). @@ -24,7 +24,7 @@ The site replaced the earlier repository-hosted site in June 2023; see [lineage ## 2025-03-10, [Leveraging Common Data Elements (CDEs) to Enhance Radiology Reporting](https://www.openimagingdata.org/leveraging-common-data-elements-cdes-to-enhance-radiology-reporting/) -A guest post from a reporting software vendor arguing that standardized data points improve clinical decision-making and data quality in radiology reporting. It describes integrating CDEs with radiology information systems and PACS while adhering to HL7, FHIR, and FHIRcast. The stated payoff is efficiency, advanced analytics, and better outcomes from structured data. This is the most recent post on the site. +A guest post from a reporting software vendor arguing that standardized data points improve clinical decision-making and data quality in radiology reporting. It describes integrating CDEs with radiology information systems and PACS while adhering to HL7, FHIR, and FHIRcast. The stated payoff is efficiency, advanced analytics, and better outcomes from structured data. This is the latest site post. ## 2024-07-02, [SIIM 2024 Update: Hackathon Edition](https://www.openimagingdata.org/siim-2024-update-hackathon-edition/) @@ -32,7 +32,7 @@ Reports the completed hackathon tool, which automatically associates concepts fr ## 2024-07-01, [Benchmarking a Vision](https://www.openimagingdata.org/benchmarking-a-vision/) -Announces a manuscript in the Journal of the American Medical Informatics Association titled "Standardizing imaging findings representation: harnessing Common Data Elements semantics and Fast Healthcare Interoperability Resources structures." The paper presents the framework for representing findings as CDE-labeled FHIR Observations, with a pulmonary nodule case study. Its claim, quoted in the post, is that CDE-labeled Observations should be the universal representation for exchanging and consuming radiology report content. +Announces a manuscript in the Journal of the American Medical Informatics Association titled "Standardizing imaging findings representation: harnessing Common Data Elements semantics and Fast Healthcare Interoperability Resources structures." The paper presents the framework for representing findings as CDE-labeled FHIR Observations, with a pulmonary nodule case study. The post quotes its claim that CDE-labeled Observations should be the universal representation for exchanging and consuming radiology report content. ## 2024-06-20, [Attaching Ontology Links to Common Data Elements](https://www.openimagingdata.org/attaching-ontology-links-to-common-data-elements/) @@ -40,7 +40,7 @@ Describes the problem the hackathon set out to solve: matching CDEs to ontology ## 2024-06-18, [AI-Powered Chest CT Reporting: Transforming Radiologist Observations into Standardized Data](https://www.openimagingdata.org/creating-common-data-elements-for-chest-cts-how-ai-is-revolutionizing-radiology-reporting/) -Reports using a large language model to generate common data element definitions from anonymized chest CT reports, by converting report text into semantic vectors, ranking relevant sections, and having radiologists review the generated definitions. More than 200 CDE definitions came out of the pilot. They were published to GitHub for community review, which is the origin of the chest CT content later carried in `CDEStaging`. +Reports using a large language model to generate common data element definitions from anonymized chest CT reports, by converting report text into semantic vectors, ranking relevant sections, and having radiologists review the generated definitions. The pilot published more than 200 definitions to GitHub for community review, originating the chest CT content later held in `CDEStaging`. ## 2024-06-17, [Counting Down to SIIM](https://www.openimagingdata.org/counting-down-to-siim/) @@ -60,7 +60,7 @@ A summary of the 2024-01-26 meeting, covering how imaging data standards connect ## 2024-01-25, [Data Model: Structure and Function](https://www.openimagingdata.org/data-model-structure-and-function/) -The clearest statement of what OIDM is structurally. It describes the model as covering the whole reporting context, observations, patient data, imaging studies, and clinical history, and says the model makes extensive use of FHIR definitions while providing a superstructure on top for easier programmatic access, comparing that relationship to how a browser's document object model relates to HTML. It names two utility libraries: anatomic locations for standardized body part terminology, and an exam type library based on the LOINC and RSNA Playbook linked to anatomic locations. That exam type library has never been built; see [exam types](/semantic-foundation/exam-types/overview.md). +Describes OIDM as covering report context, observations, patient data, imaging studies, and clinical history. It proposes structures over FHIR definitions for programmatic access, comparing the relationship to a browser's document object model and HTML. It names two utility libraries: anatomic locations for standardized body part terminology, and an exam type library based on the LOINC and RSNA Playbook linked to anatomic locations. That exam type library has never been built; see [exam types](/semantic-foundation/exam-types/overview.md). ## 2024-01-12, [2024 New Year Update](https://www.openimagingdata.org/2024-new-year-update/) @@ -72,7 +72,7 @@ Lists six engagement channels: registering on the site for newsletters and comme ## 2023-07-16, [OIDM-Based Next-gen Reporting Assistance Framework](https://www.openimagingdata.org/oidm-based-next-gen-reporting-assistance/) -Proposes a plugin architecture in which developers write assistance scripts that run inside a reporting tool's container. A script reads standardized data structures holding the report context, and can insert generated text into the report, ask the radiologist for more information, alert the radiologist to a problem, or send data to an external system. The container re-runs the scripts whenever the report context changes, which makes the assistance continuous rather than one-shot. This is the earliest statement of the direction later named the reporting SDK; see [the reporting SDK](/applications/reporting-sdk.md). +Proposes a plugin architecture in which developers write assistance scripts that run inside a reporting tool's container. A script reads standardized data structures holding the report context, and can insert generated text into the report, ask the radiologist for more information, alert the radiologist to a problem, or send data to an external system. The container reruns scripts whenever report context changes. This is the earliest statement of the direction later named the reporting SDK; see [the reporting SDK](/applications/reporting-sdk.md). ## 2023-06-26, [SIIM Update](https://www.openimagingdata.org/siim-update/) @@ -80,7 +80,7 @@ Announces the site itself and summarizes an in-person SIIM meeting on cooperatio ## 2023-06-24, [Findings, CDEs, and Observations](https://www.openimagingdata.org/findings-cdes-and-observations/) -The founding post. It argues that a radiology finding can be standardized as a FHIR Observation labeled with ACR and RSNA common data element identifiers: the Observation's code carries the CDE set identifier naming the finding type, and each attribute becomes a component carrying a CDE element identifier and a value. Its worked example is a solid 6 mm nodule in the right lower lobe, labeled with the pulmonary nodule CDE set code and element codes for composition and size. Everything in [the data structures layer](/data-structures/hierarchy.md) descends from this claim. +The founding post. It argues that a radiology finding can be standardized as a FHIR Observation labeled with ACR and RSNA common data element identifiers: the Observation's code carries the CDE set identifier naming the finding type, and each attribute becomes a component carrying a CDE element identifier and a value. Its worked example is a solid 6 mm nodule in the right lower lobe, labeled with the pulmonary nodule CDE set code and element codes for composition and size. This claim underlies [the data structures layer](/data-structures/hierarchy.md). # The About page @@ -88,7 +88,7 @@ The About page states the mission: establish common data structures representing It also commits to programming interfaces in three languages, TypeScript and JavaScript, Python, and C#. Two of the three exist as lineage repositories and neither is current; no C# library was ever created. See [lineage repositories](/history/lineage-repositories.md). -The page was last modified 2023-06-21 and has not been revised since, so it predates finding models, the Exam Finding List, and the Imaging Problem List. Where it differs from current documents, the current documents govern. +The page was last modified 2023-06-21, before finding models, the Exam Finding List, and the Imaging Problem List. Where it differs from current documents, the current documents govern. [^rss]: openimagingdata.org RSS feed, 15 posts, fetched 2026-09-21 [^about]: openimagingdata.org About page, fetched 2026-09-21 diff --git a/knowledge/history/timeline.md b/archive/knowledge/history/timeline.md similarity index 93% rename from knowledge/history/timeline.md rename to archive/knowledge/history/timeline.md index 8b8b92c..7709d24 100644 --- a/knowledge/history/timeline.md +++ b/archive/knowledge/history/timeline.md @@ -1,10 +1,10 @@ --- type: History title: Timeline -description: A dated record of the Open Imaging Data Model from its anatomic-location prologue through the January 2026 status update and the 2026 work edge. +description: Dated OIDM milestones from anatomic-location work in 2022 through current work in September 2026. tags: [history, timeline] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T02:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: gh-org resource: https://github.com/openimagingdata @@ -37,7 +37,7 @@ sources: # Prologue: 2022 -The anatomic-location work predates the project's own name. The TypeScript wrapper repository was created on 2022-02-02, the Python wrapper on 2022-07-19, and the curated dataset and its site on 2022-12-14.[^gh-org] The dataset's own changelog records version `1.0.0-rc.1` in December 2022, adding a version field and SNOMED links. The curated set reached 2,890 records and its last substantive site commit, a hierarchy tree view, landed on 2023-01-10. See [the anatomic location lineage](/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md). +Anatomic-location work began before OIDM. The TypeScript wrapper was created on 2022-02-02, the Python wrapper on 2022-07-19, and the curated dataset and site on 2022-12-14.[^gh-org] Version `1.0.0-rc.1` added a version field and SNOMED links in December 2022. The set reached 2,890 records. The last substantive site commit added a hierarchy tree view on 2023-01-10. See [the anatomic location lineage](/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md). # 2023: naming, schema, and the first demonstrations @@ -119,7 +119,7 @@ The anatomic-location work predates the project's own name. The TypeScript wrapp | 2026-08-21 | The `next-gen-2026` branch opens in the RSNA `ACR-RSNA-CDEs` repository with next-generation schema research notes in Open Knowledge Format. | | 2026-09-02 to 2026-09-15 | Decisions S30 through S51 are recorded on that branch, covering anatomy families, scope corrections, and the split between prototype and integration work. | | 2026-09-20 | Latest activity in `med-ontology-lookup`; typed provider failures merge and offline continuous integration remains uncommitted. | -| 2026-09-20 to 2026-09-21 | This knowledgebase is planned and begun. See [the build plan](/plans/2026-09-20-knowledgebase-build-plan.md). | +| 2026-09-20 to 2026-09-21 | This knowledgebase is planned and begun. See [the build plan](/references/project-direction.md). | [^gh-org]: Repository creation and push dates from the GitHub API, read 2026-09-21 [^rss]: openimagingdata.org RSS feed, 15 posts, read 2026-09-21 diff --git a/knowledge/history/use-cases.md b/archive/knowledge/history/use-cases.md similarity index 83% rename from knowledge/history/use-cases.md rename to archive/knowledge/history/use-cases.md index be7998c..02b8003 100644 --- a/knowledge/history/use-cases.md +++ b/archive/knowledge/history/use-cases.md @@ -1,10 +1,10 @@ --- type: Reference title: Use case catalog -description: The roughly 25 use case ideas collected in the UseCases repository, organized by category, with the six value categories used to tag them. +description: The roughly 25 ideas from UseCases, with source attribution and six value-category tags. tags: [history, use-cases, reference] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T02:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: usecases-index resource: https://github.com/openimagingdata/UseCases/blob/71a90d2ab2efeee4c4303dc0aef27ed3d70a6c99/Index.md @@ -16,7 +16,7 @@ sources: # What this is -The `UseCases` repository was created in September 2023 to collect ideas for what an Open Imaging Data Model (OIDM) would make possible. Its index was meant to be a holding pen: a name and a one-sentence description each, with mature entries promoted to their own files.[^usecases-index] None were promoted, and the last commit is 2024-02-26. The catalog below is the whole of it, reorganized but not extended. +The `UseCases` repository began in September 2023 to collect Open Imaging Data Model (OIDM) application ideas. Each index entry had a name and one-sentence description, with mature ideas intended to become separate files.[^usecases-index] None did. The last commit was 2024-02-26. This catalog reorganizes all entries without additions. Entries marked as contributed by the RSNA Reporting Informatics Committee are so marked in the source. The numbers after each entry are the value categories defined in the next section, exactly as the source assigns them. Four of the repository's seven headings, Protocoling System, AI Pipeline, PACS Viewing, and Data Exploration, are placeholders with no entries. @@ -39,7 +39,7 @@ Six categories, taken from the RSNA Reporting Informatics Committee's use case w # Assisted reporting -Most entries in this section were contributed by the RSNA Reporting Informatics Committee, which is noted per entry below. +Most entries came from the RSNA Reporting Informatics Committee, as marked below. - **Stroke notification** (RSNA Reporting Informatics Committee). Automated communication to the stroke team when findings of stroke are reported. (2, 3) - **Problem list update** (RSNA Reporting Informatics Committee). Findings in a radiology report automatically update the electronic medical record problem list. (3) @@ -74,11 +74,11 @@ These entries carry no value category tags in the source. # How the catalog was meant to work -The repository's README sets out an intended process that was never completed: collect ideas in the index, then define metadata for each mature case and move it to its own file organized hierarchically as Markdown or JSON.[^usecases-readme] It asks three questions that remain unanswered in the repository: what information a use case should capture, whether the format should be prose or data, and whether specialized coded inputs such as LOINC for laboratory data are needed. +The unfinished process was to collect ideas, define metadata for mature cases, and store them in hierarchical Markdown or JSON files.[^usecases-readme] The README leaves three questions open: what information to capture, whether to use prose or data, and whether coded inputs such as laboratory LOINC codes are needed. The categories the README proposes for organizing cases are workflow and list oriented, AI pipeline oriented, reporting oriented, image-viewer oriented, data exploration and outcome oriented, multi-category, and miscellaneous. The index uses a slightly different set of headings, listed above. -For where this line of thinking went next, see [the 2026 roadmap](/roadmap/roadmap-2026.md), whose applications pillar and use-case pipeline to the CDE group cover related ground without reusing this catalog. +[The 2026 roadmap](/roadmap/roadmap-2026.md) describes related applications and use-case submissions to the CDE group without reusing this catalog. [^usecases-index]: UseCases Index.md, the living list of use case ideas [^usecases-readme]: UseCases README.md, the intended process and the six value categories diff --git a/archive/knowledge/index.md b/archive/knowledge/index.md new file mode 100644 index 0000000..a09a0df --- /dev/null +++ b/archive/knowledge/index.md @@ -0,0 +1,23 @@ +--- +okf_version: "0.2" +--- + +# Open Imaging Data Model knowledge + +The repository knowledge graph for the Open Imaging Data Model (OIDM). + +**Status: pre-review draft.** Every document was generated by agents from the source repositories and has `status: draft` until the project lead verifies it. Do not treat any page as authoritative until it carries a `verified` entry and `status: stable`. + +Start with [What is OIDM](./overview/what-is-oidm.md) and [Architecture](./overview/architecture.md). Use the [glossary](./glossary/) for terms, the [repository map](./repositories/repository-map.md) for code and content, and the [roadmap](./roadmap/) and [open questions](./roadmap/open-questions.md) for plans and unresolved issues. Check each page's frontmatter for its type, sources, authorship, and review status. + + +* [overview](./overview/) - What OIDM is, the vision, and how the pieces fit +* [glossary](./glossary/) - One term per file, the vocabulary of record +* [semantic-foundation](./semantic-foundation/) - Finding models, common data elements, anatomic locations, exam types, terminologies +* [data-structures](./data-structures/) - Observation, Exam Finding List, Imaging Problem List, Imaging Persona, FHIR mapping +* [applications](./applications/) - Tools built on the foundation: Forge, viewers, extraction platforms +* [roadmap](./roadmap/) - Stated goals and open questions, by area +* [history](./history/) - Lineage repositories, prior approaches, site articles, use cases +* [repositories](./repositories/) - Map of every source repository and its branch of record +* [references](./references/) - Verbatim and near-verbatim source material +* [guides](./guides/) - Authoring conventions and the migration ledger diff --git a/archive/knowledge/log.md b/archive/knowledge/log.md new file mode 100644 index 0000000..60dce44 --- /dev/null +++ b/archive/knowledge/log.md @@ -0,0 +1,89 @@ +# Update Log + +## 2026-10-02 + +* Consolidated active implementation planning outside the bundle. Replaced the obsolete build plan with a project-direction reference, preserved source citations, and removed the old plans index. Presentation and knowledge migration remain unfinished. + +## 2026-09-22 + +* Completed Codex's source-fidelity review of seven companion drafts and corrected Data Structures, Use Cases, and Sample Applications against the incoming reviews. Added the deck's graph example and live application links; clarified proposal status and demonstrated capabilities. Draft replacement remains pending. +* Added staged drafts for Data Structures, Use Cases, and Sample Applications, with source citations, dated representations, and session authorship. Source cross-review and replacement of the current pages remain pending. + +## 2026-09-21 +* 2026-09-21 - Prose tightened across 112 concept documents by codex/gpt-6 (two passes); reviewed by five claude-opus-5 reviewers; reverts and substantive fixes applied. Sources, links, and footnotes unchanged. +* 2026-09-21 - Added `roadmap/open-questions.md`, 90 numbered entries deduplicated from all ten collecting agents and grouped into finding models, common data elements, anatomic locations, exam types and terminologies, data structures, applications, and the project narrative, each with the sources on each side and an owner where a source names one, plus a separate table of 21 source-document defects with repository and path so they can become issues. Rewritten after the five Phase 2 and 3 agents appended their sections. +* 2026-09-21 - Corrected three roadmap documents against those late sections: the med-ontology-lookup backlog is eleven of twelve issues open rather than nine open and unstarted; findingmodels issue 15 is answered by unmerged work rather than merely overlapping it; the two framings of the RadLex relationship and the 1,016 composite sided identifiers were added to the anatomy roadmap; an identifier lifecycle section covering the absence of any deprecation mechanism and findingmodel issue 42 was added to format evolution. +* 2026-09-21 - Added `roadmap/roadmap-2026.md`, the project-level direction from the January 2026 deck: the three strategic pillars, the ACR-OIDM Structured Imaging Results Working Group proposal with its structure-first and vendor-driven conditions, the four ACR priorities it says the work supports, the three next steps including the use-case pipeline to the CDE group, a per-repository work edge table linking each area roadmap, and the three deck items with no artifact behind them. +* 2026-09-21 - Added `roadmap/finding-model-format-evolution.md`, the four uncoordinated threads for the OIFM record format: the canonical structured-metadata rewrite with its eight fields and failing readiness assessment, the 2026-04-20 source schema version 2 draft (source against hydrated models, canonical attribute files, authored relationships, lifecycle, history sidecars, registries, and its explicit removals), the typed-relationship and derived-differential ideas from the next-generation CDE vocabulary, package and database schema versioning in issues 29 and 30, the superseded eight-facet specification still open as issue 25, the four format-shaping open issues, the three divergent schema extensions on content branches, and the two recorded prose-schema discrepancies. +* 2026-09-21 - Added `roadmap/finding-model-content-direction.md`: the MGB exam-oriented sub-taxonomies as the stated immediate direction with the expected supersession of Gamuts-derived models, the 2,761-row triage task, the five unmerged content branches with counts and dates, the definition cleanup plan's seven ordered categories including the 71 "with"-clause Gamuts patterns and the gamuts reclassification step that needs design discussion, the evidence-gated metadata branch rules and review counts, and the five open content issues. +* 2026-09-21 - Added `roadmap/ipl-data-model-system.md`: the project lead's four-part statement of the Imaging Problem List goals, issue 1 quoted in full substance with each element mapped to a gap that exists today, the eight active development-branch plans with the four that state goals beyond tooling, the anatomic-compatibility reconciliation follow-on quoted from the assignment rules, the documented-and-unimplemented FHIR mapping with the persistent-identity lineage design note, the IHE IDR alignment as a goal with its four unresolved differences, and the three items with no plan behind them. +* 2026-09-21 - Added `roadmap/exam-types.md`: the project lead's three-part goal including the always, usually, and possibly-included anatomy edges, the same goal stated in 2023 and 2024, the radiology domain profile as the one written design with its four Playbook implications, the Playbook governance and licensing facts that constrain any effort, and four unanswered questions including the never-published curated LOINC list and the description inconsistencies in the sample data. +* 2026-09-21 - Added `roadmap/anatomic-locations-and-radlex.md`: RadLex issue 3 with its coverage exit criterion, the 13 September 2026 overlay agreement and its two written consequences, what is owed in each direction with the is-a and structure-type request named as top priority, the two unreconciled datasets, the 2024 BodyPartIndex TODO list with per-item current status, the four tooling items in findingmodel including issues 38 and 39, and the four incompatible body region enumerations. +* 2026-09-21 - Added `roadmap/open-questions.md`, 61 numbered entries deduplicated from the six collecting agents and grouped into finding models, common data elements, anatomic locations, exam types and terminologies, data structures, applications, and the project narrative, each with the sources on each side and an owner where a source names one, plus a separate table of 15 source-document defects with repository and path so they can become issues. +* 2026-09-21 - Listed all seven documents in `roadmap/index.md`. +* Added `glossary/` with 48 Glossary Term documents, the vocabulary of record for OIDM, each giving a sourced definition, synonyms and near-synonyms, identifier form, where the term is used, and any conflict between sources. +* Terms cover the finding model format (OIFM, finding model, attribute, attribute value, index code, tag, contributor, organization code, finding taxonomy), the common data element vocabulary current and next-generation (CDE, CDE set, CDE element, RadElement, DataElement, FindingClass, Measurement, element binding, AssessmentScheme), anatomy (anatomic location, anatomic scope, laterality, contained by and part of, body region), terminologies (RadLex, RadLex RID, SNOMED CT, FMA, UMLS, LOINC, the LOINC/RSNA Radiology Playbook, Gamuts), the data structures (observation, Exam Finding List, Imaging Problem List, Imaging Persona, presence, change from prior, technical finding, exam type), FHIR and IHE encodings (CDE-labeled FHIR Observation, FHIR DiagnosticReport, FHIR Condition, IHE Imaging Diagnostic Report), and the pipeline (extraction, coding, provenance), plus OIDM itself and the Open Imaging Reporting SDK. +* Added `glossary/index.md` listing every term alphabetically with its frontmatter description, generated from the frontmatter so descriptions match exactly. +* Twenty-two conflicts between sources were recorded under "Conflicts" headings in the affected entries and collected for `roadmap/open-questions.md`. The largest are the positional presence dot code, four incompatible body-region enumerations, the component-versus-hasMember disagreement with IHE IDR, and the two unsynchronized anatomic location lineages. +* Added `repositories/repository-map.md`: every source repository with organization, role, status, branch of record, last activity, deployed URL, and bundle coverage; plus a data-flow diagram and a per-repository work edge summary. +* Added `history/timeline.md`: dated record from the 2022 anatomic-location prologue through the 2026 work edge. +* Added `history/lineage-repositories.md`: the twelve lineage repositories, including the FHIR precedent in `OpenImagingDataModel.py` and `FHIRSamples`. +* Added `history/extraction-approaches.md`: seven successive report-to-structure extraction efforts and what each established. +* Added `history/cde-template-rendering.md`: the 2023 Mustache rendering pattern and its relation to the reporting SDK direction. +* Added `history/use-cases.md`: the use case catalog and the six RSNA Reporting Informatics Committee value categories, migrated from `UseCases`. +* Added `history/site-articles.md`: the 15 openimagingdata.org posts and the About page, each verified live on 2026-09-21. +* Filled `history/index.md` and `repositories/index.md`. +* Added `overview/what-is-oidm.md`, `overview/vision.md`, `overview/architecture.md`, and `overview/getting-involved.md`, with `overview/index.md` listing all four. +* Added `references/status-update-2026-01.md`, a slide-by-slide extract of the January 2026 status deck with the collaborators slide reduced to organizations. +* 2026-09-21 - Added `references/finding-model-schema.md`, migrating the findingmodels prose schema and reconciling it field by field against the released Pydantic definitions, with the unmerged metadata fields recorded separately as work edge. +* 2026-09-21 - Added `references/anatomic-location-json-schema.md`, documenting both anatomic location record formats and naming the anatomic-locations package format as current. +* 2026-09-21 - Added `references/exam-finding-list-example.md` and `references/imaging-problem-list-example.md`, two worked examples built from the same synthetic patient in the imaging-problem-list sample data. +* 2026-09-21 - Added `references/oifm-overview-extract.md` and `references/oifm-metadata-fields-extract.md`, near-verbatim extracts of the two OIFM notes on the ACR-RSNA-CDEs next-gen-2026 branch. +* 2026-09-21 - Added `references/cde-schema-differences.md`, migrating the 2023 schema comparison and recording what the later JSON Schema versions changed. +* 2026-09-21 - Listed all seven documents plus the status update deck extract in `references/index.md`. +* 2026-09-21 - Added `semantic-foundation/anatomic-locations/why-anatomic-locations.md`, stating the curation rationale from the project site and tabulating every place anatomic location identifiers are used across OIDM. +* 2026-09-21 - Added `semantic-foundation/anatomic-locations/data-model.md`, describing the record in both lineages with counts verified against both data files, and naming the empty classification fields and the self-referential part-of records. +* 2026-09-21 - Added `semantic-foundation/anatomic-locations/laterality-conventions.md`, migrating the curation conventions from the findingmodel curation skill and joining them to the assignment-time laterality rules from the extraction platform. +* 2026-09-21 - Added `semantic-foundation/anatomic-locations/lineage-and-current-implementation.md`, contrasting the two lineages, naming the anatomic-locations package as current per the build plan, and listing what is unreconciled. +* 2026-09-21 - Added `semantic-foundation/anatomic-locations/radlex-integration.md`, covering RadLex structure, the concept-search work, RSNA/RadLex issue 3, the overlay agreement recorded on the next-gen-2026 branch, and the node request list. +* 2026-09-21 - Added `semantic-foundation/anatomic-locations/tooling.md`, a usage guide for the anatomic-locations package and CLI, the two wrapper libraries, and the raw JSON download. +* 2026-09-21 - Listed all six documents in `semantic-foundation/anatomic-locations/index.md`. +* 2026-09-21 - Added `semantic-foundation/common-data-elements/cdes-and-radelement.md`, describing the CDE set, element, and value model, the RadElement registry and its public API counts, the two repositories that carry the canonical JSON Schema, the RelaxNG-versus-API gap, the CDE-labeled FHIR Observation pattern, and the four ways OIDM uses CDEs today. +* 2026-09-21 - Added `semantic-foundation/common-data-elements/cde-staging.md`, profiling the CDEStaging repository: its file census, its two unreconciled markdown authoring conventions, the report-representation gap analyses, the chest CT prioritization roadmap, the manual path to RadElement, the unmerged conversion pipeline in findingmodels, and its branch and issue state. +* 2026-09-21 - Added `semantic-foundation/common-data-elements/next-generation-vocabulary.md`, recording what the ACR-RSNA-CDEs next-gen-2026 branch defines at commit 44836c1, marked throughout as in-progress design work in an allied project rather than an OIDM specification. +* 2026-09-21 - Listed all three documents in `semantic-foundation/common-data-elements/index.md`. +* 2026-09-21 - Nothing in these three documents is a verbatim migration; catalogs and definition corpora are described and linked, not copied. The one partial migration is recorded in the ledger. +* Added `semantic-foundation/exam-types/overview.md`: exam types as a greenfield area, the goals stated by the project lead in the 2026-09-20 planning interview, the same goal as stated in the 2023 anatomic locations roadmap and the 2024-01-25 site post, what the LOINC/RSNA Radiology Playbook is, and the three places an exam type code does work today. +* Added `semantic-foundation/exam-types/existing-building-blocks.md`: the eight existing building blocks a future exam type layer could use, including the `radiology` profile design, LOINC detection in `molu`, 997 Playbook provenance annotations in the RadLex OWL file, the eight distinct LOINC codes in the imaging-problem-list sample data with counts, the viewer display table, the exam-to-region fallback map, the 281-template RadReport corpus, and the CDE set schema's LOINC allowance. +* Added `semantic-foundation/terminologies/ontologies-used.md`: all eleven external terminologies OIDM touches, with governance, identifier form, the field that carries each one, verified occurrence counts across the finding model corpus and both anatomic location datasets, and which tool resolves each. +* Added `semantic-foundation/terminologies/med-ontology-lookup.md`: the `molu` library and CLI, its two providers, six operations, typed provider failure model, agent skill, six-phase design and twelve-issue backlog (one tracking issue plus eleven focused ones), delegated authentication design, status at `main` commit `a1fd3ae`, and its still-undocumented relationship to `findingmodel`. +* Filled `semantic-foundation/exam-types/index.md` and `semantic-foundation/terminologies/index.md`. +* 2026-09-21 - Added `semantic-foundation/finding-models/why-finding-models.md`, the narrative case for findings as the unit of data, drawn from the OIFM overview note, the January 2026 deck, and the 2023 site post. +* 2026-09-21 - Added `semantic-foundation/finding-models/finding-model-format.md`, the released record format class by class with a real definition as example, plus a labeled section for the eight unreleased metadata fields on `feature/metadata-cleanup`. +* 2026-09-21 - Added `semantic-foundation/finding-models/identifiers.md`, the OIFM and OIFMA grammar, the organization-code registry with corpus counts, the minting and duplicate-check mechanism, and the round-trip regeneration defect filed as findingmodel issue 42. +* 2026-09-21 - Added `semantic-foundation/finding-models/content-catalog.md`, profiling the `findingmodels` repository: 2,382 definitions by source organization, the generated artifacts, the repository conventions, three ways to browse, and six unmerged content branches with counts and dates. +* 2026-09-21 - Added `semantic-foundation/finding-models/finding-taxonomies.md`, the MGB exam-oriented sub-taxonomies on `taxonomy-export-2026-08-15`: columns, hierarchy semantics, 3,789 rows with 1,028 matched, and the 2,761-row triage the README names as next. +* 2026-09-21 - Added `semantic-foundation/finding-models/authoring-workflow.md`, covering Finding Model Forge, the `findingmodel-ai` command line, the three agent skills and their prompt fragments, the pre-commit validator gate, and the two open conventions plans. +* 2026-09-21 - Added `semantic-foundation/finding-models/enrichment-pipeline.md`, separating the shipped sidecar enrichment on `main` from the canonical seven-agent rewrite on the work edge, and recording the branch's own FAIL readiness on age profile and index codes. +* 2026-09-21 - Added `semantic-foundation/finding-models/finding-models-and-cdes.md`, the workbench relationship, how CDE-sourced models entered the corpus, CDEStaging as the informal path, and the IHE IDR grammar both vocabularies must fit. +* 2026-09-21 - Listed all eight documents in `semantic-foundation/finding-models/index.md`. +* 2026-09-21 - Applied the project lead's naming decision: the `lists/` hierarchies are the "MGB exam-oriented sub-taxonomies" on first use and "the sub-taxonomies" after. Retitled `finding-taxonomies.md`, updated its description and source titles, and updated the four references in `content-catalog.md`, `identifiers.md`, and the directory index. +* 2026-09-21 - Added `data-structures/hierarchy.md`, the four-level ladder from Observation to Imaging Persona, with the identifier joins and a per-level implementation status table. +* 2026-09-21 - Added `data-structures/observation.md`, covering the Exam Finding List entry shape, the presence dot codes, the extraction-time Pydantic models on the dev branch and how they map to a coded observation, the FHIR Observation precedent, and issue 1's call for a formal model. +* 2026-09-21 - Added `data-structures/exam-finding-list.md` and `data-structures/imaging-problem-list.md`, with full field tables for both branches, the grouping key change on dev, the four-state computed temporal status, both README diagrams described in words, and the documented but unimplemented FHIR mappings. +* 2026-09-21 - Added `data-structures/imaging-persona.md`, stating it as concept-only from the deck and recording the IPL-MVP longitudinal FHIR persistence note as a related unimplemented idea. +* 2026-09-21 - Migrated `docs/anatomic-location-assignment-rules.md` and `docs/technical-imaging-findings.md` from the imaging-problem-list dev branch as References. +* 2026-09-21 - Added `data-structures/fhir-mapping.md`, collecting the CDE-labeled FHIR Observation pattern, the lineage samples and Pydantic class, both current mappings, the raw PowerScribe input samples, and the evidence that no current code emits FHIR. +* 2026-09-21 - Added `data-structures/ihe-idr-alignment.md`, describing the IDR observation grammar from the next-gen-2026 extract and the four recorded differences from OIDM's encodings. +* 2026-09-21 - Added `data-structures/sample-data.md`, with per-branch counts for the synthetic patients, the file layouts, the viewer bundles, and the four generator scripts. +* 2026-09-21 - Listed all ten documents in `data-structures/index.md`. +* 2026-09-21 - Added `applications/finding-model-forge.md`, profiling the authoring application at fmf.oidm.org: the two-step creation and draft-edit wizard, the five-status draft lifecycle, MongoDB and Redis, the three generation steps with their timeout budgets, the dev refactor and the model-iteration sprint answering issue 13, and the superseded application's planning notes as history. +* 2026-09-21 - Added `applications/finding-models-site.md`, profiling the static Astro catalog: the git submodule coupling to the content repository, the build-time loader, the GitHub Pages workflow, and the live URL verified 2026-09-21. +* 2026-09-21 - Added `applications/imaging-problem-list-viewer.md`, covering the deployed viewer on `main` (three-level drill-down, four computed status sections, region filter, popovers, exam-type display mapping, static data layout, Cloudflare Pages) and `viewer_v2` on `dev` (React and Vite anatomy-first dashboard, presentation-only anatomy grouping, conservative evidence highlighting, generated-data contract, stated caveats, undeployed). +* 2026-09-21 - Added `applications/report-extraction-platform.md`, profiling the `dev`-branch platform: chunked multi-provider extraction, the extraction schema, verbatim-quote validation with the validator's six problem types, SQLModel persistence with corrections, TaskIQ jobs behind FastAPI, the two review interfaces, the evaluation harness and its honesty-first descope, the local-only PHI stance, and the eight active plans with dates. +* 2026-09-21 - Added `applications/finding-and-location-coding.md`, the coding pipeline as documented on `dev`: fast-path index lookup, four agents across term generation and selection, the coding bundle schema and unresolved-reason vocabularies, the anatomic location precedence ladder in summary, the review CSV, and the two archived earlier designs. +* 2026-09-21 - Added `applications/ipl-mvp-extraction.md`, profiling the 2025 four-stage prototype: the thin schema, embedding similarity at a 0.70 threshold, the measured 5 of 87 match rate, the stated limitations, the three unmerged experiment branches, and what carried forward. +* 2026-09-21 - Added `applications/reporting-sdk.md`, recording the SDK as concept only: the deck's applications pillar and call to action, the 2023 plugin-container post in detail, the About page's three-language promise, and the CDETemplateDemo rendering precedent. +* 2026-09-21 - Listed all seven documents in `applications/index.md`. +* **Creation**: bundle skeleton, directory indexes, authoring guide - [authoring guide](/guides/authoring-guide.md). +* **Creation**: knowledgebase build plan - [plan](/references/project-direction.md). diff --git a/archive/knowledge/overview/architecture.excalidraw b/archive/knowledge/overview/architecture.excalidraw new file mode 100644 index 0000000..f097d57 --- /dev/null +++ b/archive/knowledge/overview/architecture.excalidraw @@ -0,0 +1,1823 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "text", + "id": "band_app", + "x": 80, + "y": 16, + "width": 97.44, + "height": 17.5, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "APPLICATIONS", + "originalText": "APPLICATIONS", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "forge", + "x": 0, + "y": 40, + "width": 190, + "height": 100, + "strokeColor": "#1e3a5f", + "backgroundColor": "#60a5fa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "forge_t", + "type": "text" + }, + { + "id": "a_forge_fm", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "forge_t", + "x": 10, + "y": 50, + "width": 170, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Finding Model Forge\nauthoring wizard\nfmf.oidm.org", + "originalText": "Finding Model Forge\nauthoring wizard\nfmf.oidm.org", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "forge", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "extract", + "x": 296, + "y": 40, + "width": 190, + "height": 100, + "strokeColor": "#1e3a5f", + "backgroundColor": "#60a5fa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "extract_t", + "type": "text" + }, + { + "id": "a_extract_efl", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "extract_t", + "x": 306, + "y": 50, + "width": 170, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Report extraction\nplatform\nLLM extraction\nand coding (dev)", + "originalText": "Report extraction\nplatform\nLLM extraction\nand coding (dev)", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "extract", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "viewer", + "x": 512, + "y": 40, + "width": 190, + "height": 100, + "strokeColor": "#1e3a5f", + "backgroundColor": "#60a5fa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "viewer_t", + "type": "text" + }, + { + "id": "a_ipl_viewer", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "viewer_t", + "x": 522, + "y": 50, + "width": 170, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Imaging Problem\nList viewer\nimaging-problem-\nlist.pages.dev", + "originalText": "Imaging Problem\nList viewer\nimaging-problem-\nlist.pages.dev", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "viewer", + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "div1", + "x": 0, + "y": 224, + "width": 702, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 702, + 0 + ] + ] + }, + { + "type": "text", + "id": "band_data", + "x": 80, + "y": 230, + "width": 121.8, + "height": 17.5, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "DATA STRUCTURES", + "originalText": "DATA STRUCTURES", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "obs", + "x": 80, + "y": 315, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "obs_t", + "type": "text" + }, + { + "id": "a_obs_efl", + "type": "arrow" + }, + { + "id": "a_fm_obs", + "type": "arrow" + }, + { + "id": "a_al_obs", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "obs_t", + "x": 90, + "y": 325, + "width": 170, + "height": 60, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Observation\nfinding + location\n+ attributes", + "originalText": "Observation\nfinding + location\n+ attributes", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "obs", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "efl", + "x": 296, + "y": 315, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "efl_t", + "type": "text" + }, + { + "id": "a_obs_efl", + "type": "arrow" + }, + { + "id": "a_efl_ipl", + "type": "arrow" + }, + { + "id": "a_extract_efl", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "efl_t", + "x": 306, + "y": 325, + "width": 170, + "height": 60, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Exam Finding List\nall observations\nof one exam", + "originalText": "Exam Finding List\nall observations\nof one exam", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "efl", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ipl", + "x": 512, + "y": 315, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ipl_t", + "type": "text" + }, + { + "id": "a_efl_ipl", + "type": "arrow" + }, + { + "id": "a_ipl_viewer", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ipl_t", + "x": 522, + "y": 325, + "width": 170, + "height": 60, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Imaging Problem List\none patient, grouped\nby finding", + "originalText": "Imaging Problem List\none patient, grouped\nby finding", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ipl", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "a_obs_efl", + "x": 270, + "y": 355.0, + "width": 26, + "height": 0.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 26, + 0.0 + ] + ], + "startBinding": { + "elementId": "obs", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "efl", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_obs_efl_l", + "x": 250.52, + "y": 267.0, + "width": 64.96, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "per exam", + "originalText": "per exam", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "a_efl_ipl", + "x": 486, + "y": 355.0, + "width": 26, + "height": 0.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 26, + 0.0 + ] + ], + "startBinding": { + "elementId": "efl", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ipl", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_efl_ipl_l", + "x": 454.34000000000003, + "y": 267.0, + "width": 89.32, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "per patient", + "originalText": "per patient", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "a_extract_efl", + "x": 391.0, + "y": 140, + "width": 0.0, + "height": 175, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 175 + ] + ], + "startBinding": { + "elementId": "extract", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "efl", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_extract_efl_l", + "x": 338.22, + "y": 177.5, + "width": 105.55999999999999, + "height": 35.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "extracts from\nreports", + "originalText": "extracts from\nreports", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "a_ipl_viewer", + "x": 607.0, + "y": 315, + "width": 0.0, + "height": -175, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + -175 + ] + ], + "startBinding": { + "elementId": "ipl", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "viewer", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_ipl_viewer_l", + "x": 578.58, + "y": 195.5, + "width": 56.839999999999996, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "renders", + "originalText": "renders", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "persona", + "x": 512, + "y": 455, + "width": 190, + "height": 80, + "strokeColor": "#64748b", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "persona_t", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "persona_t", + "x": 522, + "y": 465, + "width": 170, + "height": 60, + "strokeColor": "#475569", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Imaging Persona\nplus clinical\ncontext (concept)", + "originalText": "Imaging Persona\nplus clinical\ncontext (concept)", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "persona", + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "persona_note", + "x": 512, + "y": 549, + "width": 188.5, + "height": 32.5, + "strokeColor": "#475569", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "goal: extends the Imaging\nProblem List with context", + "originalText": "goal: extends the Imaging\nProblem List with context", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "div2", + "x": 0, + "y": 619, + "width": 702, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 702, + 0 + ] + ] + }, + { + "type": "text", + "id": "band_found", + "x": 80, + "y": 625, + "width": 154.28, + "height": 17.5, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1055, + "version": 1, + "versionNonce": 1056, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SEMANTIC FOUNDATION", + "originalText": "SEMANTIC FOUNDATION", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "fm", + "x": 80, + "y": 649, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1057, + "version": 1, + "versionNonce": 1058, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "fm_t", + "type": "text" + }, + { + "id": "a_fm_obs", + "type": "arrow" + }, + { + "id": "a_forge_fm", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "fm_t", + "x": 90, + "y": 659, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1059, + "version": 1, + "versionNonce": 1060, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Finding models\nOIFM_ / OIFMA_\ncodes", + "originalText": "Finding models\nOIFM_ / OIFMA_\ncodes", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fm", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "al", + "x": 296, + "y": 649, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1061, + "version": 1, + "versionNonce": 1062, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "al_t", + "type": "text" + }, + { + "id": "a_al_obs", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "al_t", + "x": 306, + "y": 659, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1063, + "version": 1, + "versionNonce": 1064, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Anatomic locations\nRadLex RID codes", + "originalText": "Anatomic locations\nRadLex RID codes", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "al", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "et", + "x": 512, + "y": 649, + "width": 190, + "height": 80, + "strokeColor": "#64748b", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1065, + "version": 1, + "versionNonce": 1066, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "et_t", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "et_t", + "x": 522, + "y": 659, + "width": 170, + "height": 60, + "strokeColor": "#475569", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1067, + "version": 1, + "versionNonce": 1068, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Exam types\nLOINC / Playbook\n(planned)", + "originalText": "Exam types\nLOINC / Playbook\n(planned)", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "et", + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "et_note", + "x": 512, + "y": 743, + "width": 135.72, + "height": 48.75, + "strokeColor": "#475569", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1069, + "version": 1, + "versionNonce": 1070, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "supplies the LOINC\nexam code on the\nExam Finding List", + "originalText": "supplies the LOINC\nexam code on the\nExam Finding List", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "a_fm_obs", + "x": 254.8, + "y": 649, + "width": 0.0, + "height": -254, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1071, + "version": 1, + "versionNonce": 1072, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + -254 + ] + ], + "startBinding": { + "elementId": "fm", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "obs", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_fm_obs_l", + "x": 140.14000000000001, + "y": 482.0, + "width": 89.32, + "height": 35.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1073, + "version": 1, + "versionNonce": 1074, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "OIFM and\nOIFMA codes", + "originalText": "OIFM and\nOIFMA codes", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "a_al_obs", + "x": 391.0, + "y": 649, + "width": -159.0, + "height": -254, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1075, + "version": 1, + "versionNonce": 1076, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -159.0, + -254 + ] + ], + "startBinding": { + "elementId": "al", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "obs", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_al_obs_l", + "x": 343.32, + "y": 482.0, + "width": 24.36, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1077, + "version": 1, + "versionNonce": 1078, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "RID", + "originalText": "RID", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "a_forge_fm", + "x": 19.0, + "y": 140, + "width": 61.0, + "height": 549.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1079, + "version": 1, + "versionNonce": 1080, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 549.0 + ], + [ + 61.0, + 549.0 + ] + ], + "startBinding": { + "elementId": "forge", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fm", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "a_forge_fm_l", + "x": 11.080000000000002, + "y": 434.5, + "width": 56.839999999999996, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1081, + "version": 1, + "versionNonce": 1082, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "authors", + "originalText": "authors", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "cde", + "x": 80, + "y": 789, + "width": 190, + "height": 100, + "strokeColor": "#c2410c", + "backgroundColor": "#fed7aa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1083, + "version": 1, + "versionNonce": 1084, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "cde_t", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "cde_t", + "x": 90, + "y": 799, + "width": 170, + "height": 80, + "strokeColor": "#7c2d12", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1085, + "version": 1, + "versionNonce": 1086, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "ACR/RSNA CDEs at\nRadElement\nRDES / RDE codes\n(allied project)", + "originalText": "ACR/RSNA CDEs at\nRadElement\nRDES / RDE codes\n(allied project)", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "cde", + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "cde_note", + "x": 296, + "y": 807, + "width": 263.9, + "height": 32.5, + "strokeColor": "#7c2d12", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1087, + "version": 1, + "versionNonce": 1088, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "finding models graduate to CDEs;\nCDE definitions seed finding models", + "originalText": "finding models graduate to CDEs;\nCDE definitions seed finding models", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "terms", + "x": 0, + "y": 929, + "width": 603.1999999999999, + "height": 32.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1089, + "version": 1, + "versionNonce": 1090, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Terminologies underneath: RadLex, SNOMED CT, FMA, UMLS, MeSH, LOINC and the RSNA\nRadiology Playbook. Looked up through med-ontology-lookup.", + "originalText": "Terminologies underneath: RadLex, SNOMED CT, FMA, UMLS, MeSH, LOINC and the RSNA\nRadiology Playbook. Looked up through med-ontology-lookup.", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "legend_t", + "x": 0, + "y": 983.5, + "width": 45.239999999999995, + "height": 16.25, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1091, + "version": 1, + "versionNonce": 1092, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Legend", + "originalText": "Legend", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg0", + "x": 0, + "y": 1007.5, + "width": 18, + "height": 14, + "strokeColor": "#1e3a5f", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1093, + "version": 1, + "versionNonce": 1094, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg0_t", + "x": 26, + "y": 1005.5, + "width": 203.57999999999998, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1095, + "version": 1, + "versionNonce": 1096, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "solid outline: exists today", + "originalText": "solid outline: exists today", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg1", + "x": 0, + "y": 1029.5, + "width": 18, + "height": 14, + "strokeColor": "#64748b", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1097, + "version": 1, + "versionNonce": 1098, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg1_t", + "x": 26, + "y": 1027.5, + "width": 316.67999999999995, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1099, + "version": 1, + "versionNonce": 1100, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "dashed outline: documented or planned only", + "originalText": "dashed outline: documented or planned only", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg2", + "x": 0, + "y": 1051.5, + "width": 18, + "height": 14, + "strokeColor": "#c2410c", + "backgroundColor": "#fed7aa", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1101, + "version": 1, + "versionNonce": 1102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg2_t", + "x": 26, + "y": 1049.5, + "width": 233.73999999999998, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1103, + "version": 1, + "versionNonce": 1104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "orange: allied external project", + "originalText": "orange: allied external project", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": {} +} \ No newline at end of file diff --git a/knowledge/overview/architecture.md b/archive/knowledge/overview/architecture.md similarity index 54% rename from knowledge/overview/architecture.md rename to archive/knowledge/overview/architecture.md index 5c34683..93c8a9a 100644 --- a/knowledge/overview/architecture.md +++ b/archive/knowledge/overview/architecture.md @@ -4,7 +4,7 @@ title: Architecture description: How the semantic foundation, data structures, and applications fit together, which identifier system joins each pair, and what is actually built. tags: [overview, architecture, identifiers] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T15:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } sources: - id: deck resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal @@ -49,57 +49,29 @@ sources: # The shape of the system -OIDM is three layers stacked on each other, and the joints between them are identifiers. The semantic foundation defines what things are and gives each one a code. The data structures carry instances of those things, referring to them only by code. The applications create and read those structures. Nothing in the upper layers embeds a definition; everything points. - -This is what makes the system work across organizations. Two systems can agree that a finding is `OIFM_GMTS_016552` located at `RID42239` without sharing any code, any database, or any vendor. - -```mermaid -flowchart TB - subgraph SF["Semantic foundation: what things are"] - FM["Finding models
OIFM_XXXX_###### / OIFMA_XXXX_######"] - AL["Anatomic locations
RadLex RIDs"] - CDE["Common data elements
RDES### sets / RDE#### elements"] - ET["Exam types
LOINC codes"] - end - subgraph DS["Data structures: what was seen"] - OBS["Observation
finding + location + attribute values"] - EFL["Exam Finding List
one exam"] - IPL["Imaging Problem List
one patient, grouped by finding"] - IP["Imaging Persona
clinical context around the IPL"] - end - subgraph APP["Applications: what makes and reads them"] - FORGE["Finding Model Forge"] - CAT["Finding model catalog site"] - EXTR["Report extraction and coding platform"] - VIEW["IPL viewer"] - MOLU["molu terminology lookup"] - end - FM -->|findingCode, attributeCode| OBS - AL -->|anatomicLocation.locationId| OBS - ET -->|examInfo.studyLoincCode| EFL - CDE -.->|graduation target| FM - OBS --> EFL --> IPL --> IP - FORGE -->|authors| FM - CAT -->|browses| FM - EXTR -->|produces| EFL - VIEW -->|renders| IPL - MOLU -->|resolves codes for| FM - MOLU -->|resolves codes for| AL -``` +OIDM has three layers joined by identifiers. The semantic foundation defines concepts and assigns codes. Data structures hold instances that reference those definitions by code. Applications create and read the structures without embedding definitions. + +Shared identifiers let systems agree that a finding is `OIFM_GMTS_016552` at `RID42239` without sharing code, databases, or vendors. + +[![Open Imaging Data Model: three layers joined by identifiers. Applications on top, data structures in the middle, semantic foundation below, with arrows showing which codes join them.](./architecture.svg)](./architecture.svg) + +Also in the applications layer, not drawn: the finding model catalog site, the terminology lookup library, and the Open Imaging Reporting SDK, which remains a concept with no artifact. + +*Diagram source: `architecture.excalidraw` beside this document (click the image for full size), generated by `tools/diagrams/build_architecture.py`.* # The semantic foundation -A [finding model](/glossary/finding-model.md) is the definition of one radiology finding: a name, a description, synonyms, tags, optional anatomic locations, optional [index codes](/glossary/index-code.md) into external ontologies, and a list of [attributes](/glossary/attribute.md) that a radiologist would use to characterize it. Each carries an [OIFM identifier](/glossary/oifm.md) matching `OIFM_[A-Z]{3,4}_[0-9]{6}`, where the letter block identifies the contributing organization. Each attribute carries an `OIFMA_[A-Z]{3,4}_[0-9]{6}` identifier, and each choice value extends its attribute identifier with a dot suffix, so `OIFMA_GMTS_707209.1` is a specific value of a specific attribute of a specific finding.[^fm-model] A flat registry file maps every issued identifier to its definition file, which is how duplicates are caught.[^fm-ids] +A [finding model](/glossary/finding-model.md) defines one radiology finding with a name, description, synonyms, tags, and [attributes](/glossary/attribute.md) that characterize it. It can include anatomic locations and [index codes](/glossary/index-code.md) into external ontologies. Its [OIFM identifier](/glossary/oifm.md) matches `OIFM_[A-Z]{3,4}_[0-9]{6}`, with the letter block identifying the contributing organization. Attribute identifiers match `OIFMA_[A-Z]{3,4}_[0-9]{6}`. Choice values add a dot suffix to the attribute identifier, as in `OIFMA_GMTS_707209.1`.[^fm-model] A flat registry maps issued identifiers to definition files to detect duplicates.[^fm-ids] -[Anatomic locations](/glossary/anatomic-location.md) are keyed by RadLex identifiers of the form `RID####`. Sided structures are composite, as in `RID294_RID5824` for the left variant of the uterine adnexa. Two hierarchies are carried, ["contained by" and "part of"](/glossary/contained-by-and-part-of.md), together with a [laterality](/glossary/laterality.md) triad linking unsided, left, and right variants, and cross-references to SNOMED CT, FMA, UMLS, and MESH.[^anat-docs] +[Anatomic locations](/glossary/anatomic-location.md) use RadLex identifiers of the form `RID####`. Sided structures use composite identifiers, such as `RID294_RID5824` for the left uterine adnexa. Locations have ["contained by" and "part of"](/glossary/contained-by-and-part-of.md) hierarchies, [laterality](/glossary/laterality.md) links between unsided, left, and right variants, and cross-references to SNOMED CT, FMA, UMLS, and MESH.[^anat-docs] -[Common data elements](/glossary/cde.md) are the formally governed counterpart, published by the ACR and RSNA through RadElement with `RDES###` set identifiers and `RDE####` element identifiers. They are external to OIDM. The relationship runs the other way from the one people expect: finding models are the fast-moving workbench, and proven definitions are candidates to graduate into common data elements.[^deck] +[Common data elements](/glossary/cde.md) are external to OIDM, governed by the ACR and RSNA, and published through RadElement. Sets use `RDES###` identifiers, and elements use `RDE####`. Finding models support experimentation, with proven definitions becoming candidates for common data elements.[^deck] -[Exam types](/glossary/exam-type.md) are coded with LOINC. The Exam Finding List requires a LOINC code on its exam header, and the only written design for a richer exam-type capability, one that would weight the LOINC and RSNA Radiology Playbook vocabulary for orderables and carry Playbook correspondences in crosswalk provenance, lives in the terminology lookup roadmap.[^molu-roadmap] +[Exam types](/glossary/exam-type.md) use LOINC codes, which the Exam Finding List requires in its exam header. The terminology lookup roadmap contains the only written design for richer exam-type support. It proposes weighting LOINC and RSNA Radiology Playbook vocabulary for orderables and recording Playbook correspondences in crosswalk provenance.[^molu-roadmap] # The data structures -An [Observation](/glossary/observation.md) is the atomic unit: what, where, and how. The deck states it as a finding tag plus an anatomic location plus lesion characteristics, carrying presence indicators, change from prior, and measurements.[^deck] In the Exam Finding List JSON this appears as a `findingCode` with its `findingDescription`, an optional `anatomicLocation` object holding a RadLex identifier and display text, and an `attributes` array of attribute code, value code, and descriptions. +An [Observation](/glossary/observation.md) records a finding tag, anatomic location, and lesion characteristics, including presence, change from prior, and measurements.[^deck] The Exam Finding List JSON represents these as `findingCode` and `findingDescription`, an optional `anatomicLocation` with a RadLex identifier and display text, and an `attributes` array with attribute codes, value codes, and descriptions. ```json { @@ -118,17 +90,19 @@ An [Observation](/glossary/observation.md) is the atomic unit: what, where, and } ``` -An [Exam Finding List](/glossary/exam-finding-list.md) wraps all observations from one exam with a report identifier, patient information, and exam information including the LOINC-coded exam type. Repeated instances of the same finding get separate entries, each with its own observation identifier, so three kidney stones are three observations rather than one with a count.[^ipl-main] +An [Exam Finding List](/glossary/exam-finding-list.md) collects an exam's observations with a report identifier, patient information, and exam information, including the LOINC-coded exam type. Repeated instances of the same finding get separate entries, each with its own observation identifier, so three kidney stones are three observations rather than one with a count.[^ipl-main] -An [Imaging Problem List](/glossary/imaging-problem-list.md) reorganizes observations across a patient's exams by finding rather than by date, which is what makes the question "was finding X present on the most recent study" a lookup instead of a search.[^deck] An [Imaging Persona](/glossary/imaging-persona.md) is the stated goal of surrounding that with clinical context, medical baseline, specialized history, and surgical history. It exists in the deck and nowhere else. +An [Imaging Problem List](/glossary/imaging-problem-list.md) groups observations across a patient's exams by finding to answer "was finding X present on the most recent study" with a lookup.[^deck] An [Imaging Persona](/glossary/imaging-persona.md) would add clinical context, medical baseline, specialized history, and surgical history. It is a goal recorded only in the deck. -The 2024 site post describes the surrounding model in wider terms, listing observations, current report text, imaging studies, patient, order, prior studies, tracked observations, and electronic health record data as the elements OIDM organizes over FHIR.[^site-structure] +The 2024 site post describes a broader model organized over FHIR. It includes observations, current report text, imaging studies, patient, order, prior studies, tracked observations, and electronic health record data.[^site-structure] # The applications -Finding Model Forge is the authoring front end: sign in, name a finding, generate a description, review it, generate attributes, and save a draft that moves through a submission and review lifecycle.[^forge] The catalog site reads finding model definitions directly out of the content repository at build time and renders browsable pages. The report extraction and coding platform takes narrative report text, extracts findings in chunks, validates against verbatim quotes, and assigns finding and location codes in a separate post-extraction coding pass.[^ipl-dev] The viewer renders Imaging Problem Lists, with a second-generation anatomy-aware dashboard. The `molu` terminology lookup library resolves terms and codes across RadLex, SNOMED CT, FMA, LOINC, and UMLS, which is the infrastructure the enrichment and coding work depends on. +Finding Model Forge supports authoring and review. Authors sign in, name a finding, generate and review a description, generate attributes, and save a draft for submission and review.[^forge] The catalog site reads definitions from the content repository at build time and creates browsable pages. + +The report extraction and coding platform extracts findings from chunks of narrative report text and validates them against verbatim quotes. A separate pass assigns finding and location codes.[^ipl-dev] The rule that keeps the coding honest is stated in the assignment rules document on the extraction platform's development branch: an anatomic location is attached only when the report localizes the finding, and is omitted rather than guessed when it does not.[^ipl-anat-rules] -The rule that keeps the coding honest is stated in the assignment rules document on the extraction platform's development branch: an anatomic location is attached only when the report localizes the finding, and is omitted rather than guessed when it does not.[^ipl-anat-rules] +The viewer displays Imaging Problem Lists and has a second-generation anatomy-aware dashboard. The `molu` library supports enrichment and coding through term and code lookup across RadLex, SNOMED CT, FMA, LOINC, and UMLS. # Implementation status @@ -158,7 +132,7 @@ The layers are at very different stages. This table states where each piece actu | Open Imaging Reporting SDK | Concept only | Named as a strategic pillar in the deck; no repository or artifact exists[^deck] | | ACR-OIDM working group | Proposed | Stated as a call to action in the deck; not recorded as convened[^deck] | -Per-area goals and the sources that state them are collected under [roadmap](/roadmap/). Every repository, its role, and its branch of record are listed in [the repository map](/repositories/repository-map.md). +See [roadmap](/roadmap/) for goals and their sources, and [the repository map](/repositories/repository-map.md) for repository roles and branches of record. [^deck]: Open Imaging Data Model 2026 Status Update, January 2026 [^fm-model]: FindingModelFull definition, findingmodel repository, main diff --git a/archive/knowledge/overview/architecture.svg b/archive/knowledge/overview/architecture.svg new file mode 100644 index 0000000..2676e81 --- /dev/null +++ b/archive/knowledge/overview/architecture.svg @@ -0,0 +1,2 @@ +APPLICATIONSFinding Model Forgeauthoring wizardfmf.oidm.orgReport extractionplatformLLM extractionand coding (dev)Imaging ProblemList viewerimaging-problem-list.pages.devDATA STRUCTURESObservationfinding + location+ attributesExam Finding Listall observationsof one examImaging Problem Listone patient, groupedby findingper examper patientextracts fromreportsrendersImaging Personaplus clinicalcontext (concept)goal: extends the ImagingProblem List with contextSEMANTIC FOUNDATIONFinding modelsOIFM_ / OIFMA_codesAnatomic locationsRadLex RID codesExam typesLOINC / Playbook(planned)supplies the LOINCexam code on theExam Finding ListOIFM andOIFMA codesRIDauthorsACR/RSNA CDEs atRadElementRDES / RDE codes(allied project)finding models graduate to CDEs;CDE definitions seed finding modelsTerminologies underneath: RadLex, SNOMED CT, FMA, UMLS, MeSH, LOINC and the RSNARadiology Playbook. Looked up through med-ontology-lookup.Legendsolid outline: exists todaydashed outline: documented or planned onlyorange: allied external project \ No newline at end of file diff --git a/archive/knowledge/overview/getting-involved.md b/archive/knowledge/overview/getting-involved.md new file mode 100644 index 0000000..5e6de68 --- /dev/null +++ b/archive/knowledge/overview/getting-involved.md @@ -0,0 +1,92 @@ +--- +type: Guide +title: Getting involved +description: Where the OIDM community gathers, which repositories to start with, how to file issues, how content is contributed, and how this knowledgebase is edited. +tags: [overview, community, contributing] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +sources: + - id: site-involved + resource: https://www.openimagingdata.org/how-to-get-involved/ + title: "How to Get Involved, openimagingdata.org, 2023-08-14" + - id: deck + resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal + title: Open Imaging Data Model 2026 Status Update, January 2026 + - id: forge + resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/DRAFT_WORKFLOW.md + title: Draft workflow, FindingModelForge dev branch + - id: catalog-site + resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/src/pages/index.astro + title: Finding model catalog site home page, with its Submit Issue route + - id: fm-content + resource: https://github.com/openimagingdata/findingmodels/blob/4475ac1bcb591f1a0951b2082b59208def173a5d/schema/finding_model_schema.md + title: Finding model schema, prose mirror, findingmodels repository +--- + +# Where the project gathers + +OIDM's work is public. These channels provide access to its code, content, tools, and discussions. + +| Channel | Where | What it is for | +|---|---|---| +| GitHub organization | | Code, content, specifications, issue trackers | +| Project site | | Articles explaining the model, project news, registration for updates | +| Slack | The Radiology Informatics workspace at , channel `#openimagingdata` | Discussion between meetings | +| Finding Model Forge | | Authoring finding models through a browser | +| IPL viewer demo | | Seeing an Imaging Problem List rendered from real sample data | + +Use GitHub and Slack for current discussions. The site's August 2023 participation post invites newsletter registration and article comments, describes use-case and standards working groups, and seeks related projects and venues.[^site-involved] Some details have aged. The advertised working group on the TypeScript reference implementation concerns a repository now kept for lineage. The meeting section remains a placeholder. The site also has a contact link for general inquiries. + +The January 2026 deck proposes an "ACR-OIDM Structured Imaging Results Working Group" co-hosted with the ACR. It would bring together experts and vendors, with a use-case pipeline feeding the ACR and RSNA common data element group.[^deck] The group is not recorded as convened. + +# Repositories to start with + +Choose a repository by task. [The repository map](/repositories/repository-map.md) lists all repositories, including lineage projects, with their status and branches of record. + +| If you want to | Start at | +|---|---| +| Read or use the finding model content | `openimagingdata/findingmodels`, 2,382 definitions with a prose schema mirror | +| Build against the finding model format | `openimagingdata/findingmodel`, the Python library, CLI tools, and MCP server | +| Understand the data structures | `openimagingdata/imaging-problem-list`, whose `main` branch holds the stable Exam Finding List and Imaging Problem List specification and sample data | +| See extraction from narrative reports | The same repository's `dev` branch, which holds the extraction, coding, persistence, and review platform | +| Look up codes across terminologies | `openimagingdata/med-ontology-lookup`, the `molu` library and CLI | +| Work on the authoring application | `openimagingdata/FindingModelForge` | + +Check the repository map before starting. Several repositories keep current work on branches other than `main`. + +# Filing issues + +File issues in the repository that owns the affected content or code. + +- Report wrong, missing, or badly scoped finding models to `openimagingdata/findingmodels`. The public catalog's "Submit Issue" link goes there.[^catalog-site] +- Report library, CLI, or anatomic location data problems to `openimagingdata/findingmodel`. +- Ask data structure questions in `openimagingdata/imaging-problem-list`. Its open, unclaimed issue #1 requests a formal model layer with JSON Schema export for Observation, Exam Finding List, and Imaging Problem List. +- Report terminology lookup problems to `openimagingdata/med-ontology-lookup`. + +See [roadmap](/roadmap/) for open issues by area. + +# Contributing content + +Content includes [finding models](/glossary/finding-model.md) and their [anatomic locations](/glossary/anatomic-location.md). Choose one of three contribution routes. + +**Finding Model Forge.** To author a single model in the browser, sign in with GitHub and name the finding. Generate and edit its description and synonyms, then generate attributes. The draft autosaves. Submission locks it for review, and a reviewer moves it through under review to added or declined.[^forge] Editing a model after creation is an open issue on that repository, not yet available. + +**The content repository.** Definitions are JSON files, one per finding, validated on commit. A registry issues identifiers and detects duplicates. Repository agent skills support batch authoring and review. Use the prose schema mirror when editing definitions directly.[^fm-content] + +**Staged common data elements.** Informal candidate definitions go into `openimagingdata/CDEStaging` before formal ACR and RSNA review and publication through RadElement. This is a manual curation workflow. + +Identifier discipline matters in all three routes. An [OIFM identifier](/glossary/oifm.md) encodes its contributing organization in its letter block, and identifiers are never reissued or regenerated on a round trip. See [the authoring workflow](/semantic-foundation/finding-models/authoring-workflow.md) for conventions. + +# Contributing to this knowledgebase + +This repository provides canonical high-level documentation as an Open Knowledge Format bundle for people and agents. It describes and links to catalogs; it does not copy them. Keep installation instructions, API usage, and developer workflows with the code. + +Read [the authoring guide](/guides/authoring-guide.md) before editing. It covers document types, frontmatter, draft verification, links, and document migration. Two checkers must pass on every push: the Open Knowledge Format validator and a house checker for types, index coverage, links, names, and email addresses. + +Two house rules catch newcomers. Facts and stated goals only, with every goal citing the deck, issue, or plan that states it, because design proposals belong to a separate workstream. And organizations, not individuals: the project lead is named as such and nobody else is named. + +[^site-involved]: "How to Get Involved", openimagingdata.org, 2023-08-14 +[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 +[^forge]: Draft workflow, FindingModelForge dev branch +[^catalog-site]: Finding model catalog site home page +[^fm-content]: Finding model schema, prose mirror, findingmodels repository diff --git a/knowledge/overview/index.md b/archive/knowledge/overview/index.md similarity index 100% rename from knowledge/overview/index.md rename to archive/knowledge/overview/index.md diff --git a/archive/knowledge/overview/vision.md b/archive/knowledge/overview/vision.md new file mode 100644 index 0000000..4e6adb2 --- /dev/null +++ b/archive/knowledge/overview/vision.md @@ -0,0 +1,94 @@ +--- +type: Concept +title: Vision +description: Why OIDM pursues object-oriented imaging results, what that unlocks across the imaging life cycle, and what the project has proposed next. +tags: [overview, vision, strategy] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +sources: + - id: deck + resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal + title: Open Imaging Data Model 2026 Status Update, January 2026 + - id: site-structure + resource: https://www.openimagingdata.org/data-model-structure-and-function/ + title: "Data Model: Structure and Function, 2024-01-25" + - id: site-findings + resource: https://www.openimagingdata.org/findings-cdes-and-observations/ + title: "Findings, CDEs, and Observations, 2023-06-24" + - id: site-benchmark + resource: https://www.openimagingdata.org/benchmarking-a-vision/ + title: "Benchmarking a Vision, 2024-07-01" + - id: site-plugin + resource: https://www.openimagingdata.org/oidm-based-next-gen-reporting-assistance/ + title: "OIDM-Based Next-gen Reporting Assistance Framework, 2023-07-16" + - id: fmf + resource: https://fmf.oidm.org + title: Finding Model Forge, the finding model authoring application +--- + +# Object-oriented imaging results + +The January 2026 status deck calls OIDM's goal "standardizing the DNA of imaging IT" and titles itself "Realizing Object-Oriented Imaging Results."[^deck] It proposes representing each finding as an object with a type, location, attributes, and an identity that persists across exams. + +The site posts describe a finding as "a standard format based on FHIR Observations semantically labeled with ACR/RSNA Common Data Element identifiers."[^site-findings] The wider model is "a superstructure on top of these FHIR definitions to both organize them and enable more straightforward programmatic access to the data." The post compares the layers: "the relationship between OIDM and FHIR might be that between the browser's DOM and HTML."[^site-structure] FHIR supplies resources, and OIDM supplies an object model for programmatic access. + +# What object-oriented results unlock + +The deck names three benefits.[^deck] + +**A unified reading toolset.** Picture archiving and communication systems, reporting software, and artificial intelligence tools each hold partial, private representations of exam results. A shared object model would make findings created in one tool readable by the others. + +**Integrated data sources.** A common representation would let systems merge, compare, and attribute statements from radiologists, electronic medical records, and artificial intelligence tools. The deck proposes validating artificial intelligence tools by correlating their observations with radiologist observations. + +**Downstream longitudinal utility.** Findings with persistent identities would make imaging histories queryable. The deck's core query for the [Imaging Problem List](/glossary/imaging-problem-list.md) is whether a finding was present on the most recent study, with tracking of appearance, disappearance, and change. + +# The workbench relationship between finding models and CDEs + +The deck calls the [finding models](/glossary/finding-model.md) corpus "the CDE workbench."[^deck] [Common data elements](/glossary/cde.md) published through RadElement undergo formal ACR and RSNA review, versioning, and governance. That process supports adoption but slows creation. + +[Finding Model Forge](https://fmf.oidm.org) and agent-driven batch workflows support faster authoring with language model assistance. The deck describes finding models as a "rapid innovation / proving ground before ACR/RSNA CDE adoption." Their "exploratory metadata" includes "rich definitions w/ embedded relationships and semantic tags that can graduate to standards."[^deck] + +The corpus supplies knowledge for extraction and reporting tools, described in the deck as "ground-level info for tool access + LLM context engineering." Proven definitions become candidates for formal common data elements. See [Finding models and CDEs](/semantic-foundation/finding-models/finding-models-and-cdes.md). + +# The Imaging Problem List across the imaging life cycle + +The deck proposes uses for structured results throughout an imaging encounter.[^deck] + +| Stage | Stated application | +|---|---| +| Planning | MRI safety screening, mobility assessment, prior authorization | +| Intra-exam | Rules-based protocoling | +| Interpretation | Findings-oriented views in the picture archiving system, real-time quality control | +| Post-interpretation | Passive screening, research cohorts, outcomes tracking | + +The 2023 framework post proposes a reporting tool that hosts plugins and supplies "all the relevant contextual information, about the findings in the current report, about prior reports, about the exams that were being reported on." The tool reruns plugins when context changes. Plugins can insert text, request input, raise alerts, or send data onward.[^site-plugin] The data structures layer provides the structured context those plugins need. + +# Why not just let the language model read the report + +Large language models can read narrative reports, but the deck gives three reasons to retain structured results.[^deck] + +**Context engineering.** Models answer better when given relevant findings directly. Structured results let systems select that context from prior reports. + +**Deterministic logic.** MRI safety checks and protocol selection require rules over facts with known types and codes. Reinterpreting prose on each run does not provide that consistency. + +**Citable common context.** Systems acting on the same patient need shared, auditable facts. A model's private interpretation of a report provides no shared reference. + +The deck describes the Imaging Problem List as "automatically extractable from narrative report text via LLMs."[^deck] Language models convert prose into objects once for downstream systems to use. + +# The proposed working group + +The deck's call to action is a proposal, not an existing body. The deck proposes an "ACR-OIDM Structured Imaging Results Working Group," co-hosted with the ACR and bringing together expert and vendor participants.[^deck] Its principles are to define structures before mapping them to FHIR and other standards, support innovation by reporting software vendors, and host an academic-vendor "big tent" moderated by the project. + +The proposed group would address four ACR priorities: recommendation tracking, artificial intelligence validation against radiologist observations, quality metrics, and support for the *-RADS reporting systems. The next steps are to convene the group, run a use-case pipeline feeding the CDE group, and standardize the result. + +As of this writing the working group is a proposal recorded in the deck. Nothing in the source repositories records it as convened. Area-specific goals appear under [roadmap](/roadmap/). + +# Evidence offered so far + +A 2024 site post announces a manuscript in the peer-reviewed Journal of the American Medical Informatics Association titled "Standardizing imaging findings representation: harnessing Common Data Elements semantics and Fast Healthcare Interoperability Resources structures." The post summarizes its position as "CDE-labeled Observations should be the universal representation for exchanging and consuming content in radiology reports." The manuscript presents a pulmonary nodule case study and guidance on adoption challenges.[^site-benchmark] + +[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 +[^site-structure]: "Data Model: Structure and Function", 2024-01-25 +[^site-findings]: "Findings, CDEs, and Observations", 2023-06-24 +[^site-benchmark]: "Benchmarking a Vision", 2024-07-01 +[^site-plugin]: "OIDM-Based Next-gen Reporting Assistance Framework", 2023-07-16 diff --git a/archive/knowledge/overview/what-is-oidm.md b/archive/knowledge/overview/what-is-oidm.md new file mode 100644 index 0000000..9042524 --- /dev/null +++ b/archive/knowledge/overview/what-is-oidm.md @@ -0,0 +1,73 @@ +--- +type: Overview +title: What is OIDM +description: The mission of the Open Imaging Data Model, the problem it addresses, its three layers, who it serves, and where its work lives. +tags: [overview, orientation] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +sources: + - id: deck + resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal + title: Open Imaging Data Model 2026 Status Update, January 2026 + - id: site-about + resource: https://www.openimagingdata.org/ + title: openimagingdata.org, project site and tagline + - id: site-findings + resource: https://www.openimagingdata.org/findings-cdes-and-observations/ + title: "Findings, CDEs, and Observations, 2023-06-24" + - id: ipl-readme + resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/README.md + title: imaging-problem-list README on the dev branch + - id: fm-schema + resource: https://github.com/openimagingdata/findingmodels/blob/4475ac1bcb591f1a0951b2082b59208def173a5d/schema/finding_model_schema.md + title: Finding model schema, prose mirror, findingmodels repository +--- + +# What OIDM is + +The [Open Imaging Data Model (OIDM)](/glossary/oidm.md) is an open project that represents imaging exam results as structured data. The project site describes its purpose as "defining unified data structures to integrate new functionality into imaging informatics platforms."[^site-about] The January 2026 status deck calls this standardizing "the DNA of imaging IT."[^deck] + +OIDM provides a controlled vocabulary, data structures, and tools that produce and consume them. Stable identifiers connect findings and their attributes across individual [observations](/glossary/observation.md) and longitudinal patient records. The tools apply the vocabulary and structures to real reports. + +# The problem: results locked in narrative + +Imaging reports are usually narrative text. To determine whether a pulmonary nodule was present on the latest chest CT, how large it was, or whether it grew, downstream systems must interpret that text. Each system interprets it differently. + +Radiologists reconstruct finding histories from old reports because prior findings cannot be retrieved reliably. Recommendations cannot be tracked to completion. Quality metrics and registry submissions require manual abstraction. Artificial intelligence tools and radiologists express findings differently, preventing comparison. Vendors address these problems with private schemas that other systems cannot reuse. + +OIDM gives each finding a coded identity, location, and attributes in a queryable structure. The 2023 site post states the approach: "Radiology findings can be represented in a standard format based on FHIR Observations semantically labeled with ACR/RSNA Common Data Element identifiers."[^site-findings] + +# Three layers + +The project and this knowledgebase organize the work into three layers. + +**Semantic foundation.** [Finding models](/glossary/finding-model.md) define findings with descriptions, synonyms, and attributes. Each has an [Open Imaging Finding Model (OIFM)](/glossary/oifm.md) identifier.[^fm-schema] The layer also includes [anatomic locations](/glossary/anatomic-location.md) keyed by RadLex identifiers, [common data elements](/glossary/cde.md) published through RadElement, [exam types](/glossary/exam-type.md), and external terminologies. See [semantic-foundation](/semantic-foundation/). + +**Data structures.** An [Observation](/glossary/observation.md) records one finding in one exam, with its location and attribute values. An [Exam Finding List](/glossary/exam-finding-list.md) collects an exam's observations in one queryable structure.[^ipl-readme] An [Imaging Problem List](/glossary/imaging-problem-list.md) groups observations across a patient's exams by finding. An [Imaging Persona](/glossary/imaging-persona.md) is the stated goal of adding clinical context. See [data-structures](/data-structures/). + +**Applications.** Tools include a finding model authoring application, a catalog site, an Imaging Problem List viewer, a report extraction and coding platform, and a terminology lookup library. See [applications](/applications/). + +Each layer depends on the one below it through shared identifiers. [Architecture](/overview/architecture.md) explains those connections. [Vision](/overview/vision.md) explains the design rationale. + +# Who this is for + +- Developers building imaging informatics software need formats and libraries to read and write them. +- Informaticists need to understand finding models, their relationship to common data elements, and identifiers for anatomic structures. +- Standards bodies and committees evaluate vocabulary and data structures for formal standardization. +- Agents extract, code, or author content. This knowledgebase is an Open Knowledge Format bundle with machine-readable frontmatter to support that work. + +# Where things live + +OIDM spans repositories in the `openimagingdata` GitHub organization and allied projects. Several repositories keep current work on branches other than `main`. [The repository map](/repositories/repository-map.md) lists their roles, status, and branches of record. + +It describes and links to catalogs; it does not copy them. Installation instructions, API usage, and developer workflows stay with the code. + +# Getting involved + +See [Getting involved](/overview/getting-involved.md) for participation channels, repositories, issues, and content contributions. See [the authoring guide](/guides/authoring-guide.md) for knowledgebase conventions. + +[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 +[^site-about]: openimagingdata.org, project site and tagline +[^site-findings]: "Findings, CDEs, and Observations", 2023-06-24 +[^ipl-readme]: imaging-problem-list README on the dev branch +[^fm-schema]: Finding model schema, prose mirror, findingmodels repository diff --git a/knowledge/references/anatomic-location-json-schema.md b/archive/knowledge/references/anatomic-location-json-schema.md similarity index 80% rename from knowledge/references/anatomic-location-json-schema.md rename to archive/knowledge/references/anatomic-location-json-schema.md index f373ea4..216c987 100644 --- a/knowledge/references/anatomic-location-json-schema.md +++ b/archive/knowledge/references/anatomic-location-json-schema.md @@ -1,10 +1,10 @@ --- type: Reference title: Anatomic location record format -description: The two JSON record formats for anatomic locations, the original anatomiclocations.org body part schema and the current anatomic-locations package format, with a real record from each. +description: The original and current anatomic location formats, with field tables and sample records. tags: [references, anatomic-locations, schema, radlex] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: al-schema resource: https://github.com/talkasab/anatomiclocations.org/blob/1f39fa45f621cef947a3f3ef1f869334cfa5c841/data/body_parts_schema.json @@ -25,13 +25,13 @@ sources: resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/notebooks/data/anatomic_locations_noembed.json title: anatomic_locations_noembed.json, the current curated anatomic location set - id: build-plan - resource: https://github.com/openimagingdata/oidm-knowledge/blob/main/knowledge/plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, source map and work edge --- # Provenance and status -Two record formats for [anatomic locations](/glossary/anatomic-location.md) coexist in OIDM, and this document holds both. The first is the original schema from the `anatomiclocations.org` repository, read at commit `1f39fa4`, which validates a curated set of 2,890 body parts keyed by [RadLex](/glossary/radlex.md) identifier. The second is the format used by the `anatomic-locations` package inside the `findingmodel` repository, read at commit `75afd39` on `main`, which covers 2,926 records and adds classification, dual hierarchies, and explicit [laterality](/glossary/laterality.md). The build plan names the newer package and its dataset as the current anatomic data, and the older set as lineage.[^build-plan] Field tables here are compiled from the schema file and the model source; the records shown are copied unchanged from the published data. +OIDM has two [anatomic location](/glossary/anatomic-location.md) formats. The original `anatomiclocations.org` schema at `1f39fa4` validates 2,890 body parts keyed by [RadLex](/glossary/radlex.md) identifiers. The `anatomic-locations` package in `findingmodel/main` at `75afd39` covers 2,926 records with classification, dual hierarchies, and explicit [laterality](/glossary/laterality.md). The build plan identifies the newer package as current and the older set as lineage.[^build-plan] Tables derive from the schemas and model source. Example records are unchanged. # Lineage A: the anatomiclocations.org body part schema @@ -90,7 +90,7 @@ The entry for uterine adnexa shows containment, part-of, a laterality pair, sex # Lineage B: the anatomic-locations package format, current -This lineage has two representations of the same information. The curation format is the JSON file that editors change, documented in the repository's own field reference.[^fm-json-schema] The runtime format is the `AnatomicLocation` Pydantic model that the package returns from its index.[^fm-location-model] +Editors use the JSON curation format documented in the field reference.[^fm-json-schema] The package index returns the runtime `AnatomicLocation` Pydantic model.[^fm-location-model] ## Curation format fields @@ -117,7 +117,7 @@ The source file `notebooks/data/anatomic_locations_noembed.json` is a flat JSON Two fields appear in the data but not in the field reference: `sexSpecific`, on 110 records, and `anatomicLocationsId`, on a single record. The field reference also lists `"Spine"` among the common `region` values, but no record in the data uses it and the runtime enumeration has no spine member. The nine region values actually present are Head (869), Upper Extremity (582), Lower Extremity (579), Neck (235), Thorax (226), Abdomen (212), Pelvis (152), Breast (46), and Body (25). -The two hierarchy relations are kept deliberately distinct, as documented in the field reference: `containedByRef` is spatial, meaning X is inside Y, while `partOfRef` is mereological, meaning X is a component of Y. See [contained-by and part-of](/glossary/contained-by-and-part-of.md). SNOMED coding follows one rule without exception: of the Structure, Entire, and Part concepts in a SNOMED anatomy triad, always use the "Structure of" concept, because that is what SNOMED intends for finding sites and procedure sites. +The two hierarchy relations are kept deliberately distinct, as documented in the field reference: `containedByRef` is spatial, meaning X is inside Y, while `partOfRef` is mereological, meaning X is a component of Y. See [contained-by and part-of](/glossary/contained-by-and-part-of.md). SNOMED coding follows one rule without exception: from the Structure, Entire, and Part triad, always use the "Structure of" concept, because that is what SNOMED intends for finding sites and procedure sites. Laterality is expressed as a triad. The generic entry carries `leftRef` and `rightRef`; each sided entry carries `unsidedRef` back to the generic. 827 distinct left-sided and 827 distinct right-sided entries are referenced, and every reference resolves to a record in the file. 1,597 records carry an `unsidedRef`. @@ -147,7 +147,7 @@ The hypopharynx entry, unchanged from the data file and used as the worked examp ## Runtime model fields -`AnatomicLocation` is the object the package hands back. It renames the curation fields, resolves the reference objects into a lightweight `AnatomicRef` of `id` and `display`, and adds classification and materialized paths. +`AnatomicLocation` renames curation fields, represents references as `AnatomicRef` objects with `id` and `display`, and adds classification and materialized paths. | Field | Type | Default | Notes | |---|---|---|---| @@ -177,13 +177,13 @@ The hypopharynx entry, unchanged from the data file and used as the worked examp Two computed fields are serialized with the record: `is_bilateral`, true when `laterality` is generic, and `is_lateralized`, true when it is left or right. `LocationType` is a coarse split modeled on the top level of the Foundational Model of Anatomy, separating discrete structures from spaces, regions, macro body parts, organ systems, and named groups. `StructureType` is the finer classification, with members grouped as musculoskeletal, vascular, peripheral neural, brain-specific, organs, lymphatic, anatomical organization, and spatial.[^fm-enums] -The materialized paths are what make hierarchy queries cheap. Containment ancestry, descendants, and the "is X inside Y" test all read `containment_path` rather than walking parent links, and the part-of hierarchy works the same way through `partof_path`. +The materialized paths are what make hierarchy queries cheap. Containment ancestry, descendants, and the "is X inside Y" test all read `containment_path` rather than walking parent links. Part-of queries use `partof_path` the same way. A location converts to an `IndexCode` with `system` set to `anatomic_locations`, its RadLex identifier as the code, and its description as the display. That is the form an anatomic location takes when it is attached to a [finding model](/glossary/finding-model.md). # Which is current -The `anatomic-locations` package format is current. It is the format the OIDM tooling reads, it carries the larger and more recently curated dataset, and it adds the classification axes and explicit laterality that the original schema left implicit. The `anatomiclocations.org` schema remains the published format of the original curated set and of the wrapper libraries built on it, and is documented here because that data and those libraries are still reachable. +OIDM tooling uses the current `anatomic-locations` format, with its larger, more recently curated dataset, classification, and explicit laterality. The original `anatomiclocations.org` set and wrapper libraries remain available, and are documented here because that data and those libraries are still reachable. [^al-schema]: body_parts_schema.json, anatomiclocations.org repository [^al-data]: body_parts.json, anatomiclocations.org curated body part set diff --git a/knowledge/references/cde-schema-differences.md b/archive/knowledge/references/cde-schema-differences.md similarity index 75% rename from knowledge/references/cde-schema-differences.md rename to archive/knowledge/references/cde-schema-differences.md index b5d4a56..6962f5b 100644 --- a/knowledge/references/cde-schema-differences.md +++ b/archive/knowledge/references/cde-schema-differences.md @@ -1,10 +1,10 @@ --- type: Source Extract title: CDE set schema versus the RadElement API -description: Near-verbatim extract of the note cataloguing where the CDE set RelaxNG schema and the RadElement API disagree, with the later JSON Schema versions that partly close the gap. +description: A 2023 CDE schema and API comparison, with notes on later JSON Schema changes. tags: [references, cde, radelement, schema, source-extract] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: differences resource: https://github.com/openimagingdata/openimagingdata.org/blob/fb431dfc81801a5a8e621c0e8bf2acd20f74be7d/schemas/schema_differences.md @@ -26,9 +26,9 @@ sources: # Provenance and status -The catalogue below is migrated from `schemas/schema_differences.md` in the `openimagingdata.org` repository, last changed on 2023-03-28 and read at commit `fb431df`. Its structure and wording are the author's; only dash punctuation was normalized. It compares two descriptions of the same object, a [CDE set](/glossary/cde-set.md): the RelaxNG schema kept alongside it as `original_cde_set_schema.rnc`, and the de facto JSON shape returned by the [RadElement](/glossary/radelement.md) API. +This extract preserves `schemas/schema_differences.md` from `openimagingdata.org` at `fb431df`, last changed 2023-03-28. It compares the [CDE set](/glossary/cde-set.md) format in `original_cde_set_schema.rnc` with the JSON returned by the [RadElement](/glossary/radelement.md) API. Its structure and wording are the author's; only dash punctuation was normalized. -It is a 2023 snapshot and should be read as one. Since it was written, the schema has been reissued as JSON Schema in two versions, and the closing section records what those versions changed. No individual names appeared in the source. +The extract is a 2023 snapshot. The closing section compares two later JSON Schema versions. The source contains no individual names. # Differences between Defined CDE Set Schema and RadElement API @@ -69,9 +69,9 @@ The CDE Set Schema is a RelaxNG schema that defines the structure of a CDE Set. # What the JSON Schema versions changed -The `common_data_elements` repository, which mirrors the [common data element](/glossary/cde.md) definitions published on RadElement, carries two JSON Schema drafts in `schema/`, read at commit `35536d8`. Both are JSON Schema draft-07. The repository's README states that the definitions currently conform to version 1.0, not the newer version 1.1.[^cde-readme] Version 1.1 is therefore published but not in force. +The `common_data_elements` repository mirrors RadElement's [common data element](/glossary/cde.md) definitions. At `35536d8`, its `schema/` directory contains versions 1.0 and 1.1, both using JSON Schema draft-07. The README states that definitions conform to 1.0, so 1.1 is published but not in force.[^cde-readme] -Both files declare the same `$id`, `https://github.com/ACR-RSNA-CDEs/blob/v1.0.0/cde.schema.json`. Version 1.1 did not update it, so the two schemas are not distinguishable by identifier. +Both files declare `$id` as `https://github.com/ACR-RSNA-CDEs/blob/v1.0.0/cde.schema.json`, so the identifier does not distinguish them. ## Changes from 1.0 to 1.1 in the set definition @@ -94,13 +94,13 @@ At the definitions level, 1.1 drops `event`, `biological_sex`, and `boolean_valu ## How this bears on the 2023 catalogue -Three of the gaps the note recorded look different once the JSON Schema versions are taken into account. +The JSON Schema versions change three comparisons: - **`images` and `modality`.** The note lists both as present in the RelaxNG schema and missing from the API. Version 1.1 adds `images` and `modalities` to both the set and the element, moving the JSON side toward the RelaxNG side rather than away from it. - **`url` and `body_parts`.** The note lists both as API-only. Both are properties of the set in JSON Schema 1.0 and 1.1, and `body_parts` is added to the element in 1.1. - **`biological_sex` and `age_range`.** These remain unreconciled. `biological_sex` has a definition in 1.0 and is dropped in 1.1; `age_range` appears in neither JSON Schema version. Both were RelaxNG-only in 2023 and are now absent from the schema in force. -One constraint moved the other way. The note records the RelaxNG schema as allowing `RADLEX`, `SNOMEDCT`, and `LOINC` for an [index code](/glossary/index-code.md) system. Both JSON Schema versions constrain the same field to `RADLEX`, `SNOMEDCT`, `LOINC`, and `ACRCOMMON`, and both keep `url` on the index code rather than the API's `href`. +One constraint moved the other way. The note records the RelaxNG schema as allowing `RADLEX`, `SNOMEDCT`, and `LOINC` for an [index code](/glossary/index-code.md) system. Both JSON Schema versions add `ACRCOMMON` to that enumeration. Both use `url` on index codes where the API uses `href`. [^differences]: Differences between Defined CDE Set Schema and RadElement API, openimagingdata.org repository [^rnc]: original_cde_set_schema.rnc, the RelaxNG CDE set schema the note compares diff --git a/knowledge/references/exam-finding-list-example.md b/archive/knowledge/references/exam-finding-list-example.md similarity index 100% rename from knowledge/references/exam-finding-list-example.md rename to archive/knowledge/references/exam-finding-list-example.md diff --git a/knowledge/references/finding-model-schema.md b/archive/knowledge/references/finding-model-schema.md similarity index 88% rename from knowledge/references/finding-model-schema.md rename to archive/knowledge/references/finding-model-schema.md index ba110fd..3e40522 100644 --- a/knowledge/references/finding-model-schema.md +++ b/archive/knowledge/references/finding-model-schema.md @@ -1,10 +1,10 @@ --- type: Reference title: Finding model schema -description: The Open Imaging Finding Model record format as specified in the findingmodels repository, reconciled field by field against the Pydantic definitions that validate it. +description: The finding model prose schema, compared with released and unreleased Pydantic models. tags: [references, oifm, finding-models, schema] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fm-schema resource: https://github.com/openimagingdata/findingmodels/blob/4475ac1bcb591f1a0951b2082b59208def173a5d/schema/finding_model_schema.md @@ -32,7 +32,7 @@ sources: # Provenance and status -The specification below is migrated near-verbatim from `schema/finding_model_schema.md` in the `findingmodels` repository, last changed on 2025-06-17 and read here at commit `4475ac1` on `main`.[^fm-schema] Its structure and wording are the author's; only dash punctuation was normalized to house style and the term "user" was left as written. That file is a prose mirror, not an executable schema. The record format that software actually enforces is the set of Pydantic models in the `findingmodel` repository, and the two have drifted. The reconciliation section states every difference. A third section records fields that exist only on the `feature/metadata-cleanup` work-edge branch and are not in any released version. +The extract preserves `findingmodels/schema/finding_model_schema.md` from `main` at `4475ac1`, last changed 2025-06-17.[^fm-schema] Its structure and wording are the author's; only dash punctuation was normalized to house style and the term "user" was left as written. That file is a prose mirror, not an executable schema; `findingmodel`'s Pydantic classes enforce it, and the two have drifted. The reconciliation section records differences, followed by unreleased fields on `feature/metadata-cleanup`. # Finding Model Schema @@ -149,7 +149,7 @@ Contributor details. # Reconciliation with the Pydantic source of truth -The released definitions live in `packages/findingmodel/src/findingmodel/finding_model.py`, read at commit `75afd39` on `main`.[^fm-pydantic] Two classes matter. `FindingModelBase` is a definition without registry identifiers, used while authoring. `FindingModelFull` is the registered form, and it is what the prose specification above describes. The tables record what the code enforces and mark each difference from the prose. +`packages/findingmodel/src/findingmodel/finding_model.py` on `main` at `75afd39` defines both classes.[^fm-pydantic] Authors use `FindingModelBase` before identifier assignment. `FindingModelFull` is the registered form described above. The tables compare the code's constraints with the prose. ## FindingModelFull @@ -240,7 +240,7 @@ The six digits are generated at random by `generate_oifm_id` and `generate_oifma # On the work edge, not released -Everything in this section is on the `feature/metadata-cleanup` branch of `findingmodel`, read at commit `1942b06`. None of it is on `main`, and none of it appears in the prose specification. It is recorded here because it is the stated direction for the format, not because it is in effect. +This section describes unreleased work on `findingmodel/feature/metadata-cleanup` at `1942b06`. These changes are absent from `main` and the prose above. It is recorded here because it is the stated direction for the format, not because it is in effect. The branch splits `finding_model.py` into `types/models.py`, `types/attributes.py`, and `types/metadata.py`. Attribute and `IndexCode` definitions are unchanged by the split. The design document that governs the work states the goal as making structured metadata "canonical `FindingModel` state rather than disposable enrichment output".[^edge-rewrite] @@ -263,7 +263,7 @@ Three further changes ride along.[^edge-metadata] - **Model-level codes must carry a display value.** A validator on `FindingModelFull` rejects any entry in `index_codes` or `anatomic_locations` whose `display` is empty. - **Canonical `index_codes` are narrowed.** The branch restricts them to exact matches or clinically substitutable near-equivalents, and sends broader, narrower, and merely related candidates to a separate enrichment review artifact rather than to the model. -The branch's own readiness assessment is not yet passing, so the field list above should be read as the current shape of unmerged work. +The branch's own readiness assessment is not yet passing. These fields remain unmerged. [^fm-schema]: Finding Model Schema, prose specification, findingmodels repository [^fm-pydantic]: finding_model.py, released Pydantic definitions, findingmodel main branch diff --git a/knowledge/references/imaging-problem-list-example.md b/archive/knowledge/references/imaging-problem-list-example.md similarity index 100% rename from knowledge/references/imaging-problem-list-example.md rename to archive/knowledge/references/imaging-problem-list-example.md diff --git a/knowledge/references/index.md b/archive/knowledge/references/index.md similarity index 60% rename from knowledge/references/index.md rename to archive/knowledge/references/index.md index ea29d55..c2504d4 100644 --- a/knowledge/references/index.md +++ b/archive/knowledge/references/index.md @@ -1,12 +1,13 @@ # References -Verbatim and near-verbatim source material: migrated specifications, extracted notes, and worked examples built from real data. Each document says where it came from, when, and whether it is current. +Source extracts, format references, and worked examples. Each page states its origin, date, and status. * [Status update, January 2026](./status-update-2026-01.md) - A faithful extract of the January 2026 OIDM status deck, "Realizing Object-Oriented Imaging Results", slide by slide. -* [Finding model schema](./finding-model-schema.md) - The Open Imaging Finding Model record format as specified in the findingmodels repository, reconciled field by field against the Pydantic definitions that validate it. -* [Anatomic location record format](./anatomic-location-json-schema.md) - The two JSON record formats for anatomic locations, the original anatomiclocations.org body part schema and the current anatomic-locations package format, with a real record from each. +* [Finding model schema](./finding-model-schema.md) - The finding model prose schema, compared with released and unreleased Pydantic models. +* [Anatomic location record format](./anatomic-location-json-schema.md) - The original and current anatomic location formats, with field tables and sample records. * [Exam Finding List example](./exam-finding-list-example.md) - A real Exam Finding List from the imaging-problem-list sample data, trimmed to five findings, with a field-by-field walkthrough. * [Imaging Problem List example](./imaging-problem-list-example.md) - Two findings from a real Imaging Problem List in the imaging-problem-list sample data, with the grouping key and the temporal status derivation as the dev branch implements them. * [Finding models: overview](./oifm-overview-extract.md) - Near-verbatim extract of the OIFM overview note explaining why finding models exist, what counts as an imaging finding, and how a finding model is structured. * [Finding model structured metadata fields](./oifm-metadata-fields-extract.md) - Near-verbatim extract of the reference for the structured metadata fields on FindingModelBase and FindingModelFull, with the points where it differs from the branch code it describes. -* [CDE set schema versus the RadElement API](./cde-schema-differences.md) - Near-verbatim extract of the note cataloguing where the CDE set RelaxNG schema and the RadElement API disagree, with the later JSON Schema versions that partly close the gap. +* [CDE set schema versus the RadElement API](./cde-schema-differences.md) - A 2023 CDE schema and API comparison, with notes on later JSON Schema changes. +* [Project direction and source-review record](./project-direction.md) - Dated project-lead guidance and the source-review snapshot retained from the original planning records. diff --git a/knowledge/references/oifm-metadata-fields-extract.md b/archive/knowledge/references/oifm-metadata-fields-extract.md similarity index 94% rename from knowledge/references/oifm-metadata-fields-extract.md rename to archive/knowledge/references/oifm-metadata-fields-extract.md index ae42612..aec48ef 100644 --- a/knowledge/references/oifm-metadata-fields-extract.md +++ b/archive/knowledge/references/oifm-metadata-fields-extract.md @@ -4,7 +4,7 @@ title: Finding model structured metadata fields description: Near-verbatim extract of the reference for the structured metadata fields on FindingModelBase and FindingModelFull, with the points where it differs from the branch code it describes. tags: [references, oifm, finding-models, metadata, source-extract] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: note resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/notes/oifm-metadata-fields.md @@ -25,9 +25,9 @@ sources: # Provenance and status -This is a near-verbatim extract of `notes/oifm-metadata-fields.md` from the `next-gen-2026` branch of the `ACR-RSNA-CDEs` repository, read at commit `44836c1`. That file is a copy: its own frontmatter records it as fetched on 2026-07-29 from a gist published by the project lead, and describes itself as intended for review with colleagues who are not reading the code. The only change made here is that dash punctuation was normalized to house style. No individual names appeared in the source. +This near-verbatim extract comes from `ACR-RSNA-CDEs/notes/oifm-metadata-fields.md` on `next-gen-2026` at `44836c1`. The note records a 2026-07-29 copy of the project lead's gist for review by colleagues who are not reading the code. Only dash punctuation was normalized. The source contains no individual names. -The fields it documents are **not released**. They exist on the `feature/metadata-cleanup` branch of the `findingmodel` repository and on `dev`, and none of them are on `main`. The released record format, and a field-by-field reconciliation against it, is in [the finding model schema](/references/finding-model-schema.md). Terms used below have glossary entries: [finding model](/glossary/finding-model.md), [attribute](/glossary/attribute.md), [index code](/glossary/index-code.md), [anatomic location](/glossary/anatomic-location.md), and [measurement](/glossary/measurement.md). +The metadata fields are unreleased, present on `findingmodel`'s `feature/metadata-cleanup` and `dev` branches but absent from `main`. See the [finding model schema](/references/finding-model-schema.md) for the released format and comparison. Related terms are [finding model](/glossary/finding-model.md), [attribute](/glossary/attribute.md), [index code](/glossary/index-code.md), [anatomic location](/glossary/anatomic-location.md), and [measurement](/glossary/measurement.md). ## Where this differs from the branch code diff --git a/knowledge/references/oifm-overview-extract.md b/archive/knowledge/references/oifm-overview-extract.md similarity index 92% rename from knowledge/references/oifm-overview-extract.md rename to archive/knowledge/references/oifm-overview-extract.md index d21882e..7292500 100644 --- a/knowledge/references/oifm-overview-extract.md +++ b/archive/knowledge/references/oifm-overview-extract.md @@ -4,7 +4,7 @@ title: "Finding models: overview" description: Near-verbatim extract of the OIFM overview note explaining why finding models exist, what counts as an imaging finding, and how a finding model is structured. tags: [references, oifm, finding-models, source-extract] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T16:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: note resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/notes/oifm-overview.md @@ -17,7 +17,9 @@ sources: # Provenance and status -This is a near-verbatim extract of `notes/oifm-overview.md` from the `next-gen-2026` branch of the `ACR-RSNA-CDEs` repository, read at commit `44836c1`. That file is itself a copy: its own frontmatter records it as fetched from `prompts/overview.md` in the `findingmodels` repository on 2026-07-29, and the two are byte-identical to the version of that file at commit `2267d5d`, last changed on 2026-04-18. The upstream file is an authoring prompt, which is why it reads as guidance to whoever or whatever is writing a [finding model](/glossary/finding-model.md). It is current: the upstream file is live on the `main` branch of `findingmodels` and the note carries `status: stable`. The only change made here is that dash punctuation was normalized to house style. No names appeared in the source. Glossary links are not inserted into the extract body; the terms it uses are [finding model](/glossary/finding-model.md), [attribute](/glossary/attribute.md), [presence](/glossary/presence.md), [index code](/glossary/index-code.md), and [Observation](/glossary/observation.md). +This near-verbatim extract comes from `notes/oifm-overview.md` on `ACR-RSNA-CDEs`'s `next-gen-2026` branch at `44836c1`. The note records a 2026-07-29 copy of `findingmodels/prompts/overview.md`, byte-identical to commit `2267d5d`, last changed 2026-04-18. At review, the authoring prompt was current on `findingmodels/main`, and the note had `status: stable`. The upstream file is an authoring prompt, which is why it reads as guidance to whoever or whatever is writing a [finding model](/glossary/finding-model.md). + +Only dash punctuation was normalized in the extract. It contains no individual names or added glossary links. Related terms are [finding model](/glossary/finding-model.md), [attribute](/glossary/attribute.md), [presence](/glossary/presence.md), [index code](/glossary/index-code.md), and [Observation](/glossary/observation.md). # Finding Models: Overview diff --git a/archive/knowledge/references/project-direction.md b/archive/knowledge/references/project-direction.md new file mode 100644 index 0000000..cf55116 --- /dev/null +++ b/archive/knowledge/references/project-direction.md @@ -0,0 +1,183 @@ +--- +type: Reference +title: Project direction and source-review record +description: Dated project-lead guidance and the source-review snapshot retained from the original planning records. +tags: [provenance, project-direction] +status: draft +generated: { by: codex/gpt-6, at: 2026-10-02T21:35:54Z } +sources: + - id: owner-sessions + resource: scope:project-lead-planning-sessions-2026-09-20-through-2026-10-02 + title: Project-lead statements recorded in the original build, layout, and layer plans + - id: original-records + resource: https://github.com/openimagingdata/oidm-knowledge/tree/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans + title: Planning records before consolidation +--- + +# Project direction and source-review record + +This reference preserves evidence cited by the knowledge pages. It is not an execution plan. Dates identify when a statement was recorded; source-review observations are historical and require rechecking before claims of current implementation. The single implementation plan is maintained outside the bundle in `docs/plans/implementation.md`. + +Later decisions govern current work. The October pipeline retains the knowledge bundle separately from presentation pages. The October 2 clarification makes the bundle repository-only, superseding the proposed internal website. The old page-replacement instructions, page budgets, agent assignments, and deployment instructions are retired. + +## The project lead's stated goals, September 20–21 + +Recorded verbatim from the 2026-09-20 request that started this work, so that documents citing `scope:grilling-session-2026-09-20` have a written source. Only spelling was normalized. + +- **Open Imaging Finding Models.** "Definition formats, needs overhaul for increased metadata and interesting relationship/graph work coming from CDEs." "Tooling for working with these, using them, probably needs corresponding overhaul." "Actual content efforts." +- **ACR/RSNA Common Data Elements.** "Should dovetail with the OIFM work." +- **FindingModelForge.** "An authoring tool for creating OIFMs / CDEs." +- **Imaging Problem List.** "A format for representing a patient's imaging history." "Define appropriate data structures for representing both observations in radiology reports and their relationships." "Manage them (and appropriately collate them) across the patient's imaging history." "Design interesting applications that use this tool and demonstrate how this works." "Create standard data structure formats, etc. for working with these." +- **Anatomic Locations.** "There are repos, and this will be blended in with RadLex." "Also needs tooling for people working with them." +- **Exam types.** "Needs wrapper tooling around the knowledge graph represented by RadLex LOINC playbook." "Need to define the PREFERRED high-level entries like 'CT Chest', 'MRI Brain', 'X-ray Knee'." "Need to TIGHTLY wrap up with Anatomic Locations, including edges for both 'always included' (which can include hierarchies) as well as 'usually included' (for edge things) and something like 'POSSIBLY included, would have to check'." +- **Content direction (2026-09-21).** "In findingmodels, there's a recent taxonomy branch with new files in lists that's the immediate direction for all of the content, probably replacing a lot of the Gamuts-based models." The taxonomies are to be called the "MGB exam-oriented sub-taxonomies." + +## Content decisions, September 21 + +| Topic | Decision | +|---|---| +| Manuscripts in the bucket | Paraphrase facts, structures, and numbers; cite as "manuscript under review at JDIM, 2026" or "prepared for submission"; never quote; reviewer correspondence never reproduced or paraphrased. | +| Excalidraw boards | Publish the public-facing boards (object model, structured report sections, outcome tracking, exhibit figures, lineage, use-case notes). From internal boards, publish only schema and data-model ideas with organizations named; people, meeting histories, and vendor demo logistics omitted; the rest recorded as reviewed and held. | +| Diagrams | No Mermaid. Excalidraw files rendered to SVG beside each document. Per diagram, a builder is the source until the file is hand-edited, after which the builder is retired (tools/diagrams/README.md). | +| Vendor name in the deck extract | Generalized to "a reporting vendor". | +| Upstream defects | Drafted for the project lead's review before any issue is filed. | +| No specification exists | The project is in a coalescing phase; nothing is formally defined. The knowledgebase presents each source's current version of a structure side by side (repo branch and commit, manuscript under review, deck or webinar, board), dated and attributed, and states disagreements (for example the three Imaging Problem List status vocabularies) without resolving them. Words like "specification" or "canonical model" are not used for any of them. | +| Data structures versus transport | Per the project lead's 2026-09-19 manuscript notes: OIDM defines a system of data structures for imaging exam result information used inside applications; FHIR and DICOM are transport expressions designed separately and guided by the structures. The data-structure documents are to be reframed accordingly. | + +## Decisions of September 22 + +These supersede the four-pillar page list that stood here earlier today. The owner's words are quoted; everything else is the two sessions' shared understanding, confirmed in conversation. + +1. **Five named pillars. Use the names, never numbers.** "1. Foundation Context / Semantic layer: includes both the schema/meta-definitions as well as the efforts to build out content. 2. Data Structures, most ESPECIALLY in the Observation layer, and how there are two graphs and they work together. 3. SDKs, the tools that wrap the data structures and enable developers working in the application layer to manipulate instances in real-world applications (and some around helping with definition of the foundation context, obviously). 4. Theoretical Use Cases: Imaging all the different kinds of applications/tools/plugins/output products we could create using an ecosystem built around this foundation. 5. ACTUAL sample applications we've created to try to illustrate some of these ideas." The fourth was then shortened to **Use Cases**. So: **Foundation Context**, **Data Structures**, **SDKs**, **Use Cases**, **Sample Applications**. +2. **A CDE and a finding model are the same content.** "A 'CDE' and a 'Finding Model' are the same thing, but they're in different repos. Open Imaging Finding Models are in the OIDM-maintained repo, and are intended to be very INCLUSIVE, can almost be drafts. Common Data Elements live in the ACR/RSNA repository, and are intended to be WELL-REVIEWED. They're more like something published. They BOTH should soon be using the schema and classes being defined in the CDE schema re-write, with FindingClass, DataElement, Measurement, etc." Consequence: explain the common model once, then the inclusive OIDM collection and the well-reviewed ACR/RSNA collection and their relationship. Collection expectations are not per-item facts. Migration is intended, not complete. The "Observation Type" umbrella name the sessions had floated is withdrawn as unnecessary and was never approved. +3. **The OIFM reorganization around the graph is urgent.** "OIFMs VERY soon need to get re-organized around the graph-based approach we're developing in the next-gen-schema effort in CDEs. They're currently document-oriented and we need to fix that SOON." A change of organization, not added fields. Stated prominently under Foundation Context; current implementation kept distinct from direction; no storage, schema, or migration design invented beyond the source work. +4. **Source eligibility rule.** Repository documents, prompts, plans, skills, ADRs, and code are team artifacts eligible for substantive content. They are not demoted for being agent-assisted, and no statement needs separate human approval to be cited. Committing a document does not make a draft proposal or one implementation project-wide consensus. Each claim carries its source's actual status, date, and any explicit provenance label the source supplies (the CDE decision record's OWNER and CLAUDE DEFAULT, for example). Differences between sources and the owner's later corrections are preserved, not reconciled. No speculative authorship audit. +5. **Terminology.** Foundation Context (shared definitions, relationships, references) and Patient Context (this patient's imaging results plus relevant clinical context), from the SIIM 2026 deck. The CDE report graph, the Imaging Problem List representations, and the broader Patient Context are *related representations* of the Observation idea, never "the same thing serialized differently". Foundation Context and Data Structures correspond to the two graphs at the explanatory level; the report graph is the imaging-assertion part of Patient Context, not a clinical-context schema. The deck's "connective tissue" means relationships and citations between foundation concepts and is used only in that sense. Retained artifact names are used where a source's current files are cited: OIFM finding model, CDE set and element, the next-generation FindingClass, Diagnosis, Grouping, DataElement, Measurement. +6. **Organizing method** (agreed with Codex). The introduction explains the two-graph connection once within the five pillars. Idea anchors carry the recovered substance. Patient-side ideas say which shared definitions they reference; foundation-side ideas say how observations use them; related anchors link both ways. Existing worked examples are used for what each actually shows (the CDE pyelonephritis report and the deck's calculus figure show both graphs; the IPL example2 study is patient-side only; the deck's aneurysm and kidney tables are foundation-side only). Sources attach to ideas; repositories do not become chapters. Redundant generated exposition is deleted after its unique ideas are recovered; unique team-authored material is kept and counted in the budget. A glossary or question register is not a mandate to duplicate content. + +## Presentation guidance, September 22–30 + +Project lead, 2026-09-27 to 2026-09-29, on the Finding Models and Common Data Elements hub page: + +> I think what we want to say is that the finding/diagnosis definition is an unified anchor for the CONCEPT of a finding/diagnosis in a form that can be used to attach associated context: name, synonyms, definition, attributes, etc. OIFM are AN inclusive collection, intended for rapid prototyping... ACR/RSNA Common Data Elements (always include the organizations) are the well-reviewed... published through radelement.org. Forget about talking about the current schema's sets and elements--waste of bits. + +> "Finding Models and Common Data Elements" / "Definitions of findings and diagnoses anchor these concepts in a form that associated content can..." + +> "...maintained by the OIDM project..." / "The OIFM repository is intended for rapid, community-based prototyping and iteration based on an open-source model." / "...with changes proposed by anyone." / Take out "uneven" / "in the project lead's words" is RIGHT OUT. I think the thing to emphasize is that they will have the same schema and describe the same ideas. Major difference is that OIFMs is much more "beta"/rapid additions and updates and CDEs is much more "release"/reviewed and stable. + +> I think we should also emphasize that we will maintain cross-links: when something migrates to CDEs, it will continue to be maintained in the OIFM repo but have a reference to its CDE identity. "Both CDEs and OIFMs are document-oriented today, and moving..." + +The agreed hub text (2026-09-29) is the one the Opus agent writes verbatim into `site/pages/foundation-context/finding-models-and-cdes.md` (moved 2026-10-05; originally `knowledge/drafts/finding-models-and-cdes-hub.md`). + +Project lead, 2026-09-30, on figures: "Let's make the AssessmentSchemes ovals rather than diamonds--those have a flowchart implication that doesn't obtain here." Recorded as a convention in tools/diagrams/README.md. + +Project lead, 2026-09-30, four prose decisions on the relationships page: (1) change the associated-findings example to a pair the graph records as OCCURS_WITH; (2) the renal abscess clause becomes "...may have sequela beyond the course of the acute pyelonephritis episode."; (3) "whether or not one causes the other"; (4) use the schema's specificity words: pathognomonic, highly suggestive, suggestive ("they're cooler"). + +Project lead, 2026-09-30, on the pulmonary nodule neighborhood figure (illustrative; reflects updates the project lead, as the CDE project's clinical expert, intends for the CDE graph): rename "attenuation" to "composition" and "pulmonary margin" to "margin"; drop "lesion count"; "ground glass nodule" for "non-solid pulmonary nodule"; add the Grouping "pulmonary parenchymal abnormality"; add the diagnoses "pulmonary neoplasm" and "pulmonary granuloma" (subtype of the grouping); add "Fleischner criteria" as an assessment scheme; add subspecialty; show SEEN_ON CT, XR, and MR; no values; no time course or etiology for now: "I'm starting to think that may need to be a property of the edge connecting it to a diagnosis. Actually, that's the same for etiology." + +Project lead, 2026-09-30, on the relationships page: + +> Emphasizing that they're "strictly apart" makes it seem like THAT'S the issue. / Let's just call this subsection "Findings <-> Diagnoses: Manifestation and Causation" / "Pyelonephritis APPEARS AS striated nephrogram..." "when the pyelonephritis resolves, the striation goes with it..." / Next subsection called "Deriving the Differential" / "The schema distinguishes manifestation and causation to enable inference:..." / We should ALSO include the idea of associated findings here, and the properties of the edges themselves that get at how commonly findings/diagnoses are associated in the specified way / Don't include the "HPO and Orphanet" reference / "a pulmonary nodule definition specifies how a pulmonary nodule WILL BE DESCRIBED. The assessment is a separate assertion, defined by the AssessmentScheme definition, which allows for evaluation of the nodule according to the defined criteria." / "...by an ASSESSED_BY edge" + +Project lead, 2026-09-30, on the next-generation schema page: + +> "Both Open Imaging FInding Models and RSNA/ACR Common Data Elements are moving..." / Show a link to the ACR/RSNA CDE schema repository, on the next-gen-2026 branch. / "...as a graph of interconnected concepts rather than a set of documents." / "Applications will be able to use either or both collections at once via a common SDK based on the new schema." / "FindingClass" defines the properties of the items described in imaging reports. A "Diagnosis" is a separate, closely-related node type, distinguished by a different set of possible relationships. Groupings are broad classes of findings/diagnoses such as "renal abnormality", usually intended to be used in the negative. / For most of these node class definitions, we want to use "defines" rather than "is a" (this is a very tricky but important semantic distinction) / "AssessmentScheme" definitions layout the systems such as Lung-RADS via which a constellation of other findings is evaluated. / "The anatomic scope property of each other node indicates the locations and/or structures in which they may be localized (e.g., "lung parenchyma" for a pulmonary nodule). / "Edges integrate the notes into a rich fabric of interrelated concepts, enabling reliable, reproducible inference." / "Edges bind FindingClass and Diagnosis definitions to their anatomic scope and to the DataElements and Measurements used to specify their attributes. The edges themselves may contain properties that provide additional detail about the relationships between nodes." / Concept nodes as a note below the list, introducing the paragraph that also talks about anatomic scope. / Use the actual table of relationship types as examples. / "Evolution of CDEs and Finding Models: Both CDEs and existing Open Imaging Finding Models will have to migrate from their current JSON document-based organization to a new graph-oriented format, likely using RDF/OWL as an underlying storage format. More ergonomic data structures will also be developed for use in SDKs and via an API." + +Project lead, 2026-09-30, on the SDKs (for the pillars figure and the SDKs index): "I think we should also say that the SDKs help AUTHOR and MAINTAIN the underlying foundation context." + +Project lead, 2026-09-29, on Measurements (for the three-axes figure and the next-generation schema page): + +> Measurements don't have EXPLICIT units, they indicate what KIND of thing they are: length (with appropriate length units, which could be mm, cm, whatever), CT density (HU), or count (unitless), ratio (unitless), velocity (m/s, cm/s), volume (cc, mL, mm^3..., L) + +Project lead, 2026-09-24, on the Overview page: + +> OIDM is NOT just for modeling imaging results, it is for modeling the ENTIRE imaging workflow context throughout its lifecycle. It's meant to enable a common platform across the medical imaging ecosystem that enables tools that will assist radiologists, technologists, ordering providers, and back-office staff. RIGHT NOW, we happen to be focused on data structures and semantics for representing imaging results, whether from current exams or prior, and from AIs, radiologists, technologists, or modalities themselves, and for integrating those results across time. We will ALSO model the broader patient context and enable linking between imaging results and patients' OTHER diagnoses, procedures, and issues. + +> The imaging persona doesn't place the rest of the patient context "alongside"--it integrates them into a single, larger graph, underpinned by the Foundation Context. Basically, the imaging results can be integrated into the larger fabric of the patient's history. + +> Let's call it "finding/diagnosis definitions" since they're used so interchangably. It really might be that "ObservationType" is the better catch-all term. + +> "Pointers" is a... well, pointless inclusion. The idea is that the patient graph is deeply interconnected with the Foundation Context graph, attaching baseline clinical knowledge to the contingent patient facts. [...] every finding, diagnosis, location, and exam type. + +Source added by the project lead on 2026-09-24: `~/exam-types/sources/LoincRsnaRadiologyPlaybook.xlsx` (44,178 Playbook part rows with LOINC numbers, part types, RadLex IDs and preferred names; a second sheet `core-playbook-dev`, 995 rows) for the exam-types page and diagrams. The ACR-RSNA-CDEs next-gen graph (`docs/next-gen-schema/graph/*.jsonl`, `alpha/graph/definition-graph.json`, `diagrams/*.svg`) supplies real nodes and typed edges for the Foundation Context figures. + +Wording rules from these: "finding/diagnosis definitions" is the term for the WHAT axis for now ("Observation Type" is under consideration as the catch-all, not adopted); the connection between the graphs is described as interconnection that attaches knowledge to facts, never as pointers; the Imaging Persona integrates imaging results into one larger graph of the patient's history. + +Project lead, 2026-09-23, correcting the first version of this plan: + +> I think in the foundation context page, we need the document on finding models to be something that encompasses both OIFMs (and that repo) but also the RSNA/ACR Common Data Elements and the relationshpi between the two. We should ALSO think about how to present the work on the next-generation schema (which will be for BOTH CDEs and OIFMs--I think applications will be able to use both at once). + +Project lead, 2026-09-22: + +> There should be a big overview page, layout out a HIGH-LEVEL justification of the project, talking about what the CORE PIECES of it ARE (not the DETAILS, just basics about what they ARE). These are the first three pillars: foundation context, data structures, SDK. +> Then the next couple of things: start with some basics of USE CASES. We can have a LIST oriented view of cases, only SOME of which have a more fleshed out page. +> We can also have a list of our sample/demo applications, some of which are RELATED to the use cases. +> There should be a directory on the foundation context, with some documents on the key ideas related to FindingModels/CDEs and the definitions that go into them. There should be a page on the OIFM repository (with a node to its relationship to the CDE project). Tehre should be a page on AnatomicLocations, referencing existing work but also pointing to the fact that we're migrating to incorporate with RadLex. We can also describe the ExamType content to come. + +And, on the shape: no page budget; the number of pages follows from the ideas. "Repository pages" are not a pattern: the one page on the OIFM repository exists because that repository *is* the inclusive collection of finding models. + +## Source pipeline, October 1 + +> I'd like to have a literal knowledge bundle of OKF files which are mostly for internal team use. They're in a public repo, and anyone can look at them and see what they see, but they're not what we'd present. We'd ALSO like to have a series of interconnected pages that ARE intended as the outward presentation of these ideas, where the knowledge for them is a distillation of the broader knowledgebase to make it more accessible to outsiders. + +> I would ALSO like to maintain something like source material. I will create a non-public bucket on tigris for raw source material (presentations, manuscripts, emails, etc.). I (or people I give appropriate credentials to) can access that bucket to fill out a raw-sources directory, but this is NOT in the repo. We will generate DETAILED distilled summaries with key quotes and ESPECIALLY extracted graphics into a references directory. We can cite these in our knowledge bundle as needed, and if required go back and dig into the raw source. + +> I would promote "paraphrase and describe figures" to "generate new versions of the figures capturing key points". + +## Repository-only knowledge and one plan, October 2 + +The project lead described the intended pipeline as: + +> a Tigris bucket down to processed summaries to a repo-only OKF knowledge graph to the actual pages based on them + +The project lead asked to replace conflicting planning documents with one coherent plan, discard superseded instructions, and integrate useful material into a compact set. Temporary files may live in an ignored directory exposed through `.peruseshow` where review is useful. + +The old glossary was dropped by the project lead on September 29. New terms grow from the new pages as needed; incoming links must be repaired before removal. The reporting vendor and the organizations behind the 47 and 31 finding models remain generalized. The project lead permitted reuse of the SIIM deck and CDE-repository figures on September 29. + +## Source-review snapshot, September 20–22 + +The following is retained from the original build plan because existing pages cite its inventory and work-edge observations. It is a dated research snapshot, not a fresh source audit or a current work queue. Broad absence claims below describe that review and must be checked against the later idea inventory and corrected drafts before reuse. + +Inventory reports for every source live in the session scratchpad and are summarized here. "Read from" names the branch used for current state. + +| Source | Role | Read from | Key migration candidates | +|---|---|---|---| +| findingmodel | OIFM library, `anatomic-locations` package, CLIs, MCP server | `main` (2026-03) for released state; `dev` and `feature/metadata-cleanup` (2026-06-29) for the work edge | `docs/anatomic-locations.md`, `docs/manifest_schema.md`, `.claude/skills/manage-anatomic-locations/reference/*`, `tasks/done/anatomic-locations-normalized-schema.md`, enrichment PRD and plans, schema-versioning notes, `notebooks/data/anatomic_locations_noembed.json` (2,926 records, current anatomic data); on the work edge: `docs/canonical-structured-metadata-and-enrichment-rewrite.md`, ADRs 0001 to 0003, `tasks/canonical-structured-metadata-implementation-plan.md`, `docs/plans/metadata-enrichment-current-plan.md` | +| findingmodels | Content: 2,382 finding model definitions (1,933 Gamuts-derived, 256 OIDM, 115 CDE, 47 MGB, 31 MSFT), `ids.json`, MGB exam-oriented sub-taxonomies | `taxonomy-export-2026-08-15` (one commit ahead of `main`) for content direction; `main` for the corpus; content branches for in-flight batches | `schema/finding_model_schema.md`, `lists/README.md` and the six taxonomy CSVs (3,789 rows, 1,028 matched), `docs/plans/definition_cleanup.md`, `docs/plans/rebuild-finding-skills.md`, `docs/plans/findingmodel-upstream-proposals.md`; on branches: `content/chestcts` PLAN and batch progress, `findingmodels-metadata` plan history and schema changes, `content/headcts` | +| med-ontology-lookup | `molu` terminology lookup across RadLex, SNOMED CT, FMA, LOINC, UMLS | `main` (2026-09-20); no unmerged branches; offline-CI work uncommitted in a worktree | `docs/product-roadmap.md` (contains the only written exam-type design), `docs/project-review-and-proposal.md` (six-phase design), `docs/plans/2026-09-05-github-issue-backlog.md` | +| FindingModelForge | Authoring web app at fmf.oidm.org | `dev` (2026-01) is ahead of stale `main`; `feature/model-editing` (2025-11) | `docs/finding-model-creation-workflow.md`, `docs/DRAFT_WORKFLOW.md`, `docs/feature_finding_model_draft_saving.md`, `zOld/plans/*` (history), issue #13 model editing | +| CDEStaging | 259 informally authored CDE definitions upstream of RadElement | `main` (2025-11); all recent work lands on `main`; 11 topic branches from 2024 never merged | `docs/report_extraction_process.md`, `report_representation/{negative_statements,uncovered_findings,missing_attributes}.md`, `definitions/upmedic/roadmap.md`; the unmerged `report_representation` and `gamuts_work` branches noted in history | +| finding-models-site | Static catalog reader for findingmodels | `main` | Nothing beyond a profile entry | +| imaging-problem-list | Exam Finding List and Imaging Problem List spec, viewer, sample data; on `dev`, a full extraction, coding, persistence, and review platform | `dev` (2026-07-21, 258 commits ahead of `main`) for current state; `main` for the stable spec; the one feature branch is empty | `README.md`, `CLAUDE.md` (domain model), `docs/anatomic-location-assignment-rules.md`, `docs/technical-imaging-findings.md`, `docs/plans/anatomic-location-efl-ipl.md`, `docs/archive/finding-and-location-code-assignment-plan.md`, `initial-extraction-plan.md`, `docs/plans/viewer-v2-anatomy-dashboard.md`, `docs/plans/extractor-evals-redesign.md`, the extraction-reviewer plans, `prompts/validator_prompt_example.md`, issue #1 "Create System of Data Models", both PNG diagrams, sample data | +| IPL-MVP-ExtractionAndLabeling | Early 4-stage extraction and mapping pipeline | `main` (2025-10); three single-commit experiment branches | `README.md`; `FHIR_Example_Structure` on `Persistent_FHIR_Resources` (a design note without a `.md` extension) | +| ACR-RSNA-CDEs | Allied external project. `next-gen-2026` holds the project lead's next-generation vocabulary work | `next-gen-2026` (2026-09-15) | `CONTEXT.md` glossary terms; `notes/oifm-overview.md`, `notes/oifm-metadata-fields.md`, `notes/oifm-schema-v2-draft.md`, `notes/ihe-idr-extract.md`, `notes/hood-taxonomies-profile-2026-09-01.md`; the ideas in `docs/next-gen-schema/00, 01, 03, 07, 11` summarized as goals; `docs/check_bundle.py` conventions. Committee minutes and email extracts stay in the CDE repo. | +| RadLex (RSNA) | Allied external project: ontology (`RadLex.owl`, 46,899 RIDs) and a search pipeline repo | `main`; GitHub issue #3 "Add Anatomic Locations" | `docs/plans/hybrid-fts-search.md` (referenced, not migrated) | +| anatomiclocations.org, BodyPartIndex.py, BodyPartIndex.ts | Original curated anatomic location set (2,890 nodes) and wrapper libraries | `main` of each (2022 to 2024) | `docs/index.markdown` (rationale, exam-type roadmap line), `docs/code.markdown`, `data/body_parts_schema.json`, `BodyPartIndex.py/TODO.md` | +| OpenImagingDataModel.py and .ts | Lineage reference implementations | `main` | `observation.py` and `finding_model.py` shapes as FHIR precedent; Zod schemas as early types | +| FHIRSamples | Lineage: lung screening DiagnosticReport plus chained CDE-coded Observations | `main` | All JSON examples as FHIR mapping precedent | +| UseCases | Lineage: ~25 use case ideas and the six RSNA value categories | `main` | `Index.md`, `README.md` | +| openimagingdata.org (repo) | Lineage: old site scaffold and schema fixtures | `main` | `schemas/schema_differences.md`, sample JSON | +| openimagingdata.org (Ghost site) | 15 posts (2023-06 to 2025-03) and an About page | live | "Findings, CDEs, and Observations", "Data Model: Structure and Function", "Benchmarking a Vision", "OIDM-Based Next-gen Reporting Assistance Framework", the SIIM 2024 posts | +| CDETemplateDemo, SARTemplatesToCDEs, template_extraction, ReportFindingRefiner, get_ontology_findings, ontology_tools, common_data_elements | Lineage | `main` | `SARTemplatesToCDEs/RectalCAStaging/Changes_vs_RDES11.md`, `ReportFindingRefiner/README.md`, `common_data_elements/README.md` and schema versions; the rest as one-paragraph history entries | +| Gamma deck, January 2026 | Vision and roadmap | live | Whole deck as a Presentation Extract | +| SIIM 2026 deck, June 2026 ("Reports of the Future") | The framing: Patient Context and Foundation Context; the three axes (Observation Type, Anatomic Location, Exam Type) as OIDM layers over CDE/OIFM, RadLex, and the Playbook; relationships and external citations; SDKs that resolve codes; the ask | `sources/bucket/siim2026/` (public talk) | Whole deck; the slide 7 table and the foundation-context graph as figures, at the project lead's discretion | +| oidm-knowledge-sources bucket folder | Manuscripts (JACR IPL; two JDIM under review), the AI-evolution essay, the reporting-schema deck, the July 2026 SIIM webinar deck | `sources/bucket/REPORT.md` | Paraphrase and cite per the manuscripts rule; reviewer correspondence never used | + +### The work edge + +The project lead asked that the knowledgebase reflect the entire work edge, not only default branches. Cite the branch and pin the commit they read for each account of current state. + +- **findingmodels.** The MGB exam-oriented sub-taxonomies on `taxonomy-export-2026-08-15` are the immediate direction for all content and are expected to replace many of the Gamuts-derived models, which make up 1,933 of the 2,382 current definitions. Rows without a matched OIFM ID are the next triage task. Three content pipelines (`content/chestcts` as the live CT-chest front, `content/headcts`, `km`) and a metadata-enrichment branch touching 78 models are in flight, none merged. The finding model schema is being extended on three uncoordinated branches with nothing landed on `main`. +- **findingmodel.** The live thread is the canonical structured-metadata rewrite on `feature/metadata-cleanup` and `dev`: eight new optional fields (body regions, subspecialties, etiologies, entity type, applicable modalities, expected time course, age profile, sex specificity), a multi-agent enrichment architecture, and a dual-database release plan. The older eight-facet specification is superseded. The branch's own readiness assessment is not yet passing. Twenty-four open issues map one-to-one onto task plans. +- **imaging-problem-list.** `dev` is current. Eight active plans center on human-review tooling, an honesty-first evaluation redesign, an anatomy-aware viewer, and local-model strategy. Anatomic-location coding of findings has landed. Issue #1, a formal Pydantic model layer for Observation, Exam Finding List, and Imaging Problem List with JSON Schema export, is broader than any current plan and unclaimed. +- **med-ontology-lookup.** No unmerged branches. A twelve-issue backlog follows the six-phase design document. Offline CI is implemented but uncommitted. +- **CDEStaging, IPL-MVP-ExtractionAndLabeling, FindingModelForge.** Work lands on `main` in CDEStaging with stale 2024 topic branches never merged. IPL-MVP has three single-commit experiments, including a longitudinal FHIR-persistence design note. Forge's `dev` and `feature/model-editing` branches are ahead of `main`. + +Facts worth stating up front because they shape the writing: + +- The taxonomies-over-Gamuts direction and the metadata rewrite are stated as current direction in the finding model documents and the roadmap, sourced to their branches. +- Two anatomic location lineages coexist: the 2022 `anatomiclocations.org` set with wrapper libraries, and the newer `anatomic-locations` package inside `findingmodel` with laterality and dual hierarchies. The knowledgebase documents both and states which is current. +- The FHIR mapping for Exam Finding Lists and Imaging Problem Lists is documented but implemented nowhere. The knowledgebase says so. +- No exam-type artifact exists. The area is documented as goals plus existing building blocks. +- The "Open Imaging Reporting SDK" named in the deck has no artifact. It appears only in the roadmap. +- The Hood finding taxonomies are named after their author in the source repo. The knowledgebase refers to them as "the MGB exam-oriented sub-taxonomies" and credits the contributing institution, per the organizations-only rule. The project lead may override. diff --git a/knowledge/references/status-update-2026-01.md b/archive/knowledge/references/status-update-2026-01.md similarity index 88% rename from knowledge/references/status-update-2026-01.md rename to archive/knowledge/references/status-update-2026-01.md index c6a5351..8fcdc62 100644 --- a/knowledge/references/status-update-2026-01.md +++ b/archive/knowledge/references/status-update-2026-01.md @@ -4,7 +4,7 @@ title: Status update, January 2026 description: A faithful extract of the January 2026 OIDM status deck, "Realizing Object-Oriented Imaging Results", slide by slide. tags: [references, deck, vision, roadmap] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T15:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: deck resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal @@ -13,9 +13,9 @@ sources: # About this extract -This is an extract of the January 2026 Open Imaging Data Model (OIDM) status deck, presented by the project lead under the running subtitle "Standardizing the DNA of Imaging IT."[^deck] Sections below follow the deck's slides in order and keep its wording and emphasis. Two departures from the source are noted where they occur: the collaborators slide is reduced to organizations, per this knowledgebase's convention of naming organizations and not individuals, and screenshots are described rather than reproduced. +This extract follows the January 2026 Open Imaging Data Model (OIDM) status deck, presented by the project lead under the subtitle "Standardizing the DNA of Imaging IT."[^deck] Sections below follow the deck's slides in order and keep its wording and emphasis. Two departures from the source are noted where they occur: the collaborators slide is reduced to organizations, per this knowledgebase's convention of naming organizations and not individuals, and screenshots are described rather than reproduced. -The deck is the primary statement of OIDM's direction as of early 2026. Where it names something that does not yet exist, this extract keeps the claim as the deck makes it; [Architecture](/overview/architecture.md) states the implementation status of each piece, and [Vision](/overview/vision.md) works through the reasoning. +The deck is the primary statement of OIDM's direction as of early 2026. Where it names something that does not yet exist, this extract keeps the claim as the deck makes it. See [Architecture](/overview/architecture.md) for implementation status and [Vision](/overview/vision.md) for the rationale. # Title diff --git a/archive/knowledge/repositories/index.md b/archive/knowledge/repositories/index.md new file mode 100644 index 0000000..b686278 --- /dev/null +++ b/archive/knowledge/repositories/index.md @@ -0,0 +1,5 @@ +# Repositories + +Source repositories for OIDM code and content. + +* [Repository map](./repository-map.md) - OIDM source repositories, their roles, branches, activity, deployments, and related documentation. diff --git a/archive/knowledge/repositories/repository-map.excalidraw b/archive/knowledge/repositories/repository-map.excalidraw new file mode 100644 index 0000000..0fe47b2 --- /dev/null +++ b/archive/knowledge/repositories/repository-map.excalidraw @@ -0,0 +1,2024 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "text", + "id": "hdr_cde_stage", + "x": 0, + "y": 16, + "width": 89.32, + "height": 17.5, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CDE STAGING", + "originalText": "CDE STAGING", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hdr_fm_pipeline", + "x": 0, + "y": 186, + "width": 113.67999999999999, + "height": 17.5, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "FINDING MODELS", + "originalText": "FINDING MODELS", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hdr_apps", + "x": 0, + "y": 356, + "width": 97.44, + "height": 17.5, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "APPLICATIONS", + "originalText": "APPLICATIONS", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hdr_anatomy", + "x": 0, + "y": 526, + "width": 56.839999999999996, + "height": 17.5, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "ANATOMY", + "originalText": "ANATOMY", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hdr_terms", + "x": 0, + "y": 696, + "width": 146.16, + "height": 17.5, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "TERMINOLOGY LOOKUP", + "originalText": "TERMINOLOGY LOOKUP", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "cdesnap", + "x": 0, + "y": 40, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "cdesnap_t", + "type": "text" + }, + { + "id": "f3", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "cdesnap_t", + "x": 10, + "y": 50, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "common_data_elements\nRadElement snapshot", + "originalText": "common_data_elements\nRadElement snapshot", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "cdesnap", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "cdes", + "x": 305, + "y": 40, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "cdes_t", + "type": "text" + }, + { + "id": "f4", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "cdes_t", + "x": 315, + "y": 50, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CDEStaging\n259 draft CDE\ndefinitions", + "originalText": "CDEStaging\n259 draft CDE\ndefinitions", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "cdes", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "radelement", + "x": 0, + "y": 210, + "width": 190, + "height": 80, + "strokeColor": "#c2410c", + "backgroundColor": "#fed7aa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "radelement_t", + "type": "text" + }, + { + "id": "f1", + "type": "arrow" + }, + { + "id": "f3", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "radelement_t", + "x": 10, + "y": 220, + "width": 170, + "height": 60, + "strokeColor": "#7c2d12", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "RadElement (ACR/RSNA)\nRDES and RDE codes", + "originalText": "RadElement (ACR/RSNA)\nRDES and RDE codes", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "radelement", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "fms", + "x": 305, + "y": 210, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "fms_t", + "type": "text" + }, + { + "id": "f1", + "type": "arrow" + }, + { + "id": "f2", + "type": "arrow" + }, + { + "id": "f4", + "type": "arrow" + }, + { + "id": "f5", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "fms_t", + "x": 315, + "y": 220, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "findingmodels\n2,382 finding models", + "originalText": "findingmodels\n2,382 finding models", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fms", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "fm", + "x": 610, + "y": 210, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#60a5fa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "fm_t", + "type": "text" + }, + { + "id": "f2", + "type": "arrow" + }, + { + "id": "f6", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "fm_t", + "x": 620, + "y": 220, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "findingmodel\nOIFM format, index,\nCLIs, MCP", + "originalText": "findingmodel\nOIFM format, index,\nCLIs, MCP", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fm", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f1", + "x": 190, + "y": 250.0, + "width": 115, + "height": 0.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 115, + 0.0 + ] + ], + "startBinding": { + "elementId": "radelement", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fms", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f1_l", + "x": 202.84, + "y": 228.0, + "width": 89.32, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CDE-derived", + "originalText": "CDE-derived", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f2", + "x": 495, + "y": 250.0, + "width": 115, + "height": 0.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 115, + 0.0 + ] + ], + "startBinding": { + "elementId": "fms", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fm", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f2_l", + "x": 507.84000000000003, + "y": 228.0, + "width": 89.32, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "definitions", + "originalText": "definitions", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f3", + "x": 95.0, + "y": 210, + "width": 0.0, + "height": -90, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + -90 + ] + ], + "startBinding": { + "elementId": "radelement", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "cdesnap", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f3_l", + "x": 62.52, + "y": 143.0, + "width": 64.96, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "snapshot", + "originalText": "snapshot", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f4", + "x": 400.0, + "y": 120, + "width": 0.0, + "height": 90, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 90 + ] + ], + "startBinding": { + "elementId": "cdes", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fms", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f4_l", + "x": 339.1, + "y": 143.0, + "width": 121.8, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "content batches", + "originalText": "content batches", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ipl", + "x": 0, + "y": 380, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ipl_t", + "type": "text" + }, + { + "id": "f9", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ipl_t", + "x": 10, + "y": 390, + "width": 170, + "height": 60, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "imaging-problem-list\nuses models +\nlocations", + "originalText": "imaging-problem-list\nuses models +\nlocations", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ipl", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "site", + "x": 305, + "y": 380, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "site_t", + "type": "text" + }, + { + "id": "f5", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "site_t", + "x": 315, + "y": 390, + "width": 170, + "height": 60, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "finding-models-site\ncorpus catalog", + "originalText": "finding-models-site\ncorpus catalog", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "site", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "forge", + "x": 610, + "y": 380, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1055, + "version": 1, + "versionNonce": 1056, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "forge_t", + "type": "text" + }, + { + "id": "f6", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "forge_t", + "x": 620, + "y": 390, + "width": 170, + "height": 60, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1057, + "version": 1, + "versionNonce": 1058, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Finding Model Forge\nfmf.oidm.org", + "originalText": "Finding Model Forge\nfmf.oidm.org", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "forge", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f5", + "x": 400.0, + "y": 290, + "width": 0.0, + "height": 90, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1059, + "version": 1, + "versionNonce": 1060, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 90 + ] + ], + "startBinding": { + "elementId": "fms", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "site", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f5_l", + "x": 347.22, + "y": 313.0, + "width": 105.55999999999999, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1061, + "version": 1, + "versionNonce": 1062, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "git submodule", + "originalText": "git submodule", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f6", + "x": 705.0, + "y": 290, + "width": 0.0, + "height": 90, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1063, + "version": 1, + "versionNonce": 1064, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 90 + ] + ], + "startBinding": { + "elementId": "fm", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "forge", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f6_l", + "x": 680.64, + "y": 313.0, + "width": 48.72, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1065, + "version": 1, + "versionNonce": 1066, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "engine", + "originalText": "engine", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "cdes_note", + "x": 0, + "y": 302, + "width": 190, + "height": 32.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1067, + "version": 1, + "versionNonce": 1068, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "(CDEStaging also has an\ninformal path to RadElement)", + "originalText": "(CDEStaging also has an\ninformal path to RadElement)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "radlex", + "x": 0, + "y": 550, + "width": 190, + "height": 80, + "strokeColor": "#c2410c", + "backgroundColor": "#fed7aa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1069, + "version": 1, + "versionNonce": 1070, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "radlex_t", + "type": "text" + }, + { + "id": "f7", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "radlex_t", + "x": 10, + "y": 560, + "width": 170, + "height": 60, + "strokeColor": "#7c2d12", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1071, + "version": 1, + "versionNonce": 1072, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "RadLex (RSNA)\nRID codes", + "originalText": "RadLex (RSNA)\nRID codes", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "radlex", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "alorg", + "x": 305, + "y": 550, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1073, + "version": 1, + "versionNonce": 1074, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "alorg_t", + "type": "text" + }, + { + "id": "f7", + "type": "arrow" + }, + { + "id": "f8", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "alorg_t", + "x": 315, + "y": 560, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1075, + "version": 1, + "versionNonce": 1076, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "anatomiclocations.org\n+ BodyPartIndex", + "originalText": "anatomiclocations.org\n+ BodyPartIndex", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "alorg", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "alpkg", + "x": 610, + "y": 550, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#60a5fa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1077, + "version": 1, + "versionNonce": 1078, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "alpkg_t", + "type": "text" + }, + { + "id": "f8", + "type": "arrow" + }, + { + "id": "f9", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "alpkg_t", + "x": 620, + "y": 560, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1079, + "version": 1, + "versionNonce": 1080, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "anatomic-locations pkg\ninside findingmodel", + "originalText": "anatomic-locations pkg\ninside findingmodel", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "alpkg", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f7", + "x": 190, + "y": 590.0, + "width": 115, + "height": 0.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1081, + "version": 1, + "versionNonce": 1082, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 115, + 0.0 + ] + ], + "startBinding": { + "elementId": "radlex", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "alorg", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f7_l", + "x": 223.14, + "y": 568.0, + "width": 48.72, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1083, + "version": 1, + "versionNonce": 1084, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "subset", + "originalText": "subset", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f8", + "x": 495, + "y": 590.0, + "width": 115, + "height": 0.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1085, + "version": 1, + "versionNonce": 1086, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 115, + 0.0 + ] + ], + "startBinding": { + "elementId": "alorg", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "alpkg", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f8_l", + "x": 524.08, + "y": 568.0, + "width": 56.839999999999996, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1087, + "version": 1, + "versionNonce": 1088, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lineage", + "originalText": "lineage", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f9", + "x": 705.0, + "y": 550, + "width": -610.0, + "height": -90, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1089, + "version": 1, + "versionNonce": 1090, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -610.0, + -90 + ] + ], + "startBinding": { + "elementId": "alpkg", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ipl", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f9_l", + "x": 371.58, + "y": 483.0, + "width": 56.839999999999996, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1091, + "version": 1, + "versionNonce": 1092, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "used by", + "originalText": "used by", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "others", + "x": 0, + "y": 720, + "width": 190, + "height": 80, + "strokeColor": "#c2410c", + "backgroundColor": "#fed7aa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1093, + "version": 1, + "versionNonce": 1094, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "others_t", + "type": "text" + }, + { + "id": "f10", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "others_t", + "x": 10, + "y": 730, + "width": 170, + "height": 60, + "strokeColor": "#7c2d12", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1095, + "version": 1, + "versionNonce": 1096, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SNOMED CT, FMA,\nLOINC, UMLS", + "originalText": "SNOMED CT, FMA,\nLOINC, UMLS", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "others", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "molu", + "x": 305, + "y": 720, + "width": 190, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#60a5fa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1097, + "version": 1, + "versionNonce": 1098, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "molu_t", + "type": "text" + }, + { + "id": "f10", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "molu_t", + "x": 315, + "y": 730, + "width": 170, + "height": 60, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1099, + "version": 1, + "versionNonce": 1100, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "med-ontology-lookup\nmolu", + "originalText": "med-ontology-lookup\nmolu", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "molu", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "mvp", + "x": 610, + "y": 720, + "width": 190, + "height": 80, + "strokeColor": "#64748b", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1101, + "version": 1, + "versionNonce": 1102, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "mvp_t", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "mvp_t", + "x": 620, + "y": 730, + "width": 170, + "height": 60, + "strokeColor": "#475569", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1103, + "version": 1, + "versionNonce": 1104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "IPL-MVP-\nExtractionAndLabeling\n(superseded)", + "originalText": "IPL-MVP-\nExtractionAndLabeling\n(superseded)", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "mvp", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "f10", + "x": 190, + "y": 760.0, + "width": 115, + "height": 0.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1105, + "version": 1, + "versionNonce": 1106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 115, + 0.0 + ] + ], + "startBinding": { + "elementId": "others", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "molu", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "f10_l", + "x": 219.08, + "y": 738.0, + "width": 56.839999999999996, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1107, + "version": 1, + "versionNonce": 1108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lookups", + "originalText": "lookups", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "molu_note", + "x": 305, + "y": 814, + "width": 286.52, + "height": 32.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1109, + "version": 1, + "versionNonce": 1110, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "molu also enriches findingmodel\nmetadata by ontology search (see text)", + "originalText": "molu also enriches findingmodel\nmetadata by ontology search (see text)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "legend", + "x": 0, + "y": 860, + "width": 565.5, + "height": 32.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1111, + "version": 1, + "versionNonce": 1112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Orange: external terminology. Light blue: content repository.\nMid blue: library. Dark blue: application. Dashed: lineage or superseded.", + "originalText": "Orange: external terminology. Light blue: content repository.\nMid blue: library. Dark blue: application. Dashed: lineage or superseded.", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": {} +} \ No newline at end of file diff --git a/knowledge/repositories/repository-map.md b/archive/knowledge/repositories/repository-map.md similarity index 79% rename from knowledge/repositories/repository-map.md rename to archive/knowledge/repositories/repository-map.md index 0d1068f..8e4a69e 100644 --- a/knowledge/repositories/repository-map.md +++ b/archive/knowledge/repositories/repository-map.md @@ -1,10 +1,10 @@ --- type: Reference title: Repository map -description: Every source repository behind OIDM, with its organization, role, status, branch of record, last activity, deployed URL, and the documents in this bundle that cover it. +description: OIDM source repositories, their roles, branches, activity, deployments, and related documentation. tags: [repositories, index, meta] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T02:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: oidm-org @@ -14,7 +14,7 @@ sources: resource: https://github.com/RSNA/RadLex/issues/3 title: RSNA/RadLex issue 3, "Add Anatomic Locations", opened 2026-08-10 - id: build-plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, source map and work edge sections - id: taxonomy-branch resource: https://github.com/openimagingdata/findingmodels/blob/a30c3c95fa3943e7340ce87575f4b1b926987eb8/lists/README.md @@ -26,7 +26,7 @@ sources: # How to read this map -Twenty-six repositories carry the Open Imaging Data Model (OIDM). Twenty-one belong to the `openimagingdata` GitHub organization, three anatomic-location repositories belong to the project lead's personal account, and two belong to RSNA as allied external projects. +Open Imaging Data Model (OIDM) sources span twenty-six repositories. Twenty-one belong to `openimagingdata`, three anatomic-location repositories belong to the project lead's personal account, and two allied projects to RSNA. Four status values are used. @@ -37,7 +37,7 @@ Four status values are used. | lineage | Superseded by later work; kept for history | | dead | Never completed and superseded by nothing | -"Branch of record" names the branch that represents current state for readers of this bundle. Where a repository's released state and its work edge differ, both are named. "Last activity" is the repository-wide push date reported by GitHub on 2026-09-21, which can be later than the tip commit on the branch of record when the most recent push landed on another branch. +"Branch of record" names the branch that represents current state for readers of this bundle. Where a repository's released state and its work edge differ, both are named. "Last activity" is the repository-wide push date reported by GitHub on 2026-09-21. A push to another branch can make it later than the branch-of-record tip. # The repositories @@ -74,71 +74,26 @@ Four status values are used. Content repositories hold definitions and data. Library repositories turn those definitions into typed objects and searchable indexes. Application repositories consume both. Two external terminologies, RadElement and RadLex, sit outside the project and are referenced by code, not vendored into it. -```mermaid -flowchart TD - subgraph external[External terminologies] - RE[RadElement
RDES and RDE codes] - RL[RadLex
RID codes] - SN[SNOMED CT, FMA,
LOINC, UMLS] - end - - subgraph content[Content repositories] - CDS[CDEStaging
259 draft CDE definitions] - CDE[common_data_elements
RadElement snapshot] - FMS[findingmodels
2,382 finding models] - AL[anatomiclocations.org
2,890 anatomic locations] - end - - subgraph libs[Libraries] - FM[findingmodel
OIFM format, index, CLIs, MCP] - ALP[anatomic-locations package
inside findingmodel] - MOLU[med-ontology-lookup
molu] - BPI[BodyPartIndex.py and .ts] - end - - subgraph apps[Applications] - FMF[FindingModelForge
fmf.oidm.org] - FSITE[finding-models-site] - IPL[imaging-problem-list
extraction, coding, viewers] - MVP[IPL-MVP-ExtractionAndLabeling] - end - - CDS --> RE - CDE --> RE - FMS --> FM - AL --> BPI - AL --> ALP - RL --> ALP - RL --> MOLU - SN --> MOLU - RE --> FMS - MOLU --> FM - ALP --> FM - FM --> FMF - FMS --> FSITE - FM --> IPL - ALP --> IPL - FMS --> IPL - FMS --> MVP - CDS --> FMS -``` - -Three edges deserve a note. The `findingmodels` corpus is pulled into `finding-models-site` as a git submodule and resolved by `imaging-problem-list` through the published `ids.json` file, so the content repository is the single source of finding model identity. The newer anatomic data lives inside `findingmodel` as the `anatomic-locations` package rather than in the original `anatomiclocations.org` repository, and the two lineages are not synchronized. RSNA/RadLex issue 3, "Add Anatomic Locations", opened 2026-08-10, tracks folding the curated anatomic location set into RadLex itself with an exit criterion of full coverage; that is a GitHub tracking issue, not a code dependency. +[![How the repositories relate: read each row left to right, from an external terminology or content repository, through the library that packages it, to the application that uses it. RadElement feeds findingmodels and CDEStaging; findingmodels feeds findingmodel; findingmodel feeds Finding Model Forge and imaging-problem-list. RadLex feeds anatomiclocations.org, which feeds the anatomic-locations package inside findingmodel. SNOMED CT, FMA, LOINC, and UMLS feed med-ontology-lookup, which enriches findingmodel. findingmodels also feeds finding-models-site via a git submodule and the superseded IPL-MVP-ExtractionAndLabeling.](./repository-map.svg)](./repository-map.svg) + +*Diagram source: `repository-map.excalidraw` beside this document (click the image for full size), generated by `tools/diagrams/build_repository_map.py`.* + +Three edges deserve a note. The `findingmodels` corpus is pulled into `finding-models-site` as a git submodule and resolved by `imaging-problem-list` through the published `ids.json` file, so the content repository is the single source of finding model identity. Current anatomic data lives in `findingmodel`'s `anatomic-locations` package, unsynchronized with `anatomiclocations.org`. RSNA/RadLex issue 3, "Add Anatomic Locations", opened 2026-08-10, tracks folding the curated anatomic location set into RadLex itself with an exit criterion of full coverage. It creates no code dependency. # Work edge -Branch inventories taken on 2026-09-21 established what follows. None of the unmerged branches named here has an open pull request. +These branch inventories date to 2026-09-21. None of the unmerged branches below has an open pull request. -**findingmodel.** The live thread is the canonical structured-metadata rewrite, on `feature/metadata-cleanup` (tip `1942b06`, 2026-06-29) and its base `dev` (tip `504a42a`, 2026-04-11). It adds eight optional fields to the finding model base class, splits metadata assignment across seven typed agents, and plans a dual-database release so existing consumers do not break. The branch's own readiness assessment still reports a failure: two fields remain below the quality floor. The earlier eight-facet specification on `main` is superseded by this field set. Twenty-four open issues map one to one onto task plans in the repository. +**findingmodel.** The canonical metadata rewrite uses `feature/metadata-cleanup` (tip `1942b06`, 2026-06-29) and its base `dev` (tip `504a42a`, 2026-04-11). It adds eight optional fields to the finding model base class, splits metadata assignment across seven typed agents, and plans a dual-database release so existing consumers do not break. The branch's own readiness assessment still reports a failure: two fields remain below the quality floor. The earlier eight-facet specification on `main` is superseded by this field set. Twenty-four open issues map one to one onto task plans in the repository. **findingmodels.** Six branches carry unmerged work. `taxonomy-export-2026-08-15` (tip `a30c3c9`) replaces the legacy `lists/` directory with the six MGB exam-oriented sub-taxonomies as CSVs totaling 3,789 rows, of which 1,028 already match an existing finding model identifier by exact name; the remaining rows are named as the next triage task. Two content pipelines add large batches that have never merged: a CT chest conversion branch with 1,822 new definitions and a three-agent merge, create, and review pipeline of which one of twenty-one chunks is complete, and a head CT branch with 419 new definitions plus an overlapping review branch with 76 more. A metadata branch applies an approved enrichment baseline to 78 existing definitions and adds no new ones. A clinician-request branch adds 59 definitions for an intracranial problem list application. The finding model JSON schema is being extended on three of these branches independently, and none of those changes has landed on `main`. -**imaging-problem-list.** `dev` (tip `36fa30c`, 2026-07-22) is 258 commits ahead of `main` (tip `06f64a7`, 2025-11-18) and is current state. Anatomic-location coding of findings has landed, and the Imaging Problem List grouping key changed from finding code alone to finding code plus location. Eight active plans center on human-review tooling, an evaluation redesign that scores quote-first matching rather than raw finding yield, an anatomy-aware viewer, and a reassessment of local model choice. The only open issue, number 1, asks for a formal Pydantic model layer for Observation, Exam Finding List, and Imaging Problem List with JSON Schema export; it is broader than any current plan and unclaimed. The one open pull request is empty after its design document was moved to a separate repository. +**imaging-problem-list.** `dev` (tip `36fa30c`, 2026-07-22) is 258 commits ahead of `main` (tip `06f64a7`, 2025-11-18) and represents current state. Anatomic-location coding of findings has landed, and the Imaging Problem List grouping key changed from finding code alone to finding code plus location. Eight active plans center on human-review tooling, an evaluation redesign that scores quote-first matching rather than raw finding yield, an anatomy-aware viewer, and a reassessment of local model choice. The only open issue, number 1, asks for a formal Pydantic model layer for Observation, Exam Finding List, and Imaging Problem List with JSON Schema export; it is broader than any current plan and unclaimed. The one open pull request is empty after its design document was moved to a separate repository. **med-ontology-lookup.** No branch is ahead of `main`. A twelve-issue backlog follows the six-phase design in the repository's review and proposal document; nine of those issues are open and unstarted. Offline continuous integration is implemented but sits uncommitted in a working tree, pending authorization to publish. **CDEStaging.** All current work lands directly on `main`; the most recent commit is 2025-11-25. Fourteen topic branches exist and eleven are unmerged, the largest being a 2024 report-representation branch of 37 commits and a content branch of 33 commits. No branch has a commit after 2025-03-22, and no pull request proposes merging any of them. -**IPL-MVP-ExtractionAndLabeling.** Three single-commit experiment branches pushed within one week of each other in October 2025, none merged: a longitudinal multiple sclerosis sample dataset, a port of the extraction step to a typed agent, and a design note proposing persistent FHIR Observations with stable identifiers so repeat mentions of a finding across reports link to one entity. That design note carries no file extension and adds no code. +**IPL-MVP-ExtractionAndLabeling.** Three single-commit experiment branches pushed within one week of each other in October 2025, none merged: a longitudinal multiple sclerosis sample dataset, a port of the extraction step to a typed agent, and a design note proposing persistent FHIR Observations with stable identifiers so repeat mentions of a finding across reports link to one entity. The extensionless design note adds no code. **FindingModelForge.** Both `dev` (96 commits ahead of `main`, 2026-01-02) and `feature/model-editing` (60 commits ahead, 2025-11-28) are unmerged. The editing branch implements AI-assisted iteration of published models rather than manual editing; its first sprint is complete and two later sprints are not started. A peer-review workflow with public drafts and comments is finished on `dev` and absent from `main`. diff --git a/archive/knowledge/repositories/repository-map.svg b/archive/knowledge/repositories/repository-map.svg new file mode 100644 index 0000000..bc5b2fe --- /dev/null +++ b/archive/knowledge/repositories/repository-map.svg @@ -0,0 +1,2 @@ +CDE STAGINGFINDING MODELSAPPLICATIONSANATOMYTERMINOLOGY LOOKUPcommon_data_elementsRadElement snapshotCDEStaging259 draft CDEdefinitionsRadElement (ACR/RSNA)RDES and RDE codesfindingmodels2,382 finding modelsfindingmodelOIFM format, index,CLIs, MCPCDE-deriveddefinitionssnapshotcontent batchesimaging-problem-listuses models +locationsfinding-models-sitecorpus catalogFinding Model Forgefmf.oidm.orggit submoduleengine(CDEStaging also has aninformal path to RadElement)RadLex (RSNA)RID codesanatomiclocations.org+ BodyPartIndexanatomic-locations pkginside findingmodelsubsetlineageused bySNOMED CT, FMA,LOINC, UMLSmed-ontology-lookupmoluIPL-MVP-ExtractionAndLabeling(superseded)lookupsmolu also enriches findingmodelmetadata by ontology search (see text)Orange: external terminology. Light blue: content repository.Mid blue: library. Dark blue: application. Dashed: lineage or superseded. \ No newline at end of file diff --git a/knowledge/roadmap/anatomic-locations-and-radlex.md b/archive/knowledge/roadmap/anatomic-locations-and-radlex.md similarity index 70% rename from knowledge/roadmap/anatomic-locations-and-radlex.md rename to archive/knowledge/roadmap/anatomic-locations-and-radlex.md index 7cbd5b9..fcf675f 100644 --- a/knowledge/roadmap/anatomic-locations-and-radlex.md +++ b/archive/knowledge/roadmap/anatomic-locations-and-radlex.md @@ -1,10 +1,10 @@ --- type: Roadmap title: Anatomic locations and RadLex -description: "The stated goals for the anatomic location set: incorporation into RadLex as an overlay, the node requests owed to the RadLex anatomy track, the two unreconciled datasets, the tooling items carried forward from the original libraries, and the body region enumerations that disagree." +description: RadLex incorporation, anatomy node requests, dataset differences, tooling goals, and conflicting body regions. tags: [roadmap, anatomic-locations, radlex, laterality, tooling] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T19:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: radlex-issue-3 @@ -26,7 +26,7 @@ sources: resource: https://github.com/talkasab/anatomiclocations.org/blob/1f39fa45f621cef947a3f3ef1f869334cfa5c841/docs/index.markdown title: anatomiclocations.org site homepage and roadmap - id: plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, recording the project lead's statements and the 2026-09-21 branch inventories - id: graph-research resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/tasks/anatomic-locations-graph-research.md @@ -50,35 +50,35 @@ No code, commit message, or document inside the `RSNA/RadLex` repository mention # The overlay agreement -A more specific agreement, dated 13 September 2026, is recorded in the next-generation vocabulary work on the `next-gen-2026` branch of `ACR-RSNA-CDEs`. The anatomic locations file is **an overlay on [RadLex](/glossary/radlex.md)**, not a replacement and not the sole source: RadLex concepts and predicates sit underneath, and the file adds identifiers RadLex lacks, its own containment and part-of hierarchies, and [laterality](/glossary/laterality.md).[^cde-axis] +A 13 September 2026 agreement on `ACR-RSNA-CDEs`'s `next-gen-2026` branch defines the anatomic locations file as an overlay on [RadLex](/glossary/radlex.md). It adds missing identifiers, containment and part-of hierarchies, and [laterality](/glossary/laterality.md) to RadLex concepts and predicates. It does not replace RadLex or supply all anatomy content.[^cde-axis] -Two consequences are already written into that vocabulary. An anatomic location carries one identifier, its [RadLex identifier](/glossary/radlex-id.md), and a RadLex code is never shown beside it, because the location code is the RadLex code. And the compound identifiers for sided variants that RadLex lacks, such as `RID1363_RID5825`, are an acknowledged exception the RadLex track is expected to remove by minting real identifiers.[^cde-axis] +An anatomic location carries one identifier, its [RadLex identifier](/glossary/radlex-id.md), and a RadLex code is never shown beside it, because the location code is the RadLex code. And the compound identifiers for sided variants that RadLex lacks, such as `RID1363_RID5825`, are an acknowledged exception the RadLex track is expected to remove by minting real identifiers.[^cde-axis] The same record states that the overlay file is "the basis of the RadLex anatomy axis going forward," because the RadLex anatomy track is editing it rather than the published release.[^cde-axis] Two framings of the relationship are therefore in force, and nobody has reconciled them. The issue's exit criterion, full coverage of the terminology in RadLex, reads as absorption. The September agreement describes an overlay that RadLex edits in place. The two are compatible but not identical, and no source says which governs. It is carried in [open questions](/roadmap/open-questions.md). -The scale of the sided-variant exception is worth stating with it: 1,016 of the 2,926 records carry a composite identifier, so more than a third of the set is not resolvable as a RadLex identifier until real ones are minted. +Composite identifiers occur on 1,016 of 2,926 records. More than a third of the set therefore awaits resolvable RadLex identifiers. # What is owed in each direction -The gap log on that branch is the tracking artifact, kept deliberately separate from the wider design discussion so it can move independently.[^cde-gaps] The full list of node requests, with the reason and date for each, is reproduced in [RadLex integration](/semantic-foundation/anatomic-locations/radlex-integration.md). Two items are worth naming here because they are structural rather than individual concepts. +The branch's gap log tracks requests independently of design discussions.[^cde-gaps] [RadLex integration](/semantic-foundation/anatomic-locations/radlex-integration.md) lists each request, reason, and date. Two requests affect the structure of the dataset. **From OIDM to RadLex: real identifiers for sided variants.** Raised 2026-09-13, needed by every sided [Observation](/glossary/observation.md). -**From RadLex to OIDM: an is-a relation and structure-type nodes.** Raised 2026-08-19 and restated 2026-09-13 as the top priority. The curated set has no is-a relation and no structure-type nodes, which is recorded as its major shortcoming, because it blocks any scope statement of the form "applies to tendons." RadLex 4.3 has an is-a chain for every one of the 1,910 plain-identifier locations, and the stated work item is a derived, pinned overlay file handed to the anatomic locations track. Two cautions accompany it: RadLex's own release note warns that some is-a categorizations were converted from part-of and may be wrong, and the FMA-style chain is not the clinically useful layer.[^cde-axis] +**From RadLex to OIDM: an is-a relation and structure-type nodes.** Raised 2026-08-19 and restated 2026-09-13 as the top priority. The curated set has no is-a relation and no structure-type nodes, which is recorded as its major shortcoming, because it blocks any scope statement of the form "applies to tendons." RadLex 4.3 has an is-a chain for every one of the 1,910 plain-identifier locations, and the stated work item is a derived, pinned overlay file handed to the anatomic locations track. The record gives two cautions: RadLex's own release note warns that some is-a categorizations were converted from part-of and may be wrong, and the FMA-style chain is not the clinically useful layer.[^cde-axis] -Coverage of external codes is uneven and is recorded as a gap rather than assigned as work: [SNOMED CT](/glossary/snomed-ct.md) on 1,782 of 2,926 records, with 608 records carrying no codes at all.[^cde-gaps] +External-code coverage remains an unassigned gap: [SNOMED CT](/glossary/snomed-ct.md) on 1,782 of 2,926 records, with 608 records carrying no codes at all.[^cde-gaps] # The two datasets -Two lineages coexist and are not synchronized: the original 2,890-node set published at anatomiclocations.org with its two wrapper libraries, and the 2,926-record dataset inside the `anatomic-locations` package in `findingmodel`, which the build plan names as current.[^plan] The differences and the open questions between them are set out in [lineage and current implementation](/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md). +Two datasets are unsynchronized: the original 2,890-node set published at anatomiclocations.org with its two wrapper libraries, and the 2,926-record dataset inside the `anatomic-locations` package in `findingmodel`, which the build plan names as current.[^plan] The differences and the open questions between them are set out in [lineage and current implementation](/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md). Reconciling the record counts is stated nowhere as work. No document says what the extra 36 records are, and no process syncs edits in either direction. # Items carried forward from the original libraries -The clearest statement of intent for the original lineage is the TODO list added to `BodyPartIndex.py` on 2024-02-03. None of its items is done in that repository, and several were done independently in the current package.[^bpi-todo] +The `BodyPartIndex.py` TODO list dates to 2024-02-03. None of its items was completed there, though the current package independently completed several.[^bpi-todo] | Item | Status | |---|---| @@ -96,7 +96,7 @@ The site roadmap additionally lists finishing `BodyPartIndex.py` and publishing The project lead stated in the 2026-09-20 planning interview that the area needs tooling for the people who work with anatomic locations, meaning curators and coders rather than library consumers.[^plan] What exists today, the package, its command-line interface, the curation skill, and the older wrappers, is described in [tooling](/semantic-foundation/anatomic-locations/tooling.md). -Four concrete items sit in the `findingmodel` repository against that surface. +Four `findingmodel` items address this tooling. - **Hierarchy-aware related models.** A task on `dev` records that `related_models()` requires an exact identifier match today and therefore misses hierarchy-aware relations.[^hierarchy-related] - **Graph representation research.** A note on `main` works through representing the containment and laterality graph in DuckDB and records why the adjacency-list plus recursive-query alternative was not chosen for a read-only database.[^graph-research] @@ -107,7 +107,7 @@ Search quality is not a side issue for the RadLex relationship. The search and c # Four body region enumerations -[Body region](/glossary/body-region.md) is enumerated four incompatible ways across OIDM: in the anatomic locations package, in the finding model metadata rewrite, in the extraction schema, and in the viewer's filter. Thorax against chest, and whether spine is a region at all, are the recurring mismatches. No document reconciles them and no source assigns the reconciliation. It is carried, with the rest of the area's unresolved items, in [open questions](/roadmap/open-questions.md). +[Body region](/glossary/body-region.md) is enumerated four incompatible ways across OIDM: in the anatomic locations package, in the finding model metadata rewrite, in the extraction schema, and in the viewer's filter. Thorax against chest, and whether spine is a region at all, are the recurring mismatches. Reconciliation is undocumented and unassigned. See [open questions](/roadmap/open-questions.md). [^radlex-issue-3]: RSNA/RadLex issue 3, "Add Anatomic Locations" [^cde-axis]: The anatomy axis, current state, next-gen-2026 branch diff --git a/knowledge/roadmap/exam-types.md b/archive/knowledge/roadmap/exam-types.md similarity index 73% rename from knowledge/roadmap/exam-types.md rename to archive/knowledge/roadmap/exam-types.md index 1a6d290..866d226 100644 --- a/knowledge/roadmap/exam-types.md +++ b/archive/knowledge/roadmap/exam-types.md @@ -1,14 +1,14 @@ --- type: Roadmap title: Exam types roadmap -description: The goals stated for an OIDM exam type layer, by whom and when, the one written design that exists, the building blocks a future effort would start from, and the questions no source answers. +description: Exam type goals, the unimplemented Playbook profile design, and unresolved ownership and data questions. tags: [roadmap, exam-types, loinc, playbook, anatomy] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T19:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, recording the project lead's exam type goals stated in the 2026-09-20 planning interview - id: al-site resource: https://github.com/talkasab/anatomiclocations.org/blob/1f39fa45f621cef947a3f3ef1f869334cfa5c841/docs/index.markdown @@ -35,19 +35,19 @@ sources: # The state of the area -There is no [exam type](/glossary/exam-type.md) artifact. No registry, no curated code list, no exam-to-body-part mapping, and no code anywhere in the Open Imaging Data Model (OIDM) repositories that parses an exam name into modality and anatomy axes. What exists is a goal stated three times across four years, one written design that has not been implemented, and three places where a bare [LOINC](/glossary/loinc.md) code does real work today. +Open Imaging Data Model (OIDM) has no [exam type](/glossary/exam-type.md) registry, curated code list, exam-to-body-part map, or code that parses exam names into modality and anatomy. The goal appears three times across four years. One design is unimplemented, and three existing uses rely on bare [LOINC](/glossary/loinc.md) codes. -The area as it stands is described in [exam types](/semantic-foundation/exam-types/overview.md), and the inventory of what a future effort would start from is in [existing building blocks](/semantic-foundation/exam-types/existing-building-blocks.md). This document collects the goals and the open questions. +See [exam types](/semantic-foundation/exam-types/overview.md) and [existing building blocks](/semantic-foundation/exam-types/existing-building-blocks.md) for current capabilities. # The goal as the project lead states it -In the 2026-09-20 planning interview recorded in [the knowledgebase build plan](/plans/2026-09-20-knowledgebase-build-plan.md), the project lead described the intended work in three parts.[^plan] +In the 2026-09-20 planning interview recorded in [the knowledgebase build plan](/references/project-direction.md), the project lead described the intended work in three parts.[^plan] 1. **Wrapper tooling around an existing knowledge graph**, namely the graph represented by the [LOINC/RSNA Radiology Playbook](/glossary/loinc-rsna-radiology-playbook.md) together with [RadLex](/glossary/radlex.md), rather than a new vocabulary. 2. **A set of preferred high-level entries**, at the granularity clinicians and systems actually use, the examples given being CT Chest, MRI Brain, and X-ray Knee, each marked as the preferred entry among the many Playbook codes describing variants of the same study. 3. **Tight coupling to [anatomic locations](/glossary/anatomic-location.md)**, with typed edges from an exam type to the structures it covers, distinguishing anatomy that is **always included**, which may be expressed as a hierarchy rather than a flat list, anatomy that is **usually included**, and anatomy that is **possibly included and must be checked**. -The third part is what would make the artifact more than a code list. It answers the question both a report reader and an extraction pipeline need answered: given this study, what could have been assessed? +The anatomy links would tell readers and extraction tools what a study could have assessed. # The same goal, stated earlier @@ -59,13 +59,13 @@ The site post "Data Model: Structure and Function" of 2024-01-25 names two utili # The one written design -The most developed written design is in the terminology tool's product roadmap, which proposes replacing an ever-growing default search with named, inspectable domain profiles. Its `radiology` profile makes the Playbook first-class: RadLex, LOINC weighted toward the Playbook, [SNOMED CT](/glossary/snomed-ct.md), and [FMA](/glossary/fma.md), with [UMLS](/glossary/umls.md) as the crosswalk hub.[^molu-roadmap] +The terminology tool's product roadmap proposes named, inspectable domain profiles in place of a growing default search. Its `radiology` profile makes the Playbook first-class: RadLex, LOINC weighted toward the Playbook, [SNOMED CT](/glossary/snomed-ct.md), and [FMA](/glossary/fma.md), with [UMLS](/glossary/umls.md) as the crosswalk hub.[^molu-roadmap] Four Playbook-specific implications are stated in it: rank procedure and orderable queries toward Playbook terms and finding queries toward the other vocabularies; teach the crosswalk the Playbook correspondences, linking a LOINC code to its historic `RPID` and to RadLex anatomy and modality attributes, recording that in the mapping provenance; detect `RPID` codes as well as LOINC-shaped codes; and do not treat every LOINC hit as radiology.[^molu-roadmap] None of it is built. The repository's issue backlog follows a six-phase design as one tracking issue plus eleven focused ones, of which only typed provider failures is closed; the profile mechanism is part of that backlog, not a released capability.[^molu-backlog] See [med-ontology-lookup](/semantic-foundation/terminologies/med-ontology-lookup.md). -Two facts from the next-generation vocabulary analysis constrain any such effort and are worth stating with the goals. The Playbook is "actively governed, and freely licensed for commercial and non-commercial use," shipping twice yearly under joint Regenstrief Institute and RSNA governance, and it "is a separate artifact" from RadLex.[^current-understanding] Current Playbook content carries ordinary LOINC codes; `RPID` identifiers are legacy and appear only as historical codes a crosswalk would need to recognize.[^molu-roadmap] +The next-generation vocabulary analysis records two constraints. The Playbook is "actively governed, and freely licensed for commercial and non-commercial use," shipping twice yearly under joint Regenstrief Institute and RSNA governance, and it "is a separate artifact" from RadLex.[^current-understanding] Current Playbook content carries ordinary LOINC codes; `RPID` identifiers are legacy and appear only as historical codes a crosswalk would need to recognize.[^molu-roadmap] # What is unanswered diff --git a/knowledge/roadmap/finding-model-content-direction.md b/archive/knowledge/roadmap/finding-model-content-direction.md similarity index 84% rename from knowledge/roadmap/finding-model-content-direction.md rename to archive/knowledge/roadmap/finding-model-content-direction.md index 81dd343..0456631 100644 --- a/knowledge/roadmap/finding-model-content-direction.md +++ b/archive/knowledge/roadmap/finding-model-content-direction.md @@ -1,14 +1,14 @@ --- type: Roadmap title: Finding model content direction -description: "Where the finding model corpus is going: the MGB exam-oriented sub-taxonomies as the content spine, the expected supersession of Gamuts-derived models, the triage of unmatched rows, the content batches in flight, the definition cleanup plan, and the open content issues." +description: Exam-oriented content priorities, unmatched taxonomy rows, unmerged batches, and corpus cleanup plans. tags: [roadmap, finding-models, content, taxonomy, gamuts, cleanup] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T19:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, recording the project lead's statement of 2026-09-21 - id: lists-readme resource: https://github.com/openimagingdata/findingmodels/blob/a30c3c95fa3943e7340ce87575f4b1b926987eb8/lists/README.md @@ -53,7 +53,7 @@ sources: # The stated direction -The project lead stated on 2026-09-21, recorded in [the knowledgebase build plan](/plans/2026-09-20-knowledgebase-build-plan.md), that the MGB exam-oriented sub-taxonomies are the immediate direction for all [finding model](/glossary/finding-model.md) content, and that they are expected to replace many of the [Gamuts](/glossary/gamuts.md)-derived models that currently make up 1,933 of the corpus's 2,382 definitions.[^plan] +The project lead stated on 2026-09-21, recorded in [the knowledgebase build plan](/references/project-direction.md), that the MGB exam-oriented sub-taxonomies are the immediate direction for all [finding model](/glossary/finding-model.md) content, and that they are expected to replace many of the [Gamuts](/glossary/gamuts.md)-derived models that currently make up 1,933 of the corpus's 2,382 definitions.[^plan] That is a change in how content is chosen. The Gamuts import brought in whatever the source ontology contained, which is a differential-diagnosis ontology, so a large share of the resulting definitions name diagnoses rather than findings. The sub-taxonomies instead enumerate what a radiologist reading a particular [exam type](/glossary/exam-type.md) actually reports, organized by that exam type. The artifacts themselves, their columns, counts, and hierarchy semantics, are described in [the sub-taxonomies reference](/semantic-foundation/finding-models/finding-taxonomies.md). @@ -69,7 +69,7 @@ The sub-taxonomy export minted no identifiers. Rows were matched by exact name a | Carrying an existing OIFM identifier | 1,028 | | Blank, awaiting triage | 2,761 | -The README names the next task in one sentence: "Blank rows are the ones needing triage."[^lists-readme] Match rates are uneven, from 86 percent on head CT and 76 percent on chest radiography down to 10 percent on the combined chest, abdomen, and pelvis CT file and 3 percent on mammography. No plan document assigns the triage work, and no issue tracks it. +The README states: "Blank rows are the ones needing triage."[^lists-readme] Match rates are uneven, from 86 percent on head CT and 76 percent on chest radiography down to 10 percent on the combined chest, abdomen, and pelvis CT file and 3 percent on mammography. No plan document assigns the triage work, and no issue tracks it. # Content batches in flight @@ -83,7 +83,7 @@ Five `findingmodels` branches carry unmerged content work. None has an open pull | a clinician-request branch | 2025-12-12 | 59 | A requested batch for an intracranial problem list application | | `findingmodels-metadata` | 2026-06-01 | 0, modifies 78 | Applies an approved metadata enrichment baseline to existing definitions | -The CT chest branch is the live front of that pipeline. Its plan records the refactor from one monolithic agent into three focused ones with Python-level orchestration, with phases 1 through 3 complete and two follow-ups open: the create agent extracts fewer attributes than the single-agent version did, and the sub-findings schema is unresolved.[^chest-plan] Its progress document is explicit about how far the batch has run: 205 total sources, chunked into 21 groups of 10, with chunk 1 complete and chunks 2 through 21 pending.[^chest-progress] +Its plan records the refactor from one monolithic agent into three focused ones with Python-level orchestration, with phases 1 through 3 complete and two follow-ups open: the create agent extracts fewer attributes than the single-agent version did, and the sub-findings schema is unresolved.[^chest-plan] Its progress document is explicit about how far the batch has run: 205 total sources, chunked into 21 groups of 10, with chunk 1 complete and chunks 2 through 21 pending.[^chest-progress] The head CT branch's one plan document is marked complete for the soft tissue category: four new models created, nine mappings made to existing models, review done, identifiers written back, validator clean.[^headct-plan] @@ -93,7 +93,7 @@ An earlier CT chest branch, `cde_to_finding`, holds 1,915 definitions from the s A draft plan on `main`, still marked "DRAFT - for review," covers cleaning the existing corpus against the authoring conventions. It scopes 2,379 models: 1,934 Gamuts, 264 OIDM already reviewed, 115 from the common data element work, 47 from MassGeneral Brigham, and 19 from Microsoft.[^cleanup] -It gives an ordered execution sequence with counts. +The plan orders the work as follows. | Order | Category | Models | Nature of the work | |---:|---|---:|---| @@ -131,7 +131,7 @@ Five of the ten open `findingmodels` issues are content batches. None is closed Two further open issues, 17 and 32, ask for a static marketing site and a static catalog site for the content; the catalog site exists and is profiled in [the finding models site](/applications/finding-models-site.md). -How the corpus is organized and browsed today is in [the content catalog](/semantic-foundation/finding-models/content-catalog.md), and the identifier scheme that governs writebacks is in [identifiers](/semantic-foundation/finding-models/identifiers.md).[^ids-json] Where sources disagree about corpus counts and content classification, see [open questions](/roadmap/open-questions.md). +See [the content catalog](/semantic-foundation/finding-models/content-catalog.md) for organization and browsing, and [identifiers](/semantic-foundation/finding-models/identifiers.md) for writeback rules.[^ids-json] Where sources disagree about corpus counts and content classification, see [open questions](/roadmap/open-questions.md). [^plan]: Knowledgebase build plan, recording the project lead's statement of 2026-09-21 [^lists-readme]: MGB exam-oriented sub-taxonomies README, taxonomy-export-2026-08-15 branch diff --git a/knowledge/roadmap/finding-model-format-evolution.md b/archive/knowledge/roadmap/finding-model-format-evolution.md similarity index 79% rename from knowledge/roadmap/finding-model-format-evolution.md rename to archive/knowledge/roadmap/finding-model-format-evolution.md index 389d93f..de7c893 100644 --- a/knowledge/roadmap/finding-model-format-evolution.md +++ b/archive/knowledge/roadmap/finding-model-format-evolution.md @@ -1,10 +1,10 @@ --- type: Roadmap title: Finding model format evolution -description: "The stated goals for where the Open Imaging Finding Model record format is going: the canonical structured-metadata rewrite, the source schema version 2 draft, relationship and graph ideas from the next-generation CDE work, versioning, and the discrepancies a revision would have to settle." +description: Proposed metadata, schema, relationship, and versioning changes to the finding model format. tags: [roadmap, finding-models, schema, metadata, relationships, versioning] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T19:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: rewrite @@ -23,7 +23,7 @@ sources: resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/docs/next-gen-schema/07-relationship-family.md title: The finding and diagnosis relationship family, ACR-RSNA-CDEs next-gen-2026 branch - id: plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, recording the project lead's statements of 2026-09-20 and 2026-09-21 - id: issue-29 resource: https://github.com/openimagingdata/findingmodel/issues/29 @@ -59,9 +59,9 @@ sources: # What this document collects -The released Open Imaging Finding Model (OIFM) record format is described in [the finding model format](/semantic-foundation/finding-models/finding-model-format.md). This document collects the goals that have been stated for changing it, with the source that states each one. Nothing here is implemented on `main` of any repository, and nothing here is a proposal by this knowledgebase. +The released Open Imaging Finding Model (OIFM) format is described in [the finding model format](/semantic-foundation/finding-models/finding-model-format.md). This document collects the goals that have been stated for changing it, with the source that states each one. Nothing here is implemented on `main` of any repository, and nothing here is a proposal by this knowledgebase. -Four separate threads are in play, and they are not coordinated with each other: a metadata rewrite on a `findingmodel` branch, a source schema version 2 draft circulating in the common data element work, relationship and graph ideas coming out of the next-generation CDE vocabulary, and a versioning plan for packages and database artifacts. +Four efforts remain uncoordinated: a metadata rewrite on a `findingmodel` branch, a source schema version 2 draft circulating in the common data element work, relationship and graph ideas coming out of the next-generation CDE vocabulary, and a versioning plan for packages and database artifacts. # The canonical structured-metadata rewrite @@ -69,13 +69,13 @@ The most advanced thread. The design document on `feature/metadata-cleanup` stat It adds eight optional fields to both finding model classes: `body_regions`, `subspecialties`, `etiologies`, `entity_type`, `applicable_modalities`, `expected_time_course`, `age_profile`, and `sex_specificity`. Their permitted values are listed in [the format document](/semantic-foundation/finding-models/finding-model-format.md); the pipeline that populates them is described in [the enrichment pipeline](/semantic-foundation/finding-models/enrichment-pipeline.md). -Two constraints ride with it. Canonical `index_codes` are narrowed to exact or clinically substitutable matches, with broader, narrower, and merely related candidates diverted to a separate review artifact. And model-level entries in `index_codes` and `anatomic_locations` are rejected when `display` is empty.[^rewrite] +Canonical `index_codes` allow only exact or clinically substitutable matches. Broader, narrower, and related candidates go to a review artifact. Model-level `index_codes` and `anatomic_locations` entries require a nonempty `display`.[^rewrite] **Status as the branch reports it.** The execution plan on `dev` records slices 1 through 8 and 9-A complete, with slice 9-B, migrating the MCP and command-line callers plus a bulk backfill, and slice 10, backfill, fixtures, and final documentation, remaining.[^impl-plan] The branch's own readiness assessment fails: the 2026-06-03 prompt-improvement document raises expected time course to between 0.76 and 0.78 and etiologies to between 0.91 and 0.93, then states that overall readiness remains a failure because age profile and index codes are below the quality floor.[^tempo] # The source schema version 2 draft -A separate and broader draft, dated 2026-04-20 and marked "a proposed authoring schema, not an implementation-complete contract yet," is kept in the `next-gen-2026` branch of `ACR-RSNA-CDEs` as a copy of an upstream gist.[^v2-draft] It proposes a different decomposition of the format than the metadata rewrite does, and the two have not been reconciled. +A broader draft, dated 2026-04-20 and marked "a proposed authoring schema, not an implementation-complete contract yet," is kept in the `next-gen-2026` branch of `ACR-RSNA-CDEs` as a copy of an upstream gist.[^v2-draft] Its format differs from the metadata rewrite and remains unreconciled with it. | Proposal | What the draft says | |---|---| @@ -87,15 +87,15 @@ A separate and broader draft, dated 2026-04-20 and marked "a proposed authoring | Registries | `relationship_types.json` and `quantity_kinds.json` alongside the corpus | | `schema_version` on the record | Required, `"2.0"` for version 2 source files | -The draft also lists explicit removals and changes against the current shape: drop `required`; replace the numeric `unit` with `quantity_kind` plus optional `common_units`; add `synonyms` to attributes and choice values; add model-level `references`; add `lifecycle` and `related_models`; allow canonical attribute references; and keep hydrated output close to the current structure.[^v2-draft] +The draft also proposes these changes: drop `required`; replace the numeric `unit` with `quantity_kind` plus optional `common_units`; add `synonyms` to attributes and choice values; add model-level `references`; add `lifecycle` and `related_models`; allow canonical attribute references; and keep hydrated output close to the current structure.[^v2-draft] Five decisions are left open in the draft itself, including whether runtime APIs expose both the authored and the effective relationship views, where registry files live, and whether canonical attributes may be numeric or carry index codes.[^v2-draft] # Relationships and the graph, from the CDE work -The project lead's stated goal for the format, recorded in [the knowledgebase build plan](/plans/2026-09-20-knowledgebase-build-plan.md), is increased metadata plus the relationship and graph work coming out of the common data element effort.[^plan] +The project lead's stated goal for the format, recorded in [the knowledgebase build plan](/references/project-direction.md), is increased metadata plus the relationship and graph work coming out of the common data element effort.[^plan] -That effort is described in [the next-generation vocabulary](/semantic-foundation/common-data-elements/next-generation-vocabulary.md). Three of its ideas bear directly on the finding model format. +[The next-generation vocabulary](/semantic-foundation/common-data-elements/next-generation-vocabulary.md) contributes three relevant ideas. - **Typed, identified relationships.** Eight relationship pairs plus a catch-all, each carrying its own identifier so a report-level assertion can cite the standing potential it expresses.[^doc07] - **The differential as a derived view.** Computed by traversing manifestation edges and filtering by context, with no `DIFFERENTIAL_OF` edge type.[^doc07] @@ -115,7 +115,7 @@ Record-format versioning appears only in the version 2 draft's `schema_version` # Superseded, and still open as an issue -The original eight-facet classification specification is superseded. Two task documents on `main` carry it, the task status summary marks it "Blocked - needs 4 design decisions" covering scope, required against optional, markdown representation, and search indexing,[^status] and the canonical metadata implementation plan states that it supersedes the facets plan for this workstream.[^impl-plan] Issue 25 remains open against the old design.[^issue-25] +Two `main` task documents describe the superseded eight-facet specification. The status summary marks it "Blocked - needs 4 design decisions" on scope, required versus optional fields, markdown representation, and search indexing.[^status] The canonical metadata implementation plan supersedes it for this workstream.[^impl-plan] Issue 25 remains open against the old design.[^issue-25] Four other open `findingmodel` issues shape the format rather than the tooling. Issue 5 asks for three model variants: a content-only form for language model extraction, a draft form carrying everything but identifiers and contributors, and the full saveable form.[^issue-5] Issue 3 asks for a human-editable markdown form without identifiers or codes, issue 4 for a tool that folds edits to that markdown back into the JSON while preserving identifiers, and issue 8 for a `FindingObservation` concept holding worked examples of a finding model in use. @@ -136,7 +136,7 @@ Two are recorded in the format documentation and are carried in [open questions] - **Choice value code indexing.** The prose mirror on `main` numbers the first choice value `.1`; the validator in the released code writes `.0`, and the stored corpus follows the code.[^prose-schema] - **Omissions in the prose mirror.** It omits `anatomic_locations` from the full model entirely, and states none of the length and cardinality constraints the code enforces.[^prose-schema] -The content-side direction, which is separate from the format, is in [the content direction roadmap](/roadmap/finding-model-content-direction.md). +See [the content direction roadmap](/roadmap/finding-model-content-direction.md) for corpus work. [^rewrite]: Canonical Structured Metadata and Enrichment Rewrite, feature/metadata-cleanup branch [^impl-plan]: Canonical structured metadata implementation plan, findingmodel dev branch diff --git a/archive/knowledge/roadmap/index.md b/archive/knowledge/roadmap/index.md new file mode 100644 index 0000000..9680eac --- /dev/null +++ b/archive/knowledge/roadmap/index.md @@ -0,0 +1,11 @@ +# Roadmap + +Stated goals and direction, area by area, with the source that states each one. Everything here is a fact about what a deck, an issue, a plan document, or the project lead has said. Nothing here is a design proposal by this knowledgebase; proposals are a separate workstream. Where sources disagree, the disagreement is recorded rather than resolved, and collected in open questions. + +* [Roadmap 2026](./roadmap-2026.md) - The January 2026 project goals, proposed working group, use-case pipeline, and current repository work. +* [Finding model format evolution](./finding-model-format-evolution.md) - Proposed metadata, schema, relationship, and versioning changes to the finding model format. +* [Finding model content direction](./finding-model-content-direction.md) - Exam-oriented content priorities, unmatched taxonomy rows, unmerged batches, and corpus cleanup plans. +* [Imaging Problem List data model system](./ipl-data-model-system.md) - Formal data model goals, eight active plans, anatomy reconciliation, and unimplemented FHIR and IHE mappings. +* [Exam types roadmap](./exam-types.md) - Exam type goals, the unimplemented Playbook profile design, and unresolved ownership and data questions. +* [Anatomic locations and RadLex](./anatomic-locations-and-radlex.md) - RadLex incorporation, anatomy node requests, dataset differences, tooling goals, and conflicting body regions. +* [Open questions](./open-questions.md) - Every conflict between sources and every unverified fact found while building this knowledgebase, grouped by area, with the sources on each side and the owner where one is indicated, plus the defects found in source documents. diff --git a/knowledge/roadmap/ipl-data-model-system.md b/archive/knowledge/roadmap/ipl-data-model-system.md similarity index 75% rename from knowledge/roadmap/ipl-data-model-system.md rename to archive/knowledge/roadmap/ipl-data-model-system.md index 8fcc0cf..25778f8 100644 --- a/knowledge/roadmap/ipl-data-model-system.md +++ b/archive/knowledge/roadmap/ipl-data-model-system.md @@ -1,17 +1,17 @@ --- type: Roadmap title: Imaging Problem List data model system -description: "The stated goals for the Observation, Exam Finding List, and Imaging Problem List layer: the formal model system asked for in issue 1, the eight active plans on the development branch, the anatomic-compatibility follow-on, and the FHIR and IHE alignments that are documented but unimplemented." +description: Formal data model goals, eight active plans, anatomy reconciliation, and unimplemented FHIR and IHE mappings. tags: [roadmap, data-structures, ipl, observation, fhir, ihe] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T19:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: issue-1 resource: https://github.com/openimagingdata/imaging-problem-list/issues/1 title: imaging-problem-list issue 1, "Create System of Data Models", opened 2026-01-28 - id: plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, recording the project lead's statements of 2026-09-20 and 2026-09-21 - id: deck resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal @@ -44,9 +44,9 @@ sources: # The goals as the project lead states them -In the 2026-09-20 planning interview that produced [the knowledgebase build plan](/plans/2026-09-20-knowledgebase-build-plan.md), the project lead described the Imaging Problem List work in four parts: define data structures for [Observations](/glossary/observation.md) and their relationships; manage and collate those observations across a patient's history; design the applications that use them; and publish the result as standard formats.[^plan] +In the 2026-09-20 planning interview that produced [the knowledgebase build plan](/references/project-direction.md), the project lead described the Imaging Problem List work in four parts: define data structures for [Observations](/glossary/observation.md) and their relationships; manage and collate those observations across a patient's history; design the applications that use them; and publish the result as standard formats.[^plan] -The January 2026 deck states the same layer as the second strategic pillar, an ordered hierarchy from the atomic Observation through the [Exam Finding List](/glossary/exam-finding-list.md) and the [Imaging Problem List](/glossary/imaging-problem-list.md) to the [Imaging Persona](/glossary/imaging-persona.md), and names the Imaging Problem List as "a standard format for structured imaging results, automatically extractable from narrative report text."[^deck] The structures as they exist today are documented under [data structures](/data-structures/); what follows is what has been stated about where they go. +The January 2026 deck makes this hierarchy its second strategic pillar: Observation, [Exam Finding List](/glossary/exam-finding-list.md), [Imaging Problem List](/glossary/imaging-problem-list.md), and [Imaging Persona](/glossary/imaging-persona.md). It calls the Imaging Problem List "a standard format for structured imaging results, automatically extractable from narrative report text."[^deck] The structures as they exist today are documented under [data structures](/data-structures/); what follows is what has been stated about where they go. # Issue 1: a formal system of data models @@ -57,9 +57,9 @@ The one open issue in the `imaging-problem-list` repository, opened 2026-01-28, - With **extensive annotation to generate JSON schemas**. - Using **camelCase aliases in export** against **snake_case object attributes** internally. -Each element of that corresponds to a gap that exists today. There is no Observation model, no JSON Schema, and no standalone Observation record; the structure exists only inside the Exam Finding List JSON and, separately, inside the extraction pipeline's own models. Both the Exam Finding List and Imaging Problem List sample files carry a `$schema` URL that resolves to nothing. And the camelCase against snake_case split is the convention the existing sample data already follows without a model to enforce it. +There is no formal Observation model, JSON Schema, or standalone record. Observations exist within Exam Finding List JSON and separate extraction models. Both list formats have sample `$schema` URLs that resolve to nothing. Sample data follows the camelCase and snake_case convention without a model enforcing it. -The Extracted Observation subtype the issue floats maps onto a distinction the pipeline already makes in practice, between what a language model produced and what has been coded. See [Observation](/data-structures/observation.md). +The proposed subtype reflects the pipeline's distinction between language model output and coded findings. See [Observation](/data-structures/observation.md). # The eight active plans as direction @@ -87,11 +87,11 @@ This is a data model question, not a display question, because the grouping key The mapping is stated in the specification on `main`: a report containing a list of [FHIR Condition](/glossary/fhir-condition.md) objects labeled with the finding identifier, each containing FHIR Observations documenting which exams the finding was recorded on, with exam date and LOINC-coded exam type.[^ipl-main] -No code in any repository produces a Condition resource, and no current plan claims to. The full picture, including the lineage precedents that do implement a FHIR encoding, is in [FHIR mapping](/data-structures/fhir-mapping.md). One lineage experiment states an adjacent goal: a design note on an unmerged `IPL-MVP-ExtractionAndLabeling` branch proposes persistent FHIR Observations with stable identifiers, so repeated mentions of a finding across reports link to one entity rather than creating new ones.[^fhir-design-note] It carries no file extension and adds no code. +No code in any repository produces a Condition resource, and no current plan claims to. See implemented lineage precedents in [FHIR mapping](/data-structures/fhir-mapping.md). One lineage experiment states an adjacent goal: a design note on an unmerged `IPL-MVP-ExtractionAndLabeling` branch proposes persistent FHIR Observations with stable identifiers, so repeated mentions of a finding across reports link to one entity rather than creating new ones.[^fhir-design-note] The extensionless note adds no code. # IHE Imaging Diagnostic Report alignment -The deck states that the Exam Finding List "connects to the IHE Imaging Diagnostic Report (IDR) FHIR representation."[^deck] That connection is a goal. No OIDM repository implements or references the profile; what exists is a full extract of the Phase II public comment draft on the `next-gen-2026` branch of `ACR-RSNA-CDEs`.[^idr-extract] +The deck states that the Exam Finding List "connects to the IHE Imaging Diagnostic Report (IDR) FHIR representation."[^deck] The connection remains a goal. No OIDM repository implements or references the profile. `ACR-RSNA-CDEs`'s `next-gen-2026` branch holds a full Phase II public comment draft extract.[^idr-extract] Four differences between the profile and current OIDM encodings are recorded in that extract, all listed as items to raise during public comment and none resolved: whether attributes ride in `Observation.component` or in `hasMember`; a vocabulary collision on the words "finding" and "observation"; whether a diagnosis is a FHIR Observation or a FHIR Condition; and whether [laterality](/glossary/laterality.md) lives in the location identifier or in a separate field. The profile also puts three questions back to the RadElement side, including what the coding system identifier for RadElement codes is. All are carried in [IHE IDR alignment](/data-structures/ihe-idr-alignment.md) and in [open questions](/roadmap/open-questions.md). diff --git a/knowledge/roadmap/open-questions.md b/archive/knowledge/roadmap/open-questions.md similarity index 99% rename from knowledge/roadmap/open-questions.md rename to archive/knowledge/roadmap/open-questions.md index f66d959..54407d3 100644 --- a/knowledge/roadmap/open-questions.md +++ b/archive/knowledge/roadmap/open-questions.md @@ -1,10 +1,10 @@ --- type: Reference title: Open questions -description: Every conflict between sources and every unverified fact found while building this knowledgebase, grouped by area, with the sources on each side and the owner where one is indicated. +description: Every conflict between sources and every unverified fact found while building this knowledgebase, grouped by area, with the sources on each side and the owner where one is indicated, plus the defects found in source documents. tags: [roadmap, open-questions, conflicts, gaps] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T19:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: deck resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal @@ -97,7 +97,7 @@ sources: resource: https://github.com/openimagingdata/findingmodels/issues/15 title: findingmodels issue 15, "Bring in CDE Staging MD content" - id: plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, the record of the 2026-09-20 and 2026-09-21 planning sessions --- @@ -258,7 +258,7 @@ Errors in source documents rather than open design questions. Each could become | `openimagingdata/oidm-knowledge` | `knowledge/glossary/open-imaging-reporting-sdk.md` | Cites a site URL that returns HTTP 404; the working URL, used in three other documents here, ends `oidm-based-next-gen-reporting-assistance/` | | `openimagingdata/oidm-knowledge` | `knowledge/references/imaging-problem-list-example.md` | Says the example holds 123 findings "across eight exams"; the file has ten distinct report identifiers over ten exam dates | | `openimagingdata/oidm-knowledge` | `knowledge/repositories/repository-map.md` | Gives last-commit dates for the three anatomic location repositories and for RadLex that do not match their default-branch tips, and names RadLex's branch of record as `main` although the search and curation pipeline exists only on an experiment branch | -| `openimagingdata/oidm-knowledge` | `knowledge/plans/2026-09-20-knowledgebase-build-plan.md` | The target tree sketches the Playbook glossary file as `loinc-playbook.md`; the file written is `loinc-rsna-radiology-playbook.md` and every link uses that name | +| `openimagingdata/oidm-knowledge` | Historical build-plan target tree | Resolved by planning consolidation on 2026-10-02; the obsolete target tree is retired. The existing glossary file remains `loinc-rsna-radiology-playbook.md`. | [^deck]: "Open Imaging Data Model 2026 Status Update", January 2026 [^prose-schema]: Finding Model Schema, prose mirror, findingmodels main branch diff --git a/knowledge/roadmap/roadmap-2026.md b/archive/knowledge/roadmap/roadmap-2026.md similarity index 82% rename from knowledge/roadmap/roadmap-2026.md rename to archive/knowledge/roadmap/roadmap-2026.md index 4b38e82..24dcd76 100644 --- a/knowledge/roadmap/roadmap-2026.md +++ b/archive/knowledge/roadmap/roadmap-2026.md @@ -1,17 +1,17 @@ --- type: Roadmap title: Roadmap 2026 -description: The project-level direction stated in the January 2026 status deck, the working group and use-case pipeline it proposes, and a per-repository summary of what is currently in flight. +description: The January 2026 project goals, proposed working group, use-case pipeline, and current repository work. tags: [roadmap, deck, working-group, acr, work-edge] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T19:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: deck resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal title: "Open Imaging Data Model 2026 Status Update: Realizing Object-Oriented Imaging Results, January 2026" - id: plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, recording the branch inventories of 2026-09-21 and the project lead's statements - id: repo-map resource: /repositories/repository-map.md @@ -32,9 +32,9 @@ sources: # What this document is -This is the project-level direction for the Open Imaging Data Model (OIDM), as stated by its sources. The single most complete statement is the January 2026 status deck, extracted in full at [status update, January 2026](/references/status-update-2026-01.md).[^deck] Below the deck sit the working repositories, whose direction is visible in their unmerged branches, plan documents, and open issues. +The January 2026 status deck states the Open Imaging Data Model (OIDM) direction. See [the full extract](/references/status-update-2026-01.md).[^deck] Repository branches, plans, and issues record work toward it. -Nothing here is a proposal. Each goal is attributed to the deck, an issue, or a plan document that states it. Where a goal has no artifact behind it, that is said. Per-area goals live in the five area roadmaps linked from [the roadmap index](/roadmap/); unresolved conflicts between sources are collected in [open questions](/roadmap/open-questions.md). +Nothing here is a proposal. Each goal is attributed to the deck, an issue, or a plan document that states it. Where a goal has no artifact behind it, that is said. Per-area goals live in the five [area roadmaps](/roadmap/); unresolved conflicts between sources are collected in [open questions](/roadmap/open-questions.md). # The three strategic pillars @@ -65,11 +65,11 @@ No record of the working group being convened exists in any repository read for Three, in the deck's own words: host and moderate an academic and vendor "big tent"; run a **use-case pipeline feeding the CDE group**; standardize the result.[^deck] -The use-case pipeline is the one of the three with existing material behind it. The lineage `UseCases` repository holds roughly 25 use case ideas organized against the RSNA value categories, described in [use cases](/history/use-cases.md). The direction the deck states is that such use cases feed the ACR and RSNA common data element group, which is the same direction as the workbench relationship described in [finding models and CDEs](/semantic-foundation/finding-models/finding-models-and-cdes.md). +The use-case pipeline is the one of the three with existing material behind it. The lineage `UseCases` repository provides roughly 25 ideas tagged with RSNA value categories. See [use cases](/history/use-cases.md). The deck would send such cases to the ACR and RSNA CDE group, consistent with the process described in [finding models and CDEs](/semantic-foundation/finding-models/finding-models-and-cdes.md). # The work edge, repository by repository -Branch inventories taken on 2026-09-21 established what follows; the full detail is in [the repository map](/repositories/repository-map.md).[^repo-map] None of the unmerged branches named here has an open pull request. +These inventories date to 2026-09-21. See [the repository map](/repositories/repository-map.md).[^repo-map] None of the unmerged branches named here has an open pull request. | Repository | Live thread | In flight | Area roadmap | |---|---|---|---| @@ -92,7 +92,7 @@ Three items in the deck have no artifact in any repository. They are direction, A fourth item, [exam types](/glossary/exam-type.md), is not in the deck at all but has been stated as a goal in three places since 2022 with no artifact produced; see [the exam types roadmap](/roadmap/exam-types.md). -The implementation status of every piece of the system, built and unbuilt, is tabulated in [architecture](/overview/architecture.md). +See implementation status in [architecture](/overview/architecture.md). [^deck]: "Open Imaging Data Model 2026 Status Update: Realizing Object-Oriented Imaging Results", January 2026 [^plan]: Knowledgebase build plan diff --git a/knowledge/semantic-foundation/anatomic-locations/data-model.md b/archive/knowledge/semantic-foundation/anatomic-locations/data-model.md similarity index 81% rename from knowledge/semantic-foundation/anatomic-locations/data-model.md rename to archive/knowledge/semantic-foundation/anatomic-locations/data-model.md index 3518e0d..a4b504a 100644 --- a/knowledge/semantic-foundation/anatomic-locations/data-model.md +++ b/archive/knowledge/semantic-foundation/anatomic-locations/data-model.md @@ -1,10 +1,10 @@ --- type: Format Specification title: Anatomic location data model -description: The record model for an anatomic location in both lineages, its identifier scheme, its two hierarchies, its laterality triads, its classification fields, and its external codes, with verified counts. +description: Anatomic location identifiers, fields, hierarchies, classification, and code coverage in both datasets. tags: [semantic-foundation, anatomic-locations, schema, radlex, laterality] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: al-schema resource: https://github.com/talkasab/anatomiclocations.org/blob/1f39fa45f621cef947a3f3ef1f869334cfa5c841/data/body_parts_schema.json @@ -37,7 +37,7 @@ sources: # Scope -This document describes the record: what an [anatomic location](/glossary/anatomic-location.md) carries and what each field means. The verbatim JSON Schema and the full runtime field list live in [the anatomic location record format reference](/references/anatomic-location-json-schema.md). Two datasets exist and are not reconciled with each other, so both are described here; [lineage and current implementation](/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md) says which is current and why. +An [anatomic location](/glossary/anatomic-location.md) record has two unreconciled formats. This page describes both. See [the record format reference](/references/anatomic-location-json-schema.md) for the verbatim JSON Schema and runtime fields, and [lineage and current implementation](/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md) for their status. | Dataset | Records | Version | Field naming | |---|---|---|---| @@ -76,11 +76,11 @@ Every record carries a containment parent. The distinction between the two hiera The original set stores parent pointers only and lets a library derive children. The current set stores both directions as `{id, display}` reference objects, so a consumer can read a node's children without indexing the whole file. Containment is a rooted tree from `RID39569`, whole body. The part-of relation covers fewer than half the records and is not a tree. -Two data defects are visible in the current file and are worth knowing before traversing it. 111 records carry a `partOfRef` pointing at themselves, and one carries a self-referential `leftRef`. The build process already expects self-referential containment at the root, which is legitimate, but self-referential part-of on 111 nodes is not.[^fm-normalized] +The current file has 111 self-referential `partOfRef` values and one self-referential `leftRef`. The build already expects self-referential containment at the root, which is legitimate, but self-referential part-of is a defect.[^fm-normalized] # Laterality triads -A sided structure is represented by three records, not by a flag on one. The generic record carries `leftRef` and `rightRef`; each sided record carries `unsidedRef` back to the generic one and a reference to its counterpart. The full convention, including the containment and synonym rules for sided entries, is in [laterality conventions](/semantic-foundation/anatomic-locations/laterality-conventions.md). See also [laterality](/glossary/laterality.md). +A sided structure uses generic, left, and right records. The generic record has `leftRef` and `rightRef`. Each sided record has `unsidedRef` and a counterpart reference. See [laterality conventions](/semantic-foundation/anatomic-locations/laterality-conventions.md) for containment and synonym rules, and [laterality](/glossary/laterality.md) for the term. | Field | Original set | Current set | |---|---|---| @@ -145,7 +145,7 @@ The current format, unchanged from the data file. It shows containment, part-of, # From record to usable object -The curation format is what an editor changes. The package that consumes it normalizes each record into an `AnatomicLocation` object, resolves the reference objects, adds a laterality enum, and precomputes a materialized containment path and part-of path so that ancestry, descendants, and the "is X inside Y" test are string comparisons rather than recursive walks.[^fm-normalized] A location converts to an [index code](/glossary/index-code.md) with system `anatomic_locations`, its RadLex identifier as the code, and its description as the display, which is the form it takes when attached to a [finding model](/glossary/finding-model.md).[^fm-location-model] +The curation format is what an editor changes. The package that consumes it normalizes each record into an `AnatomicLocation` object, resolves references, and adds a laterality enum. Precomputed containment and part-of paths make ancestry, descendants, and "is X inside Y" tests string comparisons.[^fm-normalized] A location becomes an [index code](/glossary/index-code.md) with system `anatomic_locations`, its RadLex identifier as code, and its description as display. [Finding models](/glossary/finding-model.md) use this form.[^fm-location-model] [^al-schema]: body_parts_schema.json, anatomiclocations.org [^al-data]: body_parts.json, release 1.0.0-rc.1 diff --git a/archive/knowledge/semantic-foundation/anatomic-locations/index.md b/archive/knowledge/semantic-foundation/anatomic-locations/index.md new file mode 100644 index 0000000..8ce05cf --- /dev/null +++ b/archive/knowledge/semantic-foundation/anatomic-locations/index.md @@ -0,0 +1,8 @@ +# Anatomic locations + +* [Why anatomic locations](./why-anatomic-locations.md) - Why OIDM curates anatomic identifiers and where the model uses them. +* [Anatomic location data model](./data-model.md) - Anatomic location identifiers, fields, hierarchies, classification, and code coverage in both datasets. +* [Laterality conventions](./laterality-conventions.md) - Sided location identifiers and references, with rules for assigning laterality to findings. +* [Lineage and current implementation](./lineage-and-current-implementation.md) - The original and current anatomic location datasets, their consumers, and unresolved differences. +* [RadLex integration](./radlex-integration.md) - RadLex integration, the anatomy overlay agreement, search limitations, and pending node requests. +* [Anatomic location tooling](./tooling.md) - Lookup, search, and hierarchy traversal through current and older anatomic location tools. diff --git a/knowledge/semantic-foundation/anatomic-locations/laterality-conventions.md b/archive/knowledge/semantic-foundation/anatomic-locations/laterality-conventions.md similarity index 82% rename from knowledge/semantic-foundation/anatomic-locations/laterality-conventions.md rename to archive/knowledge/semantic-foundation/anatomic-locations/laterality-conventions.md index c9e2b27..db5ba05 100644 --- a/knowledge/semantic-foundation/anatomic-locations/laterality-conventions.md +++ b/archive/knowledge/semantic-foundation/anatomic-locations/laterality-conventions.md @@ -1,10 +1,10 @@ --- type: Reference title: Laterality conventions -description: How sided anatomic locations are identified, linked, contained, and named in the curated set, and how a side is assigned to a finding at coding time. +description: Sided location identifiers and references, with rules for assigning laterality to findings. tags: [semantic-foundation, anatomic-locations, laterality, reference, migrated] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fm-laterality resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/.claude/skills/manage-anatomic-locations/reference/laterality-conventions.md @@ -25,7 +25,7 @@ sources: # Two questions -[Laterality](/glossary/laterality.md) is decided twice in OIDM, and the rules are different. Curation decides how a sided structure is represented in the [anatomic location](/glossary/anatomic-location.md) set. Coding decides which of those records a given finding in a given report gets. The first half of this document is the curation convention, migrated from the finding model repository's curation skill.[^fm-laterality] The second half is the assignment rule from the extraction platform.[^ipl-rules] +[Laterality](/glossary/laterality.md) has two sets of rules. Curation defines sided records in the [anatomic location](/glossary/anatomic-location.md) set, as described in the migrated curation convention below.[^fm-laterality] Coding assigns those records to findings using the extraction platform's rules.[^ipl-rules] # Compound identifier pattern @@ -90,9 +90,9 @@ Identifier `{base}_RID5825`. Has `leftRef`, its counterpart, and `unsidedRef`. ` | `unsidedRef` only | `generic` | Maps to generic | | None | `nonlateral` | Not a lateralized structure | -The key insight is that a reference points at the counterpart, not at itself. A left entry has `rightRef` and `unsidedRef`. It does not have `leftRef`, because it is the left variant. +A reference points to the counterpart. A left entry has `rightRef` and `unsidedRef`, but no `leftRef`. -This inversion is easy to get wrong, and it has been got wrong: open issue 39 in the finding model repository reports that the build's `determine_laterality()` assigns left and right backwards.[^fm-issues] +Open issue 39 in the finding model repository reports that the build's `determine_laterality()` assigns left and right backwards.[^fm-issues] # Containment and synonyms for sided entries @@ -102,7 +102,7 @@ Synonyms follow a consistent pattern. Generic `["hilum", "lung hilum", "hilum of # Assigning a side to a finding -Curation says which records exist. Assignment says which one a given observation gets. The precedence is stated as explicit text side, then sided-exam side, then generic unsided.[^ipl-rules] +The precedence is stated as explicit text side, then sided-exam side, then generic unsided.[^ipl-rules] - **A non-sided exam never introduces a side.** On a CT abdomen, "adrenal glands unremarkable" resolves to the generic adrenal gland, not to a left one. "No hydronephrosis" with no side stated resolves to the generic kidney. - **A sided exam sides the finding.** A left shoulder radiograph plus "humerus fracture" resolves to the left humerus. @@ -120,7 +120,7 @@ Curation says which records exist. Assignment says which one a given observation The operational test for separable against inseparable: if you could meaningfully say "the left one is larger or newer," it is separable; if it is a named diffuse entity, it is inseparable.[^ipl-rules] -The full ladder, including the specificity and exam-region rules that surround these laterality rules, is in [anatomic location assignment rules](/data-structures/anatomic-location-assignment-rules.md). +See [anatomic location assignment rules](/data-structures/anatomic-location-assignment-rules.md) for the full precedence, specificity, and exam-region rules. # Known limitation diff --git a/knowledge/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md b/archive/knowledge/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md similarity index 79% rename from knowledge/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md rename to archive/knowledge/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md index e668c93..1963fce 100644 --- a/knowledge/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md +++ b/archive/knowledge/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md @@ -1,10 +1,10 @@ --- type: Concept title: Lineage and current implementation -description: The two anatomic location lineages, the original curated set with its wrapper libraries and the anatomic-locations package inside findingmodel, which one is current, and what remains unreconciled between them. +description: The original and current anatomic location datasets, their consumers, and unresolved differences. tags: [semantic-foundation, anatomic-locations, lineage, status] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: al-site resource: https://github.com/talkasab/anatomiclocations.org/blob/1f39fa45f621cef947a3f3ef1f869334cfa5c841/docs/index.markdown @@ -31,7 +31,7 @@ sources: resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/tasks/anatomic-locations-graph-research.md title: Graph representation in DuckDB research note, findingmodel main - id: build-plan - resource: https://github.com/openimagingdata/oidm-knowledge/blob/main/knowledge/plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, source map and work edge - id: cde-axis resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/docs/next-gen-schema/11-anatomy-axis.md @@ -40,7 +40,7 @@ sources: # Two lineages -[Anatomic locations](/glossary/anatomic-location.md) exist twice in OIDM. The first lineage is the original curated set published at anatomiclocations.org with two wrapper libraries. The second is the `anatomic-locations` package inside the `findingmodel` repository, with its own data file. They descend from the same curation effort and hold nearly the same concepts, but they are separate artifacts with different field names, different record counts, and no synchronization between them. +OIDM has two [anatomic location](/glossary/anatomic-location.md) datasets from the same curation effort. The original, published at anatomiclocations.org, has two wrapper libraries. The current `anatomic-locations` package in `findingmodel` uses a separate file. They contain nearly the same concepts, with different field names and counts, and no synchronization. | | Original lineage | Current lineage | |---|---|---| @@ -59,7 +59,7 @@ Three repositories under the project lead's personal account, all under the ISC `BodyPartIndex.ts` is the TypeScript wrapper, published to npm as `@talkasab/body_part_index` with the data bundled in the package. It is the richer of the two wrappers, documenting local-code mapping and every hierarchy accessor.[^bpi-ts] Its last commit on `main` is 2022-12-18. -`BodyPartIndex.py` is the Python wrapper. Its README says installation from PyPI is pending, and the site roadmap still lists "finish BodyPartIndex.py and publish to PyPI" as an open item.[^al-site] Its last commit on `main`, 2024-02-03, added a TODO list that remains the clearest statement of what that lineage intended next: move the repository to the `openimagingdata` organization, adopt ACR Common codes, acknowledge DICOM codes, track URLs, rewrite `BodyPart` as a Pydantic model, and on the website build a better tree browser and a way for people to submit suggestions about a specific node.[^bpi-todo] None of those items is done in that repository. Several were done independently in the other lineage. +`BodyPartIndex.py` is the Python wrapper. Its README says installation from PyPI is pending, and the site lists "finish BodyPartIndex.py and publish to PyPI" as open.[^al-site] Its last `main` commit, 2024-02-03, added these TODOs: move to `openimagingdata`, adopt ACR Common codes, acknowledge DICOM codes, track URLs, and rewrite `BodyPart` in Pydantic. It also requested a better website tree browser and suggestions for specific nodes.[^bpi-todo] None is complete in that repository. The current lineage independently completed several. # The current lineage @@ -74,7 +74,7 @@ The JSON source data is hosted outside the repository, with the migration tool r # Which is current -The build plan states it plainly: the `anatomic-locations` package inside `findingmodel` and its 2,926-record dataset are the current anatomic data, and the original set with its wrapper libraries is lineage.[^build-plan] Everything built since uses the package. The extraction and coding platform resolves locations through it, the finding model tooling attaches its identifiers to definitions, and the next-generation vocabulary work points at its data file pinned to a commit rather than at the older one.[^cde-axis] +The build plan identifies `anatomic-locations` and its 2,926-record dataset as current.[^build-plan] The extraction platform resolves locations through it, finding model tools attach its identifiers, and the next-generation vocabulary work cites its data at a pinned commit.[^cde-axis] The original lineage is not dead content. Its data file is still the download the public site offers, its npm package still installs, and both are still reachable at the addresses published in 2022. It is superseded as the working set, not withdrawn. diff --git a/knowledge/semantic-foundation/anatomic-locations/radlex-integration.md b/archive/knowledge/semantic-foundation/anatomic-locations/radlex-integration.md similarity index 61% rename from knowledge/semantic-foundation/anatomic-locations/radlex-integration.md rename to archive/knowledge/semantic-foundation/anatomic-locations/radlex-integration.md index a130a49..bc6b6ed 100644 --- a/knowledge/semantic-foundation/anatomic-locations/radlex-integration.md +++ b/archive/knowledge/semantic-foundation/anatomic-locations/radlex-integration.md @@ -1,10 +1,10 @@ --- type: Concept title: RadLex integration -description: What RadLex is, how the curated anatomic location set relates to it as an overlay, the open RSNA tracking issue for folding the set into RadLex, and the running list of node requests. +description: RadLex integration, the anatomy overlay agreement, search limitations, and pending node requests. tags: [semantic-foundation, anatomic-locations, radlex, rsna, integration] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: radlex-issue-3 resource: https://github.com/RSNA/RadLex/issues/3 @@ -39,23 +39,23 @@ Three features of its structure matter to OIDM: - **Provenance is carried on the concept.** A `RID:Source` annotation records free text such as "Playbook", "LOINC", or "LI-RADS 2020", saying where a concept came from. - **Obsolescence is explicit.** `RID:Replaced_by` points a retired concept at its successor. -The `RSNA/RadLex` repository is not a copy of the ontology alone. Its working branches hold a concept search and curation pipeline that parses the OWL with `rdflib`, writes Parquet triples, builds a DuckDB graph of nodes, edges, and search documents, and drives an agent harness that matches report findings to RadLex concepts and proposes candidate new concepts for review.[^radlex-repo] +The `RSNA/RadLex` working branches contain a search and curation pipeline. It parses OWL with `rdflib`, writes Parquet triples, and builds a DuckDB graph of nodes, edges, and search documents. An agent harness matches report findings to concepts and proposes new concepts for review.[^radlex-repo] # Why search quality is the gating problem -The pipeline's search plan is worth reading because it names the failure mode that any integration has to avoid. Concept lookup failed "silently": a term that should match returned nothing or returned noise, and the consuming agent concluded the concept was missing from RadLex. That false-gap outcome is called the worst failure mode of the project, because it flows straight into proposing a concept and pollutes the candidate file with duplicates of concepts RadLex already has.[^radlex-search] +The search plan describes lookup failing "silently": known terms returned no matches or noise. Agents then proposed duplicates as new concepts, the plan's worst failure mode.[^radlex-search] -Two defects produced it. The graph query ranked alphabetically rather than by relevance, so searching `LUL` returned 69 rows with the correct concept, `RID1327` upper lobe of left lung, sorting 67th behind concepts that merely contain the letters. A replacement scorer fixed ranking but matched literal words only, so `part-solid nodule`, `hilar adenopathy`, and `enlarged lymph nodes` all missed.[^radlex-search] +Alphabetical ranking put `RID1327`, upper lobe of left lung, 67th among 69 results for `LUL`. A replacement scorer fixed ranking but matched only literal words, missing `part-solid nodule`, `hilar adenopathy`, and `enlarged lymph nodes`.[^radlex-search] -The design keeps exact matching as the confidence signal and adds BM25 underneath as a recall layer, with a per-query relative cutoff because BM25 scores are not comparable across queries. The stated goal is "recall good enough that a MISS is real evidence of an ontology gap rather than evidence of unlucky phrasing." The plan is marked complete, shipped 2026-08-20.[^radlex-search] For OIDM this matters because a gap list handed to RadLex is only as trustworthy as the search that produced it. +The design combines exact matching for confidence with BM25 for recall. A per-query relative cutoff accounts for scores being incomparable across queries. Its goal is "recall good enough that a MISS is real evidence of an ontology gap rather than evidence of unlucky phrasing." The plan is marked complete and shipped 2026-08-20.[^radlex-search] # The tracking point `RSNA/RadLex` issue 3, "Add Anatomic Locations", was opened on 2026-08-10 by RSNA and is open. Its exit criterion is one line: "100% coverage of the Anatomic Locations terminology in RadLex."[^radlex-issue-3] -That issue is the whole of the integration as recorded in the RadLex repository. No code, commit message, or document inside the repository mentions anatomic locations, body parts, or OIDM. The relationship exists at the issue level, not yet in the ontology file. +The issue is the only integration record in the `RSNA/RadLex` repository. No code, commit message, or repository document mentions anatomic locations, body parts, or OIDM. The integration is not in the ontology file. -The January 2026 status update states the direction more simply, saying the anatomic locations are "being formally incorporated into RadLex by RSNA committee."[^deck] The issue is the evidence for that statement; the ontology content is not there yet. +The January 2026 update says the locations are "being formally incorporated into RadLex by RSNA committee."[^deck] The issue supports this direction, but the ontology content is pending. # The overlay agreement @@ -70,13 +70,13 @@ Two consequences are already written into that vocabulary: # What RadLex can supply in return -The traffic is not one way. The curated set has no is-a relation and no structure-type nodes, which is recorded as its major shortcoming. RadLex 4.3 has an is-a chain for every one of the 1,910 plain-identifier locations, up to "anatomical entity," and walking it yields a first structure-type layer without inventing anything: 352 locations under muscle organ, 221 under artery, 137 under vein, 115 under tendon, 101 under bone organ, 28 each under joint and nerve, and 26 under lymph node.[^cde-axis] +The curated set has no is-a relation and no structure-type nodes, which is recorded as its major shortcoming. All 1,910 plain-identifier locations have a RadLex 4.3 is-a chain to "anatomical entity," yielding 352 locations under muscle organ, 221 under artery, 137 under vein, 115 under tendon, 101 under bone organ, 28 each under joint and nerve, and 26 under lymph node, without inventing anything.[^cde-axis] Two cautions are recorded with that finding. RadLex's own release note warns that some of its is-a categorizations were converted from part-of and may be wrong, so a derived layer needs a clinical skim. And the FMA-style chain, in which a lung is a "lobular organ," is not the clinically useful layer. The work item is a derived, pinned overlay file handed to the anatomic locations track.[^cde-axis] # The node request list -The gap log on the same branch keeps a running list of specific "we need this node" changes to carry to the RadLex anatomy track, kept deliberately separate from the wider design discussion so it can move independently.[^cde-gaps] +The branch's gap log tracks "we need this node" requests separately from broader design work.[^cde-gaps] | Request | Needed by | Raised | |---|---|---| @@ -87,7 +87,7 @@ The gap log on the same branch keeps a running list of specific "we need this no | Real RadLex identifiers for the compound sided variants | every sided [Observation](/glossary/observation.md) | 2026-09-13 | | An is-a relation and structure-type nodes | scope of the form "applies to tendons" | 2026-08-19, restated 2026-09-13 as the top priority | -The log also records closures and oddities, which is what makes it useful as a status document rather than a wish list. `pleural space` was found present as `RID1363` with both sided forms and is marked closed. External-code coverage is uneven, with SNOMED CT on 1,782 of 2,926 records and 608 records carrying no codes at all. And `lung` is contained by `pleural space` in the data, which is anatomically odd and which any check that walks containment from a lung location will pass through.[^cde-gaps] +The `pleural space` request is closed because `RID1363` and both sided forms exist. SNOMED CT covers 1,782 of 2,926 records, while 608 have no external codes. The data also places `lung` inside `pleural space`, an anatomic oddity that affects containment traversal.[^cde-gaps] Goals for this area, and who has stated them, are collected in [the anatomic locations and RadLex roadmap](/roadmap/anatomic-locations-and-radlex.md). RadLex's place among the terminologies OIDM uses is described in [ontologies used](/semantic-foundation/terminologies/ontologies-used.md). diff --git a/knowledge/semantic-foundation/anatomic-locations/tooling.md b/archive/knowledge/semantic-foundation/anatomic-locations/tooling.md similarity index 79% rename from knowledge/semantic-foundation/anatomic-locations/tooling.md rename to archive/knowledge/semantic-foundation/anatomic-locations/tooling.md index e3f7f13..4563911 100644 --- a/knowledge/semantic-foundation/anatomic-locations/tooling.md +++ b/archive/knowledge/semantic-foundation/anatomic-locations/tooling.md @@ -1,10 +1,10 @@ --- type: Guide title: Anatomic location tooling -description: How to look up, search, and traverse anatomic locations with the anatomic-locations package and CLI, the older wrapper libraries, and the raw JSON download. +description: Lookup, search, and hierarchy traversal through current and older anatomic location tools. tags: [semantic-foundation, anatomic-locations, tooling, cli, guide] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: fm-docs @@ -45,7 +45,7 @@ sources: | The raw data, no dependency | The JSON download from the project site | | To curate the set itself | The `manage-anatomic-locations` skill in `findingmodel`, and [laterality conventions](/semantic-foundation/anatomic-locations/laterality-conventions.md) | -Installation and configuration detail belongs with the code and is not repeated here. The package README and the anatomic locations guide in `findingmodel` are the reference.[^fm-pkg-readme][^fm-docs] +See the package README and guide for installation and configuration.[^fm-pkg-readme][^fm-docs] # The anatomic-locations package @@ -62,9 +62,9 @@ print(location.description) # "kidney" print(location.region.value) # "Abdomen" ``` -Four capabilities matter in practice. +The package provides four capabilities. -**Direct lookup and code lookup.** `get()` for the three forms above, and `find_by_code(system, code)` to come in from [SNOMED CT](/glossary/snomed-ct.md), [FMA](/glossary/fma.md), or RadLex. The assignment rules for the extraction platform tell coders to prefer direct lookup over search for a named target organ, because search is less reliable and produces retrieval misses such as "prostate" returning salivary glands.[^ipl-rules] +**Direct lookup and code lookup.** `get()` accepts identifiers, descriptions, or synonyms. `find_by_code(system, code)` accepts [SNOMED CT](/glossary/snomed-ct.md), [FMA](/glossary/fma.md), or RadLex codes. Assignment rules prefer direct lookup for a named organ to avoid search misses such as "prostate" returning salivary glands.[^ipl-rules] **Hierarchy traversal.** `get_containment_ancestors()` and `get_containment_descendants()` on a location, plus `get_children_of()` on the index. Both the containment and the part-of hierarchies are backed by materialized paths, so these are not recursive queries. @@ -107,7 +107,7 @@ whole body - RID39569 # Where the database comes from -The DuckDB file is built separately from the JSON source, published to remote storage, and referenced from a central `manifest.json` that also carries the finding model index. On first use the package fetches it, checks the manifest for a newer version on later runs, and caches it locally.[^fm-normalized] The path can be overridden with the `ANATOMIC_DB_PATH` environment variable or a `db_path` constructor argument, which is how a pinned build or a locally rebuilt database is used.[^fm-pkg-readme] +The JSON source is built into a DuckDB file published to remote storage. A central `manifest.json` references it alongside the finding model index. The package downloads and caches the database on first use, then checks the manifest for updates.[^fm-normalized] Use `ANATOMIC_DB_PATH` or the `db_path` constructor argument for a pinned or locally rebuilt database.[^fm-pkg-readme] # What is not exposed @@ -129,13 +129,13 @@ bodyPart?.getPartOf(); // RID270, female genital system const left = bodyPart?.getLeft(); // RID294_RID5824 ``` -It carries the fullest hierarchy surface of any of the libraries: `isContained` and `isPartOf` tests, immediate and full children in both hierarchies, and full ancestors in both.[^bpi-ts] +The TypeScript library provides `isContained`, `isPartOf`, immediate and full children, and full ancestors for both hierarchies.[^bpi-ts] `BodyPartIndex.py` is the Python counterpart. Its README states that installation from PyPI is pending, so it is used from source or with a local data file. It offers `get()` by identifier or code, `get_by_code()`, `search()` over names and synonyms, `is_contained()`, the laterality triad through `left`, `right`, and `unsided`, and a `snomed_code` property that falls back to the unsided version and then the immediate parent when the sided record carries no code of its own.[^bpi-py] # The raw data -The project site publishes the curated set as a single JSON file with its JSON Schema and a changelog beside it, under the ISC license.[^al-code] That is the zero-dependency path, and the field meanings are in [the data model](/semantic-foundation/anatomic-locations/data-model.md) and, verbatim, in [the record format reference](/references/anatomic-location-json-schema.md). +The project site publishes a JSON file, JSON Schema, and changelog under the ISC license.[^al-code] This requires no library dependency. See [the data model](/semantic-foundation/anatomic-locations/data-model.md) and [the verbatim record format reference](/references/anatomic-location-json-schema.md). [^fm-docs]: Anatomic locations guide, findingmodel main [^fm-pkg-readme]: anatomic-locations package README diff --git a/knowledge/semantic-foundation/anatomic-locations/why-anatomic-locations.md b/archive/knowledge/semantic-foundation/anatomic-locations/why-anatomic-locations.md similarity index 50% rename from knowledge/semantic-foundation/anatomic-locations/why-anatomic-locations.md rename to archive/knowledge/semantic-foundation/anatomic-locations/why-anatomic-locations.md index 8369c4e..41f0e2e 100644 --- a/knowledge/semantic-foundation/anatomic-locations/why-anatomic-locations.md +++ b/archive/knowledge/semantic-foundation/anatomic-locations/why-anatomic-locations.md @@ -1,10 +1,10 @@ --- type: Concept title: Why anatomic locations -description: Why OIDM curates its own anatomic location set instead of pointing at an existing ontology, and every place those identifiers are used across the model. +description: Why OIDM curates anatomic identifiers and where the model uses them. tags: [semantic-foundation, anatomic-locations, radlex, rationale] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: al-site resource: https://github.com/talkasab/anatomiclocations.org/blob/1f39fa45f621cef947a3f3ef1f869334cfa5c841/docs/index.markdown @@ -28,9 +28,9 @@ sources: # The problem the set solves -The premise is stated in one sentence on the project site: "Standard identifiers for discrete anatomic locations would enable numerous levels of interoperability if applied broadly."[^al-site] Two systems that agree a lesion sits at `RID2772` agree on the kidney without sharing a database, a vendor, or a schema. +The project site states: "Standard identifiers for discrete anatomic locations would enable numerous levels of interoperability if applied broadly."[^al-site] Systems using `RID2772` agree on the kidney without sharing a database, vendor, or schema. -The obvious way to get such identifiers is to adopt an existing anatomy ontology whole. The site says why that was rejected. Attempts to use [RadLex](/glossary/radlex.md) or [SNOMED CT](/glossary/snomed-ct.md) directly run into three problems:[^al-site] +The site identifies three problems with adopting [RadLex](/glossary/radlex.md) or [SNOMED CT](/glossary/snomed-ct.md) directly:[^al-site] - **Lack of numerous desired terms.** Structures radiologists name every day are missing. - **Too many unnecessary or degenerate terms.** Several concepts denote the same structure, so a coder has no principled way to choose one and a consumer cannot tell two records mean the same place. @@ -38,15 +38,15 @@ The obvious way to get such identifiers is to adopt an existing anatomy ontology # Curation as the answer -The response is a curated subset rather than a new ontology: "We are curating a subset of anatomic concepts from existing ontologies and shaping them into a usable collection of anatomic identifiers for informatics interoperability."[^al-site] The method was to start from RadLex terms already recognized in radiology reports, keep the used and useful ones, place each in an anatomic hierarchy, attach cross-references to other ontologies, and record [laterality](/glossary/laterality.md) and sex phenotype.[^al-site] +"We are curating a subset of anatomic concepts from existing ontologies and shaping them into a usable collection of anatomic identifiers for informatics interoperability."[^al-site] The project selected useful RadLex terms from radiology reports, organized them in an anatomic hierarchy, and added ontology cross-references, [laterality](/glossary/laterality.md), and sex phenotype.[^al-site] -Two properties are claimed for the result and are the point of the exercise. It is complete enough, containing "almost all clinically used anatomic terms." It is non-degenerate, with "no uncertainty as to which node represents a structure."[^al-site] Non-degeneracy is what makes the identifier usable as a join key. See [anatomic location](/glossary/anatomic-location.md) for the term itself and [the data model](/semantic-foundation/anatomic-locations/data-model.md) for the record shape. +The site claims coverage of "almost all clinically used anatomic terms." It also claims "no uncertainty as to which node represents a structure."[^al-site] Unambiguous identifiers can serve as join keys. See [anatomic location](/glossary/anatomic-location.md) and [the data model](/semantic-foundation/anatomic-locations/data-model.md). # Separable identity -Anatomy is one of the two axes of an [Observation](/glossary/observation.md), and the model keeps it separate from the other. The January 2026 status update states the atomic unit as "what plus where plus attributes: finding tag plus anatomic location plus lesion characteristics," and says anatomy is "baked into OIFM definitions and Observation objects."[^deck] The set is "curated to identify where findings are visualized on imaging exams," which is a narrower target than anatomy in general and explains why structures with no imaging appearance are absent.[^deck] +An [Observation](/glossary/observation.md) separates the finding from its location. The January 2026 update describes "what plus where plus attributes: finding tag plus anatomic location plus lesion characteristics," with anatomy "baked into OIFM definitions and Observation objects."[^deck] The set is "curated to identify where findings are visualized on imaging exams," excluding structures with no imaging appearance.[^deck] -Separating what from where is what lets a single [finding model](/glossary/finding-model.md) for a cyst serve the kidney, the liver, and the breast, and what lets the [Imaging Problem List](/glossary/imaging-problem-list.md) offer "precision filtering via anatomy-embedded definitions."[^deck] +A single [finding model](/glossary/finding-model.md) for a cyst can therefore apply to the kidney, liver, or breast. The [Imaging Problem List](/glossary/imaging-problem-list.md) uses this separation for "precision filtering via anatomy-embedded definitions."[^deck] # Where the identifiers are used @@ -58,11 +58,11 @@ Separating what from where is what lets a single [finding model](/glossary/findi | Finding and location [coding](/glossary/coding.md) | The coding pass generates search terms, searches the location index, and has a selector choose from the returned candidates, with assignment governed by a stated precedence ladder[^ipl-rules] | | Imaging Problem List viewer | Region and organ-cluster filters, and a body schematic whose zones are driven by the location's region and laterality[^viewer-plan] | -The rule that keeps this honest is stated with the assignment rules: every assigned identifier must exist in the set, and a structure absent from it is left unassigned rather than forced to a wrong code.[^ipl-rules] That discipline turns missing terms into a visible list of requests rather than silent mis-coding. See [RadLex integration](/semantic-foundation/anatomic-locations/radlex-integration.md) for where those requests go. +Every assigned identifier must exist in the set. Missing structures stay unassigned to avoid incorrect codes.[^ipl-rules] See [RadLex integration](/semantic-foundation/anatomic-locations/radlex-integration.md) for term requests. # What curation costs -A curated set is a maintained artifact, not a free one. The site's own roadmap names the standing content work: review the hierarchy, add, prune, and modify; edit and improve synonyms; complete SNOMED identification.[^al-site] It also names the companion effort that has not been built, "a companion for exam types based on LOINC/RadLex Playbook exam definitions that specify all included body parts for the exam," which remains the earliest written statement of the [exam type](/glossary/exam-type.md) goal.[^al-site] See [exam types](/semantic-foundation/exam-types/overview.md). +The site's roadmap calls for reviewing, adding, pruning, and modifying hierarchy nodes, improving synonyms, and completing SNOMED identification.[^al-site] It also proposes "a companion for exam types based on LOINC/RadLex Playbook exam definitions that specify all included body parts for the exam," the earliest written [exam type](/glossary/exam-type.md) goal.[^al-site] It remains unbuilt. See [exam types](/semantic-foundation/exam-types/overview.md). [^al-site]: anatomiclocations.org site homepage, rationale, approach, and roadmap [^deck]: Open Imaging Data Model 2026 Status Update, January 2026 diff --git a/knowledge/semantic-foundation/common-data-elements/cde-staging.md b/archive/knowledge/semantic-foundation/common-data-elements/cde-staging.md similarity index 72% rename from knowledge/semantic-foundation/common-data-elements/cde-staging.md rename to archive/knowledge/semantic-foundation/common-data-elements/cde-staging.md index 6c6e2d8..f59abeb 100644 --- a/knowledge/semantic-foundation/common-data-elements/cde-staging.md +++ b/archive/knowledge/semantic-foundation/common-data-elements/cde-staging.md @@ -1,10 +1,10 @@ --- type: Project Profile title: CDE staging -description: The CDEStaging repository, where informally authored candidate common data element definitions collect before the formal review pipeline, and how its content feeds finding models. +description: Candidate CDE definitions, report coverage gaps, and their conversion into finding models. tags: [semantic-foundation, cde, radelement, repositories, content] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T18:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: readme @@ -50,7 +50,7 @@ sources: # What it is -`CDEStaging` is where candidate [common data element](/glossary/cde.md) definitions collect before anyone tries to make them official. Its README states the purpose in one line: "a staging area for definitions of radiology common data elements (CDEs) in JSON format ... prior to their entering the review pipeline."[^readme] The banner above that line gives the concrete goal: "A multi-vendor project to generate a near-complete set of CDE Sets to represent the findings in chest CT reports."[^readme] +`CDEStaging` collects candidate [common data element](/glossary/cde.md) definitions before formal review. Its README calls it "a staging area for definitions of radiology common data elements (CDEs) in JSON format ... prior to their entering the review pipeline."[^readme] The stated goal is "A multi-vendor project to generate a near-complete set of CDE Sets to represent the findings in chest CT reports."[^readme] Contributions are organized by contributor. The README says provisional definitions go in the `definitions` directory "with separate folders for participating vendors."[^readme] Three folders are named after participating vendors, five carry a contributor prefix combined with a modality and body region (CT chest, CT abdomen and pelvis, chest radiograph, knee MRI, and O-RADS), and one holds location value lists. @@ -69,11 +69,11 @@ The `definitions/` tree holds 463 files at that commit. The largest single collection is the CT chest folder, with 206 markdown files and 103 JSON files. -The JSON files are worth a note, because the README's framing suggests otherwise. They do not follow the ACR and RSNA `cde.schema.json`. They carry a finding name, a description, and a list of attributes typed as `choice` or `numeric` with named values, which is the shape a [finding model](/glossary/finding-model.md) uses rather than the shape a [CDE set](/glossary/cde-set.md) uses. Everything in this repository is a pre-submission draft in whatever form its author found convenient. +The JSON files use a name, description, and `choice` or `numeric` attributes with named values. This resembles a [finding model](/glossary/finding-model.md), rather than a [CDE set](/glossary/cde-set.md) conforming to `cde.schema.json`. All content is a pre-submission draft in its author's chosen format. # Two authoring conventions -The markdown definitions were written under two conventions that coexist and were never reconciled. +The markdown definitions use two unreconciled conventions. **Coded `.cde.md` files.** Fourteen files in one vendor folder follow a structured convention: a `# Finding:` heading, a `source` and a `coding` bullet carrying a [RadLex identifier](/glossary/radlex-id.md), then one `##` section per [attribute](/glossary/attribute.md), with coded values, numeric attributes declared as `float` with a unit, and multi-select attributes marked in the heading.[^cardiomegaly] @@ -114,15 +114,15 @@ The markdown definitions were written under two conventions that coexist and wer - **Course**: Retroesophageal / Pretracheal / Other ``` -Some of these folders hold only bare finding lists with no attributes at all, one line per finding name, which is how the chest radiograph and knee MRI folders are organized. +The chest radiograph and knee MRI folders contain bare finding lists, one name per line, without attributes. -`docs/Authoring.md` was meant to reconcile them. It is two sentences long, points at the upstream schema, and says "the authoring guide will be hosted here as well."[^authoring] It was never written. +`docs/Authoring.md` is a two-sentence stub pointing to the upstream schema. It says "the authoring guide will be hosted here as well."[^authoring] The guide was never written. # The report representation work -A second body of work in the repository asks a different question: given real report text, what does the existing definition set fail to capture? Four documents record the answers. +Four documents record what the definitions fail to capture from report text. -**The extraction process.** `docs/report_extraction_process.md` works one chest CT report end to end, turning its prose into "a list of mini-observations" of the form finding, attribute, value.[^extraction-process] It closes with two notes to self: finding, attribute, and value names need to match the project's own finding definitions, and blanket negative statements such as "Lungs are clear" need separate cataloguing. Both notes became the next two documents. +**The extraction process.** `docs/report_extraction_process.md` converts a chest CT report into "a list of mini-observations" containing a finding, attribute, and value.[^extraction-process] Its closing notes call for matching names to the definitions and cataloguing blanket negatives such as "Lungs are clear". The next two documents address those tasks. **Composite negative statements.** A corpus of roughly 830 words of real negative and composite report sentences, grouped by organ system, collected to work out how absent findings should be modeled.[^negatives] Many are not simple negations. "Absence of intravenous contrast limits sensitivity for detecting solid organ findings" is a statement about the exam, not about a finding, and it recurs throughout the abdomen section. @@ -136,19 +136,19 @@ Alongside them, `report_representation/structured_extractions/` holds 52 worked One vendor folder holds a roadmap table covering chest CT findings across nine organ-system sections: pulmonary, pleura, cardiovascular, lymphatic and endocrine, musculoskeletal, abdominal, lines and tubes, devices, and post-operative or treatment changes.[^roadmap] It has 100 finding rows. Each row carries the finding name, a frequency judgment of common, uncommon, or rare, a priority column, a status column holding links to any RadElement sets that already cover the finding, an "in V1" flag, and a RadLex identifier. -At the read commit, 22 rows are flagged for version 1, 18 distinct `RDES` sets are referenced, and 19 distinct RadLex identifiers appear. The table is the clearest single artifact tying informal staged content to the published [RadElement](/glossary/radelement.md) catalog and to [RadLex](/glossary/radlex.md), and it is where the project recorded which findings already had a governed definition and which did not. +At the read commit, 22 rows are flagged for version 1, 18 distinct `RDES` sets are referenced, and 19 distinct RadLex identifiers appear. It records which staged findings have governed definitions in [RadElement](/glossary/radelement.md) and mappings to [RadLex](/glossary/radlex.md). # The path to RadElement -There is no automated pipeline. The README and the authoring stub both point at `RSNA/ACR-RSNA-CDEs` for the canonical `cde.schema.json` and a sample set,[^readme][^authoring] and the roadmap links findings to the RadElement sets that cover them, but nothing in the repository describes a mechanical route from a staged definition to a submitted one. The process is manual curation: draft informally here, harmonize, then submit through the ACR and RSNA review pipeline. See [CDEs and RadElement](/semantic-foundation/common-data-elements/cdes-and-radelement.md). +The README and authoring stub point to `RSNA/ACR-RSNA-CDEs` for `cde.schema.json` and a sample set.[^readme][^authoring] The roadmap links existing RadElement sets, but no automated submission pipeline exists. Authors draft, harmonize, and submit through ACR and RSNA review. See [CDEs and RadElement](/semantic-foundation/common-data-elements/cdes-and-radelement.md). # How it feeds finding models -The staged content is an input to the finding model corpus, and this connection is more developed than the one to RadElement. +Finding model conversion is more developed than RadElement submission. `findingmodels` issue 15, "Bring in CDE Staging MD content," opened 2025-05-28 and still open, asks for a script that takes a markdown file, checks whether a definition already exists for that finding, and either converts it or merges it into the existing definition, reusing element identifiers and synonyms where possible, and adding the appropriate contributor.[^fm-issue-15] -That work exists on the `content/chestcts` branch of `findingmodels`, unmerged at commit `0472a46`. A triage script reads the CT chest folder of `CDEStaging` and compares each source against the finding model search index;[^chestcts-triage] a conversion script then runs the survivors through a multi-agent pipeline of merge, create, and review steps and writes validated finding model JSON.[^chestcts-convert] Both scripts default to the same `CDEStaging` CT chest directory as input. The content direction is covered in [finding model content direction](/roadmap/finding-model-content-direction.md). +That work exists on the `content/chestcts` branch of `findingmodels`, unmerged at commit `0472a46`. A triage script compares CT chest sources with the finding model search index.[^chestcts-triage] A conversion script uses merge, create, and review agents to write validated JSON.[^chestcts-convert] Both default to the `CDEStaging` CT chest directory. See [finding model content direction](/roadmap/finding-model-content-direction.md). # Branch state and open issues diff --git a/knowledge/semantic-foundation/common-data-elements/cdes-and-radelement.md b/archive/knowledge/semantic-foundation/common-data-elements/cdes-and-radelement.md similarity index 84% rename from knowledge/semantic-foundation/common-data-elements/cdes-and-radelement.md rename to archive/knowledge/semantic-foundation/common-data-elements/cdes-and-radelement.md index e980fb2..8339273 100644 --- a/knowledge/semantic-foundation/common-data-elements/cdes-and-radelement.md +++ b/archive/knowledge/semantic-foundation/common-data-elements/cdes-and-radelement.md @@ -1,10 +1,10 @@ --- type: Concept title: CDEs and RadElement -description: What ACR and RSNA common data elements are, where they are published and schematized, where the two published schema descriptions disagree, and how the Open Imaging Data Model uses them today. +description: Governed CDE definitions, RadElement publication, schema differences, and OIDM usage. tags: [semantic-foundation, cde, radelement, schema, fhir] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T18:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: radelement-site resource: https://radelement.org/ @@ -61,16 +61,16 @@ The public API at `api3.rsna.org/radelement/v1/sets` listed 281 sets on 2026-09- The `common_data_elements` repository is the Open Imaging Data Model (OIDM) local snapshot of that registry. Its README describes it as "a periodically updated version of the Common Data Element definitions as stored and maintained in the ACR/RSNA RadElement web site," formatted as JSON for easy consumption.[^cde-readme] At commit `35536d8` it holds 145 set definitions under `definitions/`, one file per set named `RDES###.cde.json`, and the README indexes all 145 alphabetically by clinical topic with their set identifiers. -The snapshot is data, not code. It is used as a reference corpus rather than as a dependency, and it is not regenerated automatically. See [the repository map](/repositories/repository-map.md) for its status. +The snapshot is a reference corpus, not a code dependency. It is not regenerated automatically. See [the repository map](/repositories/repository-map.md). # The schema, and the two places that claim it -Stated plainly: the canonical CDE JSON Schema exists in two repositories, under three filenames, and all three declare the same identifier. +Three CDE JSON Schema files in two repositories declare the same identifier. - `RSNA/ACR-RSNA-CDEs` carries `cde.schema.json` at its repository root. `CDEStaging` names this file as the canonical schema and points authors at it.[^staging-readme] The file is byte-identical on the `master` branch and on the `next-gen-2026` branch, so the next-generation work has not changed it.[^acr-schema][^acr-schema-ng] - `common_data_elements` carries `schema/cde.schema-1.0.json` and `schema/cde.schema-1.1.json`, and its README says the schemas are the ones "maintained at the development repo."[^cde-readme] -The upstream `cde.schema.json` is byte-identical to `cde.schema-1.1.json`, the newer of the two local copies. The definitions in the snapshot conform to version 1.0, which the README states explicitly.[^cde-readme] So the schema published as canonical upstream is a version ahead of the data published beside it. +The upstream `cde.schema.json` is byte-identical to local version 1.1. The snapshot definitions conform to 1.0, one version behind the upstream schema.[^cde-readme] All three files are JSON Schema draft-07 and all three declare the same `$id`: @@ -96,11 +96,11 @@ The JSON Schema versions close part of the gap and leave part of it open. `image # CDE-labeled FHIR Observations -The idea that ties CDEs to the rest of OIDM is the [CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md): one radiology finding expressed as a FHIR [Observation](/glossary/observation.md) whose codes come from a CDE set, its elements, and their values. +A [CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md) expresses one finding as a FHIR [Observation](/glossary/observation.md), coded using a CDE set, its elements, and their values. `FHIRSamples` implements the pattern concretely in a lung cancer screening scenario.[^fhirsamples] An AI-produced finding Observation carries `code.coding` pointing at the `radelement.org` system with the set code `RDES195` for pulmonary nodule; each `component` entry carries an element code such as `RDE1717` with the chosen value code, for example `RDE1717.1`, as `valueCodeableConcept`. A second Observation repeats the same finding with `status` changed from `preliminary` to `final` to mark radiologist confirmation, and a third is `derivedFrom` both the imaging study and the radiologist's finding, carrying a Lung-RADS category (`RDES267`) as a component. -That chain, set code to element code to value code, all through one coding system, is what "CDE-labeled" means in practice. See [FHIR mapping](/data-structures/fhir-mapping.md) for where the current data structures stand against it, and [lineage repositories](/history/lineage-repositories.md) for the full example. +"CDE-labeled" refers to this chain of set, element, and value codes within one coding system. See [FHIR mapping](/data-structures/fhir-mapping.md) for current mappings and [lineage repositories](/history/lineage-repositories.md) for the full example. # How OIDM uses CDEs today @@ -126,7 +126,7 @@ Across the corpus, `RADELEMENT` is the smallest of the index code systems in use **As the standards-track counterpart of finding models.** Finding models are authored quickly and are not balloted; CDEs are governed and are. [Finding models and CDEs](/semantic-foundation/finding-models/finding-models-and-cdes.md) covers the relationship. -One thing worth recording because it is easy to assume otherwise: the current extraction prototypes do not label with RadElement codes. `IPL-MVP-ExtractionAndLabeling` maps extracted findings to a curated list of 15 finding models keyed by OIFM identifier, with no `RDES` or `RDE` code anywhere in the repository,[^ipl-mvp] and the `imaging-problem-list` repository contains no RadElement codes either. The CDE-labeled Observation pattern is implemented in the lineage samples and documented, not in the working extraction pipelines. +Current extraction prototypes use OIFM identifiers. `IPL-MVP-ExtractionAndLabeling` maps findings to 15 curated models and contains no `RDES` or `RDE` codes.[^ipl-mvp] Neither does `imaging-problem-list`. The CDE-labeled pattern remains in documentation and lineage samples. # The next generation diff --git a/archive/knowledge/semantic-foundation/common-data-elements/index.md b/archive/knowledge/semantic-foundation/common-data-elements/index.md new file mode 100644 index 0000000..1c50d39 --- /dev/null +++ b/archive/knowledge/semantic-foundation/common-data-elements/index.md @@ -0,0 +1,5 @@ +# Common data elements + +* [CDEs and RadElement](./cdes-and-radelement.md) - Governed CDE definitions, RadElement publication, schema differences, and OIDM usage. +* [CDE staging](./cde-staging.md) - Candidate CDE definitions, report coverage gaps, and their conversion into finding models. +* [Next-generation CDE vocabulary](./next-generation-vocabulary.md) - What the next-gen-2026 branch of the ACR-RSNA-CDEs repository defines, covering the working vocabulary, the graph shape, the anatomy axis, the display grammar, and the alpha implementation, recorded as in-progress design work in an allied project. diff --git a/knowledge/semantic-foundation/common-data-elements/next-generation-vocabulary.md b/archive/knowledge/semantic-foundation/common-data-elements/next-generation-vocabulary.md similarity index 99% rename from knowledge/semantic-foundation/common-data-elements/next-generation-vocabulary.md rename to archive/knowledge/semantic-foundation/common-data-elements/next-generation-vocabulary.md index 068465b..9920c41 100644 --- a/knowledge/semantic-foundation/common-data-elements/next-generation-vocabulary.md +++ b/archive/knowledge/semantic-foundation/common-data-elements/next-generation-vocabulary.md @@ -40,7 +40,7 @@ sources: resource: https://github.com/RSNA/ACR-RSNA-CDEs/blob/44836c19f4e025cf1684a015ed5cc63c29eaf7f3/notes/oifm-schema-v2-draft.md title: FindingModel Source Schema v2 Draft, a copy of the upstream gist - id: plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, which places these ideas in the roadmap for finding models and the Imaging Problem List --- diff --git a/knowledge/semantic-foundation/exam-types/existing-building-blocks.md b/archive/knowledge/semantic-foundation/exam-types/existing-building-blocks.md similarity index 100% rename from knowledge/semantic-foundation/exam-types/existing-building-blocks.md rename to archive/knowledge/semantic-foundation/exam-types/existing-building-blocks.md diff --git a/knowledge/semantic-foundation/exam-types/index.md b/archive/knowledge/semantic-foundation/exam-types/index.md similarity index 100% rename from knowledge/semantic-foundation/exam-types/index.md rename to archive/knowledge/semantic-foundation/exam-types/index.md diff --git a/knowledge/semantic-foundation/exam-types/overview.md b/archive/knowledge/semantic-foundation/exam-types/overview.md similarity index 92% rename from knowledge/semantic-foundation/exam-types/overview.md rename to archive/knowledge/semantic-foundation/exam-types/overview.md index 9e57c87..b40e971 100644 --- a/knowledge/semantic-foundation/exam-types/overview.md +++ b/archive/knowledge/semantic-foundation/exam-types/overview.md @@ -46,7 +46,7 @@ This document states the goals as goals and the facts as facts. It proposes noth # The goal as the project lead states it -In the 2026-09-20 planning interview that produced [the knowledgebase build plan](/plans/2026-09-20-knowledgebase-build-plan.md), the project lead described the intended exam type work in three parts.[^plan] +In the 2026-09-20 planning interview that produced [the knowledgebase build plan](/references/project-direction.md), the project lead described the intended exam type work in three parts.[^plan] 1. **Wrapper tooling around an existing knowledge graph.** The graph is the one represented by the [LOINC/RSNA Radiology Playbook](/glossary/loinc-rsna-radiology-playbook.md) together with [RadLex](/glossary/radlex.md), rather than a new vocabulary built from scratch. 2. **A set of preferred high-level entries.** Names at the granularity clinicians and systems actually use, such as CT Chest, MRI Brain, and X-ray Knee, marked as the preferred entry among the many Playbook codes that describe variants of the same study. @@ -74,7 +74,7 @@ Two consequences matter for anyone reading exam type material in these repositor Three uses exist now. All three run on a bare LOINC code with no exam type object behind it. -**As the exam header of an [Exam Finding List](/glossary/exam-finding-list.md).** The specification requires that the structure "must also have basic information (keyed by a curated list of LOINC codes) about what exam this is."[^ipl-readme] In the data this is `examInfo.studyLoincCode` beside `studyIdentifier`, `studyDateTime`, and a free-text `studyDescription`; the domain notes give `72133-2` for CT Abdomen and Pelvis Without Contrast as the worked example.[^ipl-claude] The [Imaging Problem List](/glossary/imaging-problem-list.md) carries the same pair forward on every observation as `exam_type_code` and `exam_type_display`, which is how a finding's time course is labelled by study. The curated list of LOINC codes that the specification refers to has never been published. +**As the exam header of an [Exam Finding List](/glossary/exam-finding-list.md).** The imaging-problem-list README requires that the structure "must also have basic information (keyed by a curated list of LOINC codes) about what exam this is."[^ipl-readme] In the data this is `examInfo.studyLoincCode` beside `studyIdentifier`, `studyDateTime`, and a free-text `studyDescription`; the domain notes give `72133-2` for CT Abdomen and Pelvis Without Contrast as the worked example.[^ipl-claude] The [Imaging Problem List](/glossary/imaging-problem-list.md) carries the same pair forward on every observation as `exam_type_code` and `exam_type_display`, which is how a finding's time course is labelled by study. The curated list of LOINC codes that the README refers to has never been published. **As the fallback [body region](/glossary/body-region.md) for a finding with no anatomy.** The anatomic location assignment rules used by the extraction and coding platform put the exam at the bottom of a three-step precedence ladder. Explicit anatomy in the report text or section context wins; otherwise the finding's own target organ; otherwise, for findings with no anatomic noun at all such as "soft tissue mass" or "generalized osteoporosis," the rules "fall back to the exam-scoped coarse region (thorax, abdomen, head, ...), sided only if the exam is sided."[^ipl-rules] The exam also supplies [laterality](/glossary/laterality.md) under a stated source priority of explicit text side, then sided-exam side, then generic, so that a left shoulder radiograph sides its findings while an abdominal CT never introduces a side. The rules carry a small exam-to-region table of RadLex identifiers, reproduced in [existing building blocks](/semantic-foundation/exam-types/existing-building-blocks.md). diff --git a/knowledge/semantic-foundation/finding-models/authoring-workflow.md b/archive/knowledge/semantic-foundation/finding-models/authoring-workflow.md similarity index 75% rename from knowledge/semantic-foundation/finding-models/authoring-workflow.md rename to archive/knowledge/semantic-foundation/finding-models/authoring-workflow.md index b2f1386..6aeaf40 100644 --- a/knowledge/semantic-foundation/finding-models/authoring-workflow.md +++ b/archive/knowledge/semantic-foundation/finding-models/authoring-workflow.md @@ -1,10 +1,10 @@ --- type: Guide title: Authoring workflow -description: How finding models are created and reviewed today, through the Forge web application, the findingmodel-ai command line, the three agent skills, and the validator that gates every commit. +description: Finding model authoring, review, and validation through Forge, the CLI, and repository skills. tags: [semantic-foundation, finding-models, authoring, workflow, guide] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: forge-workflow resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/finding-model-creation-workflow.md @@ -40,7 +40,7 @@ sources: # Two front doors -A [finding model](/glossary/finding-model.md) reaches the corpus through one of two routes. A web application handles one finding at a time with a person driving. A set of agent skills in the content repository handles conversational and bulk authoring with a person reviewing. Both produce the same JSON, and both end at the same validator. +Authors create [finding models](/glossary/finding-model.md) through Forge or repository agent skills. Forge handles one finding with a person driving. Skills support conversational and bulk authoring with human review. Both produce JSON checked by the same validator. # Finding Model Forge @@ -55,7 +55,7 @@ A draft carries the user, the name, the raw inputs, the server-generated JSON, a # The findingmodel-ai command line -The `findingmodel-ai` package is the scriptable half of the same capability, installable from PyPI and configured with an API key for at least one model provider.[^ai-readme] Three commands cover the common path. +Install `findingmodel-ai` from PyPI and configure an API key for at least one provider.[^ai-readme] Its main authoring commands are: ```bash findingmodel-ai make-info "pneumothorax" # name to description and synonyms @@ -67,7 +67,7 @@ The same functions are available as a Python API, and an optional web-search key # Agent skills in the content repository -Three skills live under `.claude/skills/` in `findingmodels` and divide the work by shape of the job. +Three skills in `findingmodels/.claude/skills/` handle authoring and review. | Skill | Use it when | Shape | |---|---|---| @@ -75,7 +75,7 @@ Three skills live under `.claude/skills/` in `findingmodels` and divide the work | `finding-batch` | a list, CSV, or directory of source content | triage the whole batch, then draft each finding in an isolated sub-agent | | `finding-review` | existing definition files named by path, glob, or directory | lint, then review each file in an isolated sub-agent, then human sign-off | -Each skill file is deliberately thin. All rules, command cheatsheets, and procedures live in `prompts/fragments/`, and the skill loads the fragment named at each step rather than carrying the rules itself.[^skill-author] Sixteen fragments cover naming, synonym rules, scope and specificity, presence and change, the associated-versus-component distinction, search and triage, mechanical lint, the quality checklist, review file generation, CSV writeback, and handoff to the review interface. `core_concept.md` is the orientation fragment every skill reads first; it states the two guiding principles as "a finding is a noun phrase; everything else is an attribute" and "findings exist in time."[^core-concept] A companion `defaults.yml` holds the contributor and organization defaults, the permitted source codes for identifier minting, and suggested tag sets per campaign, which the skill confirms with the user at session start. +Skills load rules, command cheatsheets, and procedures from `prompts/fragments/` as needed.[^skill-author] Sixteen fragments cover naming, synonyms, scope, presence, change, associated versus component findings, search, triage, lint, quality review, review files, CSV writeback, and review-interface handoff. Every skill starts with `core_concept.md`, whose principles are "a finding is a noun phrase; everything else is an attribute" and "findings exist in time."[^core-concept] At session start, the skill confirms defaults from `defaults.yml` for contributor, organization, identifier source codes, and campaign tags. Three constraints run through all three skills. @@ -87,9 +87,9 @@ Review runs mechanical lint first, with auto-fix for trivially fixable errors su # The gate every route passes -Whatever produced the JSON, it lands in `defs/` and is committed. A pre-commit hook runs `uv run scripts/validator.py --with-git-adds` on every commit, serially, whether or not a definition was staged.[^precommit] The validator validates every definition against the format, fails on a duplicate finding or attribute identifier, reformats the JSON in place, regenerates the markdown render in `text/`, rewrites the corpus index, rewrites `ids.json`, and stages all of it. See [identifiers](/semantic-foundation/finding-models/identifiers.md) for what the duplicate check protects. +A pre-commit hook runs `uv run scripts/validator.py --with-git-adds` serially on every commit, even without staged definitions.[^precommit] It validates all `defs/` files, rejects duplicate finding or attribute identifiers, reformats JSON, rebuilds `text/`, the corpus index, and `ids.json`, then stages the results. See [identifiers](/semantic-foundation/finding-models/identifiers.md). -The practical consequence is that the generated artifacts cannot drift from the source. It is also why the repository's guidance forbids editing them by hand. +Regeneration keeps derived files consistent with definitions. Repository guidance prohibits editing them by hand. # Requests and cleanup diff --git a/knowledge/semantic-foundation/finding-models/content-catalog.md b/archive/knowledge/semantic-foundation/finding-models/content-catalog.md similarity index 78% rename from knowledge/semantic-foundation/finding-models/content-catalog.md rename to archive/knowledge/semantic-foundation/finding-models/content-catalog.md index afce140..26bc061 100644 --- a/knowledge/semantic-foundation/finding-models/content-catalog.md +++ b/archive/knowledge/semantic-foundation/finding-models/content-catalog.md @@ -1,10 +1,10 @@ --- type: Project Profile title: Finding model content catalog -description: "The findingmodels repository: what is in the corpus, where its identifiers and generated renders live, the conventions that govern it, how to browse it, and the content batches in flight on unmerged branches." +description: Published finding model content, repository conventions, browsing tools, and unmerged batches. tags: [semantic-foundation, finding-models, content, repository, project-profile] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: fm-claude @@ -38,7 +38,7 @@ sources: # What the repository is -`findingmodels` is the content repository of the Open Imaging Data Model (OIDM). It holds finding model definitions and nothing that executes them. The format those definitions obey lives in a different repository, `findingmodel`, and is described in [the format document](/semantic-foundation/finding-models/finding-model-format.md). +`findingmodels` holds Open Imaging Data Model (OIDM) definitions. The separate `findingmodel` repository defines their format, described in [the format document](/semantic-foundation/finding-models/finding-model-format.md). Read at commit `4475ac1` on `main`, dated 2026-04-28, the repository contains 2,382 definitions. @@ -65,13 +65,13 @@ Every identifier carries a three-or-four-letter organization segment recording w | `MGB` | MassGeneral Brigham | 47 | | `MSFT` | Microsoft | 31 | -Four in five definitions are Gamuts-derived. That fact shapes everything about the corpus's current state: the [Radiology Gamuts Ontology](/glossary/gamuts.md) is a differential-diagnosis resource rather than a finding vocabulary, so its imported entries carry patterns that do not sit comfortably as findings. The [content direction roadmap](/roadmap/finding-model-content-direction.md) records the stated intent that the [MGB exam-oriented sub-taxonomies](/semantic-foundation/finding-models/finding-taxonomies.md) replace many of them. +Four in five definitions derive from the [Radiology Gamuts Ontology](/glossary/gamuts.md); the Radiology Gamuts Ontology is a differential-diagnosis resource rather than a finding vocabulary, so its imported entries carry patterns that do not sit comfortably as findings. The [content direction roadmap](/roadmap/finding-model-content-direction.md) records the stated intent that the [MGB exam-oriented sub-taxonomies](/semantic-foundation/finding-models/finding-taxonomies.md) replace many of them. Two further organizations, `RSNA` and `ACR`, are registered in the package's seeded organization list but have contributed no definitions under their own codes.[^base-orgs] Individual contributors appear on definitions as `Person` records with a GitHub username, an email address, and an organization code; organizations appear as `Organization` records with a name, a code, and a URL. # Conventions -The repository's own guidance file states the rules that govern changes.[^fm-claude] +The repository guidance sets these rules.[^fm-claude] - Three artifacts are auto-generated and must never be hand-edited: `text/*.md`, `index.md`, and `ids.json`. They are rebuilt from `defs/` by `uv run scripts/validator.py`. - Every finding identifier must be unique, and every attribute identifier must be unique across all files, not merely within one. The validator fails the commit if either check fails. @@ -85,7 +85,7 @@ A separate open plan, the definition cleanup plan, catalogs the conventions the # How to browse -Three routes exist, at different levels of convenience. +Browse through the repository, catalog site, or search tools. - **The repository.** Read `index.md` for the whole corpus as one table, then follow its links into `text/` for a rendered definition or `defs/` for the JSON. - **The catalog site.** `finding-models-site` is a static Astro site that pulls the content repository in as a git submodule and renders `/models/` and `/models/[slug]` pages at build time. It is published at [openimagingdata.github.io/finding-models-site](https://openimagingdata.github.io/finding-models-site/) and profiled in [applications](/applications/finding-models-site.md). @@ -93,7 +93,7 @@ Three routes exist, at different levels of convenience. # Content in flight -Six branches carry unmerged content work. None has an open pull request, and none is on `main`. The figures below are branch-to-`main` differences as of the dates given. +Six content branches remain unmerged with no open pull requests. Counts compare each branch with `main` at the stated dates. | Branch | Tip | New definitions | What it is | |---|---|---:|---| @@ -104,9 +104,9 @@ Six branches carry unmerged content work. None has an open pull request, and non | `findingmodels-metadata` | 2026-06-01 | 0 | Applies an approved enrichment baseline to 78 existing definitions and adds no new ones. | | `taxonomy-export-2026-08-15` | 2026-08-15 | 0 | Replaces `lists/` with the six MGB exam-oriented sub-taxonomy files. | -The chest CT branch is the largest by far and the least complete: its own progress document records 205 source items split into 21 chunks of ten, with chunk 1 done and chunks 2 through 21 pending.[^chestcts] The head CT branch's plan document is marked complete for the soft tissue category, which produced four new models and nine mappings onto existing ones, with identifiers written back to the source list and the validator run clean.[^headcts] +The chest CT progress document lists 205 sources in 21 chunks of ten. Chunk 1 is done, with 2 through 21 pending.[^chestcts] The head CT soft-tissue plan is complete, with four new models and nine mappings to existing models. Identifiers were written back to the source list and validation passed.[^headcts] -Two consequences follow. First, the published corpus of 2,382 understates by roughly 2,400 the number of definitions that have been drafted somewhere. Second, the JSON schema file in `schema/` is being extended independently on three of these branches, with nothing landed on `main`. +Roughly 2,400 definitions have been drafted beyond the 2,382 published models. Three branches also extend `schema/` independently, with no changes merged into `main`. Ten issues are open on the repository. Five request further content batches, including RadElement-derived definitions, chest CT and chest radiograph device lists, abdomen CT findings, knee MRI findings, and CDEStaging markdown content. Four cover tooling, including running the validator as a GitHub action and locking `main`. One asks for a static site with a landing page. diff --git a/knowledge/semantic-foundation/finding-models/enrichment-pipeline.md b/archive/knowledge/semantic-foundation/finding-models/enrichment-pipeline.md similarity index 77% rename from knowledge/semantic-foundation/finding-models/enrichment-pipeline.md rename to archive/knowledge/semantic-foundation/finding-models/enrichment-pipeline.md index aa731a4..c23dc92 100644 --- a/knowledge/semantic-foundation/finding-models/enrichment-pipeline.md +++ b/archive/knowledge/semantic-foundation/finding-models/enrichment-pipeline.md @@ -4,7 +4,7 @@ title: Metadata enrichment pipeline description: How finding model definitions get structured metadata assigned by language models, what shipped, what the canonical rewrite on the work edge changes, and why it has not been released. tags: [semantic-foundation, finding-models, enrichment, metadata, concept] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: prd resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/docs/finding-enrichment-prd.md @@ -46,7 +46,7 @@ sources: # The problem -A [finding model](/glossary/finding-model.md) definition written by a person carries a name, a description, synonyms, and [attributes](/glossary/attribute.md). It usually does not carry ontology codes, anatomic locations, or the classification facts that make a corpus of 2,382 definitions browsable: which body region, which subspecialty reads it, which modalities show it, whether it is a finding or a diagnosis. Assigning those by hand across the corpus is not practical, so the project built a language-model pipeline to propose them. +Authors supply [finding model](/glossary/finding-model.md) names, descriptions, synonyms, and [attributes](/glossary/attribute.md). Ontology codes, locations, body regions, subspecialties, modalities, and finding-versus-diagnosis classification are often missing. A language-model pipeline proposes these metadata across the 2,382-definition corpus. Two generations of that pipeline exist. The first shipped and is on `main`. The second is a rewrite on the work edge that has not been released. @@ -68,11 +68,11 @@ The goal is stated as making structured metadata "canonical `FindingModel` state ## Seven agents, not one -Where the optimization proposal recommended one unified classifier, the rewrite went the other way. A decision record on the branch states that assignment is performed by seven focused agents, each with a lean external prompt and each emitting a typed decision: entity type, etiology and tempo, patient applicability, subspecialty domain, modality applicability, ontology decision, and anatomy decision. The reason given is that "focused agents allow concise/clean prompts, per-field evaluation, and targeted tuning of weak fields," accepting more model calls per finding as the cost.[^adr2] +Where the optimization proposal recommended one unified classifier, the rewrite went the other way. The rewrite uses seven agents with external prompts and typed outputs: entity type, etiology and tempo, patient applicability, subspecialty domain, modality applicability, ontology decision, and anatomy decision. ADR 0002 states that "focused agents allow concise/clean prompts, per-field evaluation, and targeted tuning of weak fields," at the cost of more calls.[^adr2] -An orchestrator, `assign_metadata()`, gathers candidates and assembles the decisions but "never decides a field value itself."[^agent-arch] It runs in stages: candidate gathering from ontology search and the anatomic index, bounded by a configurable limit defaulting to 15; then the ontology and anatomy agents, which select among those candidates rather than generating values; then entity type, patient applicability, subspecialty, and modality in parallel; then etiology and tempo, which runs last because it depends on entity type and is validated against it. Two search-agent pairs feed candidates and never set final metadata. An auditor is advisory only. +`assign_metadata()` assembles decisions but "never decides a field value itself."[^agent-arch] It gathers ontology and anatomy candidates with a configurable limit defaulting to 15, then asks ontology and anatomy agents to select from them. Entity type, patient applicability, subspecialty, and modality run in parallel. Etiology and tempo run last, depending on and validated against entity type. Two search-agent pairs supply candidates without setting metadata. An auditor provides advice only. -Each agent has its own component evaluation, which is what makes per-field tuning possible. +Each agent has a component evaluation for tuning its fields. ## Releasing without breaking anyone @@ -80,13 +80,13 @@ A third decision record covers distribution. Two DuckDB artifacts are published # Why it has not shipped -The branch measures itself and reports failure. A prompt-improvement document dated 2026-06-03 records raising the expected time course score from 0.69 to between 0.76 and 0.78, and the etiologies score from 0.74 to between 0.91 and 0.93 by scoring errors asymmetrically according to clinical consequence. It then states that overall readiness remains a failure because age profile and index codes are still below the quality floor.[^tempo] +The 2026-06-03 prompt-improvement document reports expected time course scores rising from 0.69 to between 0.76 and 0.78 and etiology from 0.74 to between 0.91 and 0.93. Scoring weighted errors asymmetrically by clinical consequence. Age profile and index codes still failed the quality floor, so overall readiness failed.[^tempo] -The index codes weakness was traced separately. At roughly 0.674 against a floor of 0.85 it was the weakest field, and the cause was missing RadElement coverage in the ontology search. Adding that coverage alone reached about 0.736, which the document calls insufficient; the approach that followed splits searchable source codes from codes carried forward.[^index-codes] +Index codes scored about 0.674 against a 0.85 floor, the weakest field, and the cause was missing RadElement coverage in the ontology search. Adding it raised the score to about 0.736, still insufficient. Subsequent work separates searchable source codes from carried-forward codes.[^index-codes] The active plan on the branch is therefore not a corpus run. Its stated goal is to get the branch and the sibling data repository "to a coherent, reviewable, git-clean state so we can move toward supervised corpus enrichment," and it says so explicitly: "The immediate milestone is not a broad corpus run."[^current-plan] Five ordered commits cover documentation consolidation, review-evidence fixtures and gate validation tests, tool and prompt refactoring, data-repository apply tooling, and the approved baseline. The plan also records "No commits are made without explicit permission." -The human review evidence it protects is specific: 150 unique reviewed records and 180 review events, with a latest effective status of 67 approved and 83 requiring feedback, up from an original 46 approved after a targeted follow-up pass updated 21 records. +Review evidence covers 150 unique records and 180 events. Current effective statuses are 67 approved and 83 requiring feedback. A follow-up pass updated 21 records, increasing approvals from 46 to 67. # The data side diff --git a/knowledge/semantic-foundation/finding-models/finding-model-format.md b/archive/knowledge/semantic-foundation/finding-models/finding-model-format.md similarity index 83% rename from knowledge/semantic-foundation/finding-models/finding-model-format.md rename to archive/knowledge/semantic-foundation/finding-models/finding-model-format.md index d113a6e..a928a4a 100644 --- a/knowledge/semantic-foundation/finding-models/finding-model-format.md +++ b/archive/knowledge/semantic-foundation/finding-models/finding-model-format.md @@ -1,10 +1,10 @@ --- type: Format Specification title: Finding model format -description: The released Open Imaging Finding Model record format, class by class and field by field, with a real example and the unreleased metadata fields on the work edge. +description: Released finding model fields, validation rules, examples, and unreleased metadata extensions. tags: [semantic-foundation, finding-models, oifm, schema, format] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fm-model resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/finding_model.py @@ -84,7 +84,7 @@ An [attribute](/glossary/attribute.md) is one characterization axis. Two variant | `unit` | no | yes | no | string or null | | `index_codes` | yes | yes | no, `Ided` variants only | non-empty if present | -Three behaviors are enforced by validators rather than by field declarations. A choice attribute must carry at least two values. `max_selected` is repaired on input: it becomes 1 when missing or falsy, and is clamped to the number of values when larger or when the literal string `"all"` is supplied. And value codes are generated rather than supplied. +Validators require at least two choice values and generate value codes. They also repair `max_selected`: missing or falsy values become 1. Values above the number of choices, or the literal string `"all"`, become the number of choices. A `ChoiceValueIded` has `value_code` matching `^OIFMA_[A-Z]{3,4}_[0-9]{6}\.\d+$`, a `name`, an optional `description`, and optional `index_codes`. A validator overwrites each `value_code` with `.` counting from zero, so the first value of an attribute ends in `.0`, the second in `.1`, and so on. The prose mirror's example numbers values from `.1`, which does not match what the code writes or what the stored corpus contains. Real presence attributes carry `.0` for absent and `.1` for present. @@ -128,7 +128,7 @@ Contributors are `Person` or `Organization`.[^fm-contributor] An `Organization` } ``` -The `Laterality` attribute is elided here for length. Serialization conventions follow from the repository's validator: files are rewritten with consistent formatting on every commit, null and absent optional fields are omitted rather than written as `null`, and the generated markdown render, the repository index, and the identifier registry are regenerated from `defs/` and must never be hand-edited.[^fm-claude] +The example omits the `Laterality` attribute. On every commit, the validator reformats JSON, omits null and absent optional fields, and regenerates markdown, the corpus index, and the identifier registry from `defs/`. Generated files must not be edited manually.[^fm-claude] # On the work edge, not released @@ -147,9 +147,11 @@ The branch splits `finding_model.py` into `types/models.py`, `types/attributes.p | `age_profile` | `AgeProfile` | `applicability` is `all_ages` or a list of nine MeSH-derived age stages; `more_common_in` is an optional list of the same | | `sex_specificity` | `SexSpecificity` | male-specific, female-specific, sex-neutral | -Three changes ride along. Legacy values normalize on input, so title-cased body regions lowercase themselves, `Arm` and `Leg` become `upper_extremity` and `lower_extremity`, `ALL` becomes `whole_body`, modalities `CR` and `DX` become `XR`, and free-text age labels expand into an `AgeProfile`. Model-level entries in `index_codes` and `anatomic_locations` are rejected if `display` is empty. And canonical `index_codes` are narrowed to exact matches or clinically substitutable near-equivalents, with broader, narrower, and merely related candidates diverted to a separate review artifact. +The branch also normalizes legacy input. Body regions become lowercase, `Arm` and `Leg` become `upper_extremity` and `lower_extremity`, and `ALL` becomes `whole_body`. Modalities `CR` and `DX` become `XR`, and free-text ages expand into `AgeProfile`. -The branch's own readiness assessment does not pass. The prose reference for these fields, and the three places where it disagrees with the branch code, is in [the metadata fields extract](/references/oifm-metadata-fields-extract.md). The pipeline that populates them is described in [the enrichment pipeline](/semantic-foundation/finding-models/enrichment-pipeline.md), and the direction as a stated goal is in [format evolution](/roadmap/finding-model-format-evolution.md). +Model-level `index_codes` and `anatomic_locations` require nonempty `display`. Canonical `index_codes` accept exact or clinically substitutable matches. Broader, narrower, and related candidates go to a separate review artifact. + +The branch fails its readiness assessment. [The metadata fields extract](/references/oifm-metadata-fields-extract.md) records three disagreements between its code and prose. See [the enrichment pipeline](/semantic-foundation/finding-models/enrichment-pipeline.md) for assignment and [format evolution](/roadmap/finding-model-format-evolution.md) for planned work. [^fm-model]: finding_model.py, findingmodel main branch [^fm-contributor]: contributor.py, findingmodel main branch diff --git a/knowledge/semantic-foundation/finding-models/finding-models-and-cdes.md b/archive/knowledge/semantic-foundation/finding-models/finding-models-and-cdes.md similarity index 100% rename from knowledge/semantic-foundation/finding-models/finding-models-and-cdes.md rename to archive/knowledge/semantic-foundation/finding-models/finding-models-and-cdes.md diff --git a/knowledge/semantic-foundation/finding-models/finding-taxonomies.md b/archive/knowledge/semantic-foundation/finding-models/finding-taxonomies.md similarity index 67% rename from knowledge/semantic-foundation/finding-models/finding-taxonomies.md rename to archive/knowledge/semantic-foundation/finding-models/finding-taxonomies.md index 610f9c7..60b4a06 100644 --- a/knowledge/semantic-foundation/finding-models/finding-taxonomies.md +++ b/archive/knowledge/semantic-foundation/finding-models/finding-taxonomies.md @@ -1,10 +1,10 @@ --- type: Reference title: MGB exam-oriented sub-taxonomies -description: The six exam-oriented finding hierarchies contributed by MassGeneral Brigham, their columns and hierarchy semantics, their row and match counts, and the triage they call for. +description: Six MGB exam-oriented finding hierarchies, their fields, counts, model matches, and pending triage. tags: [semantic-foundation, finding-models, taxonomy, content, reference] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: lists-readme resource: https://github.com/openimagingdata/findingmodels/blob/a30c3c95fa3943e7340ce87575f4b1b926987eb8/lists/README.md @@ -19,13 +19,13 @@ sources: resource: https://github.com/openimagingdata/findingmodels/blob/4475ac1bcb591f1a0951b2082b59208def173a5d/ids.json title: ids.json, the identifier registry, findingmodels main branch - id: build-plan - resource: /plans/2026-09-20-knowledgebase-build-plan.md + resource: /references/project-direction.md title: Knowledgebase build plan, recording the project lead's statement of 2026-09-21 --- # What they are -The MGB exam-oriented sub-taxonomies are six hierarchies of radiology finding names, contributed by MassGeneral Brigham and exported into the `findingmodels` repository on 2026-08-15. They live on the branch `taxonomy-export-2026-08-15`, read here at commit `a30c3c9`, which replaces the repository's earlier `lists/` directory wholesale and adds no finding model definitions of its own.[^lists-readme] +MassGeneral Brigham contributed six finding hierarchies, exported to `findingmodels` on 2026-08-15. The `taxonomy-export-2026-08-15` branch, read at `a30c3c9`, replaces `lists/` and adds no model definitions.[^lists-readme] Each file is a comma-separated table. Together they hold 3,789 rows, of which 1,028 already carry the identifier of an existing [finding model](/glossary/finding-model.md). @@ -69,7 +69,7 @@ Two columns sit deliberately outside that tree. `category` groups rows anatomica | `diagnosis` | 932 | | blank | 414 | -That split mirrors the distinction the finding model work elsewhere calls entity type, where a finding is what you see and a diagnosis is what you conclude. Blank rows are mostly the generic parents, which exist to hold a subtree rather than to be reported. +The values correspond to entity type elsewhere in the finding model work. The `finding_type` column's values are `observation` and `diagnosis`, where a finding is what you see and a diagnosis is what you conclude. Blank rows are mostly generic parents that group children without being reported. Two real rows show the shape: @@ -79,23 +79,23 @@ airway_abnormality,airway,,,,,OIFM_OIDM_449436 airspace_opacity,lung,lung_abnormality,"Air space opacity,Infiltrate,Airspace disease",observation,,OIFM_CDE_000225 ``` -The first is a top-level parent with no `finding_type`. The second is a child of `lung_abnormality`, typed as an observation, carrying three synonyms and matched to a definition that entered the corpus from the common data element work. +The first row is an untyped root. The second is an observation under `lung_abnormality`, with three synonyms and a CDE-derived model identifier. # Matching status and the triage task No identifiers were minted during the export. Filled rows were matched by exact name against `ids.json` across all branches, plus identifiers previously written back into the old lists.[^lists-readme] The README records a consequence of those earlier writebacks: "An ID can appear on two rows where those earlier writebacks mapped two findings onto one model." -The match rate varies enormously by file. Head CT is 86 percent matched and chest radiography 76 percent, because both areas have had content batches run against them. Mammography is 3 percent matched and the combined chest, abdomen, and pelvis CT file 10 percent, against 2,064 rows. +Head CT is 86 percent matched and chest radiography 76 percent after content batches in both areas. Mammography is 3 percent matched, and the 2,064-row combined CT file is 10 percent matched. -The README names the next task in one sentence: "Blank rows are the ones needing triage." That is 2,761 rows. Triage means deciding, for each, whether an existing model covers it under a different name, whether it needs a new model, or whether it is not a finding at all. No plan document in the repository yet assigns that work. +The README states: "Blank rows are the ones needing triage." Each of the 2,761 unmatched rows needs classification as an existing model under another name, a new model, or something other than a finding. No plan document in the repository yet assigns that work. # Why this matters -The project lead stated on 2026-09-21 that the sub-taxonomies are the immediate direction for all finding model content, and that they are expected to replace many of the Gamuts-derived models that currently make up 1,933 of the corpus's 2,382 definitions. That statement is recorded in [the knowledgebase build plan](/plans/2026-09-20-knowledgebase-build-plan.md).[^build-plan] +The project lead stated on 2026-09-21 that the sub-taxonomies are the immediate direction for all finding model content, and that they are expected to replace many of the Gamuts-derived models that currently make up 1,933 of the corpus's 2,382 definitions. That statement is recorded in [the knowledgebase build plan](/references/project-direction.md).[^build-plan] -This is a change in how content gets chosen. The Gamuts import brought in whatever the source ontology contained. The sub-taxonomies instead enumerate what a radiologist reading a particular exam type actually reports, organized by the exam type. A finding model corpus built against them would be scoped to assessable anatomy per modality, which is the scoping rule the authoring guidance already states. The stated direction, and what depends on it, is in [the content direction roadmap](/roadmap/finding-model-content-direction.md). +This is a change in how content gets chosen. The Gamuts import brought in whatever the source ontology contained. The sub-taxonomies instead enumerate what a radiologist reading a particular exam type actually reports, organized by the exam type. They would scope the corpus to anatomy assessable by modality, as authoring guidance requires. See [the content direction roadmap](/roadmap/finding-model-content-direction.md). -The glossary entry for the artifact type is [finding taxonomy](/glossary/finding-taxonomy.md). The files themselves are on the branch and are not copied into this knowledgebase. +See [finding taxonomy](/glossary/finding-taxonomy.md) for the artifact type. Source CSVs remain on the branch. [^lists-readme]: MGB exam-oriented sub-taxonomies README, taxonomy-export-2026-08-15 branch [^xr-chest]: xr_chest_findings.csv, taxonomy-export-2026-08-15 branch diff --git a/knowledge/semantic-foundation/finding-models/identifiers.md b/archive/knowledge/semantic-foundation/finding-models/identifiers.md similarity index 75% rename from knowledge/semantic-foundation/finding-models/identifiers.md rename to archive/knowledge/semantic-foundation/finding-models/identifiers.md index 20a8d3d..ecb5d89 100644 --- a/knowledge/semantic-foundation/finding-models/identifiers.md +++ b/archive/knowledge/semantic-foundation/finding-models/identifiers.md @@ -1,10 +1,10 @@ --- type: Reference title: Finding model identifiers -description: The OIFM and OIFMA identifier grammar, the organization-code segment and its registered values, value codes, how identifiers are minted and checked, and what stability is guaranteed. +description: Finding, attribute, and value identifier formats, generation, validation, and stability limits. tags: [semantic-foundation, finding-models, oifm, identifiers, reference] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: fm-model resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/finding_model.py @@ -28,7 +28,7 @@ sources: # The grammar -Three identifier forms carry the whole referencing scheme. Each is enforced by a regular expression on the Pydantic field that holds it.[^fm-model] +Pydantic fields enforce three identifier formats.[^fm-model] | Identifier | Regular expression | Example | Enforced on | |---|---|---|---| @@ -38,7 +38,7 @@ Three identifier forms carry the whole referencing scheme. Each is enforced by a The digit count is a module constant, `ID_LENGTH`, set to 6 and interpolated into all three patterns, so the three move together if it ever changes. -Reading one of these left to right gives you the whole address. `OIFMA_CDE_000632.1` is value 1 of attribute `000632`, contributed under organization code `CDE`. Because the attribute identifier is globally unique rather than scoped to its finding, an attribute code alone identifies the finding it belongs to, which is what makes the registry lookup in the next section possible. +`OIFMA_CDE_000632.1` identifies value 1 of attribute `000632` under organization `CDE`. A globally unique attribute identifier also identifies its parent finding through the registry. # The organization segment @@ -88,24 +88,24 @@ Identifiers are never written by hand. The authoring tools call the generators; `oifm_ids` maps each finding identifier to the file that defines it. `attribute_ids` maps each attribute identifier to a pair of file name and attribute name. Value codes are not registered separately, because they are derived from the attribute identifier. -The registry is rebuilt from `defs/` by `scripts/validator.py`, which walks every definition file, validates it, and fails if a finding identifier or an attribute identifier is seen twice.[^validator] That check is the only thing standing between random minting and a collision. A pre-commit hook runs the validator on every commit and stages the regenerated `ids.json`, the markdown renders, and the repository index, so a definition cannot land without the registry landing with it. +`scripts/validator.py` rebuilds the registry from `defs/`, validates each file, and rejects repeated finding or attribute identifiers.[^validator] This is the sole collision check. Every commit's pre-commit hook runs it and stages the registry, markdown renders, and corpus index. # Round-trip regeneration, issue 42 -The one documented way to lose an identifier is to round-trip a registered model through `FindingModelBase`. That class has no `oifm_id` field, so validating a stored definition against it silently discards the finding identifier and every attribute identifier. The helper that adds identifiers then sees none present and mints new ones.[^issue42] +Validating a registered model through `FindingModelBase`, which has no `oifm_id`, silently drops its finding and attribute identifiers. The helper then generates new ones.[^issue42] The issue, open since 2026-04-18, describes this as "a silent data-integrity hazard for any workflow that loads an existing `.fm.json` as a dict, needs to mutate it, and wants ID allocation for just the new part." It was hit in a review flow that added a change-from-prior attribute to a legacy model carrying only presence; the published finding identifier and the existing attribute identifier were both regenerated, and external references to them would have become dangling. The workaround shipped in the content work operates on the dictionary directly and allocates only the identifiers genuinely missing, building value codes from the attribute identifier by hand. Four candidate fixes are enumerated in the issue and none has been chosen. # Stability -What is documented amounts to this. +The documented stability rules are: - An identifier, once minted and committed, names one definition file, and the validator enforces that no second file claims it. - The registry admits that an identifier can legitimately appear on more than one row of an external list. The [MGB exam-oriented sub-taxonomies](/semantic-foundation/finding-models/finding-taxonomies.md) note that "an ID can appear on two rows where those earlier writebacks mapped two findings onto one model." That is a property of those files, not of the registry. - Nothing in the repository documents a deprecation, retirement, or supersession mechanism for an identifier. There is no version field on a finding model in the released format, and schema versioning is an open task rather than a shipped feature. - The round-trip defect above is the known exception to stability in practice, and it is unresolved. -Downstream systems rely on this. Exam Finding Lists store nothing but the identifier and a display string, so a changed identifier breaks the link between a stored [Observation](/glossary/observation.md) and its definition. The content repository is the single source of finding model identity for the catalog site, which consumes `defs/` as a git submodule, and for the extraction platform, which resolves identifiers through the published registry. See [the repository map](/repositories/repository-map.md). +Exam Finding Lists reference definitions by identifier and display string, so identifier changes break stored [Observation](/glossary/observation.md) links. The content repository supplies identity to the catalog site, which reads `defs/` through a git submodule, and to the extraction platform's registry lookups. See [the repository map](/repositories/repository-map.md). [^fm-model]: finding_model.py, findingmodel main branch [^base-orgs]: base_organizations.jsonl, findingmodel main branch diff --git a/archive/knowledge/semantic-foundation/finding-models/index.md b/archive/knowledge/semantic-foundation/finding-models/index.md new file mode 100644 index 0000000..b1e3a27 --- /dev/null +++ b/archive/knowledge/semantic-foundation/finding-models/index.md @@ -0,0 +1,10 @@ +# Finding models + +* [Why finding models](./why-finding-models.md) - Why OIDM defines findings, what a finding model contains, and how observations use it. +* [Finding model format](./finding-model-format.md) - Released finding model fields, validation rules, examples, and unreleased metadata extensions. +* [Finding model identifiers](./identifiers.md) - Finding, attribute, and value identifier formats, generation, validation, and stability limits. +* [Finding model content catalog](./content-catalog.md) - Published finding model content, repository conventions, browsing tools, and unmerged batches. +* [MGB exam-oriented sub-taxonomies](./finding-taxonomies.md) - Six MGB exam-oriented finding hierarchies, their fields, counts, model matches, and pending triage. +* [Authoring workflow](./authoring-workflow.md) - Finding model authoring, review, and validation through Forge, the CLI, and repository skills. +* [Metadata enrichment pipeline](./enrichment-pipeline.md) - How finding model definitions get structured metadata assigned by language models, what shipped, what the canonical rewrite on the work edge changes, and why it has not been released. +* [Finding models and common data elements](./finding-models-and-cdes.md) - The workbench relationship: finding models as fast exploratory definitions that can graduate into governed ACR and RSNA common data elements, and the vocabularies both have to fit. diff --git a/archive/knowledge/semantic-foundation/finding-models/why-finding-models.md b/archive/knowledge/semantic-foundation/finding-models/why-finding-models.md new file mode 100644 index 0000000..f3fe0c6 --- /dev/null +++ b/archive/knowledge/semantic-foundation/finding-models/why-finding-models.md @@ -0,0 +1,85 @@ +--- +type: Concept +title: Why finding models +description: Why OIDM defines findings, what a finding model contains, and how observations use it. +tags: [semantic-foundation, finding-models, oifm, concept] +status: draft +generated: { by: codex/gpt-6, at: 2026-09-21T20:34:50Z } +sources: + - id: oifm-overview + resource: https://github.com/openimagingdata/findingmodels/blob/2267d5d0b430ec7117a6da91efe6069da38c0056/prompts/overview.md + title: "Finding Models: Overview, findingmodels repository" + last_modified: 2026-04-18 + - id: deck + resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal + title: "Open Imaging Data Model 2026 Status Update: Realizing Object-Oriented Imaging Results, January 2026" + - id: site-post + resource: https://www.openimagingdata.org/findings-cdes-and-observations/ + title: "Findings, CDEs, and Observations, openimagingdata.org, 2023-06-24" + - id: fm-model + resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/finding_model.py + title: FindingModelFull definition, findingmodel repository, main branch + - id: efl-example + resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/README.md + title: imaging-problem-list README, main branch, the Exam Finding List specification +--- + +# The finding is the unit + +A radiology report records assertions about findings, including their presence, absence, and change. The Open Imaging Data Model (OIDM) represents each finding type with a reusable [finding model](/glossary/finding-model.md). + +Finding models "turn that unstructured language into structured, machine-accessible observation objects, each linked to a rich knowledge base of clinical context."[^oifm-overview] An agent matches report findings to definitions and emits an [Observation](/glossary/observation.md) with a finding type and [attribute](/glossary/attribute.md) values. + +"A radiology report is a snapshot, but the findings it describes persist across exams. A pleural effusion seen today may be the same one from last week, now larger."[^oifm-overview] Shared definitions let the [Imaging Problem List](/glossary/imaging-problem-list.md) group observations by finding across exams and institutions. + +# What a finding is, and is not + +Findings include pathologic entities, physiologic observations, devices and hardware, postsurgical states, anatomic variants, and image quality problems. The authoring overview includes diagnoses: "Diagnostic terms are first-class findings, do not question whether a diagnosis 'should' be a finding."[^oifm-overview] Both "bilateral perihilar opacities" and "pulmonary edema" qualify. + +The test is whether a radiologist would write "there is X" or "no X" as a standalone statement. It excludes finding states such as "stable cardiac silhouette", qualified findings such as "large pleural effusion", normal anatomy, radiographic signs, interpretation techniques, exam metadata, clinical history, and recommendations. The principle is that "a finding is a noun phrase; everything else is an attribute."[^oifm-overview] The [overview extract](/references/oifm-overview-extract.md) has examples of appropriate scope. + +"No fracture" records an observation with [presence](/glossary/presence.md) absent. The finding was looked for and not found. Broad models such as "chest wall fracture" and "upper abdominal abnormality" support these negative assertions. + +# What the definition carries + +A definition contains a canonical name, a description for radiologists, synonyms, [tags](/glossary/tag.md), [index codes](/glossary/index-code.md), optional [anatomic locations](/glossary/anatomic-location.md), [contributors](/glossary/contributor.md), and attributes. Tags support browsing; index codes link to SNOMED CT, RadLex, and the Radiology Gamuts Ontology.[^oifm-overview][^fm-model] + +Attributes characterize a finding. By convention, most models start with presence, whose values are absent, present, indeterminate, and unknown, then [change from prior](/glossary/change-from-prior.md). Finding-specific attributes follow, such as size, severity, density, morphology, and enhancement pattern. Each choice value has a code so an Observation can identify the finding, attribute, and selected value. Tools generate all identifiers. Authors do not assign them by hand. See the [format document](/semantic-foundation/finding-models/finding-model-format.md). + +Definition errors affect downstream interpretation: "A sloppy synonym maps the wrong concept to an observation; a nonsensical attribute propagates meaningless data; a poorly scoped model conflates distinct clinical entities. Errors here don't just look bad, they cause incorrect downstream reasoning about patient care."[^oifm-overview] + +# What a finding model is not + +A finding model defines one finding. Earlier structured-reporting approaches used templates to prescribe report content and order; finding models leave report composition to the application. + +It is not a common data element, at least not yet. The January 2026 deck calls them "the CDE workbench" and "a proving ground for formal common data elements".[^deck] The relationship runs from finding model toward [common data element](/glossary/cde.md), not the other way, and it is worked through in [finding models and CDEs](/semantic-foundation/finding-models/finding-models-and-cdes.md). + +A definition describes a finding type, such as pleural effusion. Patient-specific observations reference that definition, as described in the [architecture](/overview/architecture.md). + +# How the definitions get used + +An [Exam Finding List](/glossary/exam-finding-list.md) entry references the model by its OIFM identifier in `findingCode`. Downstream structures reference finding models only by identifier. It adds an optional anatomic location and an `attributes` array pairing OIFMA attribute identifiers with permitted value codes.[^efl-example] Nothing in the Exam Finding List repeats the definition; a reader that wants to know what `OIFM_GMTS_016552` means resolves it against the content repository. + +```json +{ + "findingCode": "OIFM_GMTS_016552", + "findingDescription": "urinary tract calculus", + "attributes": [ + { + "attributeCode": "OIFMA_GMTS_707209", + "attributeValueCode": "OIFMA_GMTS_707209.1", + "attributeValueDescription": "present" + } + ] +} +``` + +The Imaging Problem List groups a patient's observations across exams using the same identifiers. See the [Exam Finding List document](/data-structures/exam-finding-list.md). + +The 2023 site post described a FHIR Observation "semantically labeled with ACR/RSNA Common Data Element identifiers", with a set identifier for the finding type and element identifiers for its attributes.[^site-post] OIDM now also mints its own identifiers for definitions that no common data element covers. + +[^oifm-overview]: "Finding Models: Overview", findingmodels repository +[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 +[^site-post]: "Findings, CDEs, and Observations", openimagingdata.org, 2023-06-24 +[^fm-model]: FindingModelFull definition, findingmodel repository, main branch +[^efl-example]: imaging-problem-list README, main branch diff --git a/archive/knowledge/semantic-foundation/index.md b/archive/knowledge/semantic-foundation/index.md new file mode 100644 index 0000000..5586ac5 --- /dev/null +++ b/archive/knowledge/semantic-foundation/index.md @@ -0,0 +1,7 @@ +# Semantic foundation + +* [Finding models](./finding-models/) - Open Imaging Finding Models: format, identifiers, content, authoring +* [Common data elements](./common-data-elements/) - ACR/RSNA common data elements and how finding models relate to them +* [Anatomic locations](./anatomic-locations/) - The curated anatomic location set, its data model, and RadLex integration +* [Exam types](./exam-types/) - Exam types: goals and existing building blocks +* [Terminologies](./terminologies/) - External terminologies and the lookup tooling diff --git a/archive/knowledge/semantic-foundation/terminologies/index.md b/archive/knowledge/semantic-foundation/terminologies/index.md new file mode 100644 index 0000000..4e2052d --- /dev/null +++ b/archive/knowledge/semantic-foundation/terminologies/index.md @@ -0,0 +1,6 @@ +# Terminologies + +External vocabularies referenced by OIDM and tools for resolving their codes. + +* [Ontologies used](./ontologies-used.md) - External terminology identifiers, OIDM fields, code counts, and lookup support. +* [med-ontology-lookup](./med-ontology-lookup.md) - Medical terminology lookup, crosswalks, provider failures, planned features, and integration status. diff --git a/knowledge/semantic-foundation/terminologies/med-ontology-lookup.md b/archive/knowledge/semantic-foundation/terminologies/med-ontology-lookup.md similarity index 75% rename from knowledge/semantic-foundation/terminologies/med-ontology-lookup.md rename to archive/knowledge/semantic-foundation/terminologies/med-ontology-lookup.md index 1e671c9..cbb85f0 100644 --- a/knowledge/semantic-foundation/terminologies/med-ontology-lookup.md +++ b/archive/knowledge/semantic-foundation/terminologies/med-ontology-lookup.md @@ -1,10 +1,10 @@ --- type: Project Profile title: med-ontology-lookup -description: The molu library and command line tool that resolves medical terms and codes across RadLex, SNOMED CT, FMA, LOINC, and UMLS, its current capabilities, its stated direction, and its status. +description: Medical terminology lookup, crosswalks, provider failures, planned features, and integration status. tags: [semantic-foundation, terminologies, tooling, project-profile] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:30:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } stale_after: 2027-09-21 sources: - id: readme @@ -44,11 +44,11 @@ sources: # What it is -`med-ontology-lookup` is a Python library and command line tool, `molu`, that looks up medical terms and codes in [RadLex](/glossary/radlex.md), [SNOMED CT](/glossary/snomed-ct.md), [FMA](/glossary/fma.md), [LOINC](/glossary/loinc.md), and [UMLS](/glossary/umls.md) through the BioPortal and UMLS Terminology Services REST APIs.[^readme] It is the infrastructure layer under every other piece of OIDM work that needs to turn a phrase into a code: [index code](/glossary/index-code.md) assignment on [finding models](/glossary/finding-model.md), anatomic coding, and the enrichment pipeline. +`med-ontology-lookup` is a Python library and CLI, `molu`, for medical terms and codes in [RadLex](/glossary/radlex.md), [SNOMED CT](/glossary/snomed-ct.md), [FMA](/glossary/fma.md), [LOINC](/glossary/loinc.md), and [UMLS](/glossary/umls.md). It calls BioPortal and UMLS Terminology Services REST APIs.[^readme] Its role is phrase-to-code lookup for tasks such as [index code](/glossary/index-code.md) assignment to [finding models](/glossary/finding-model.md), anatomic coding, and enrichment. Package integration remains pending, as described below. -Its stated purpose is broader than a search box. The roadmap describes it as "the small, dependable layer that lets people and agents resolve medical language to versioned concepts, inspect the clinically relevant part of an ontology graph, and translate identifiers without learning each terminology provider's API."[^roadmap] +The roadmap calls it "the small, dependable layer that lets people and agents resolve medical language to versioned concepts, inspect the clinically relevant part of an ontology graph, and translate identifiers without learning each terminology provider's API."[^roadmap] -The repository is `openimagingdata/med-ontology-lookup`. Install and API key instructions are in its README and are not repeated here.[^readme] +See `openimagingdata/med-ontology-lookup`'s README for installation and API keys.[^readme] # Providers and credentials @@ -80,11 +80,11 @@ Results are typed. A `Concept` carries `concept_id`, `code`, `ontology`, `pref_l The distinguishing design decision is that a failure is a result, not a silent fallback. A `ProviderFailure` carries a `FailureCategory`, including `not_found`, `authentication`, `authorization`, `licensing`, `rate_limited`, `timeout`, `unavailable`, and `invalid_response`, and a `ProviderOperation` naming which call failed.[^models] The policy is that only absence of a result or an unsupported operation may trigger a fallback to another provider; authentication, licensing, rate limit, timeout, and upstream unavailability stay visible instead of being disguised as an empty answer.[^failures] -This matters to callers because SNOMED CT and UMLS are licensed. An unlicensed caller gets a licensing failure rather than a result that looks like the concept does not exist. +An unlicensed SNOMED CT or UMLS caller gets a licensing failure rather than an apparent missing concept. # Agent skill -The repository ships an agent skill under `skills/med-ontology-lookup/`, portable across agent runtimes, that tells an agent to classify the input, search first for free text, and use the CLI rather than scraping the BioPortal or UMLS web interfaces.[^skill] It is the reason this tool appears in coding workflows across the other repositories. +The portable skill at `skills/med-ontology-lookup/` instructs agents to classify input, search free text first, and use the CLI instead of scraping BioPortal or UMLS.[^skill] Coding workflows in other repositories use it. # Stated direction @@ -108,9 +108,9 @@ Read at `main`, commit `a1fd3ae`, dated 2026-09-20. # Relation to the rest of OIDM -The relationship is real in practice and thin in the documentation. The enrichment pipeline in `findingmodel` runs ontology search concurrently with anatomic location lookup to propose index codes for a finding model, and that phase is recorded as complete.[^enrichment-plan] But that pipeline calls a BioOntology client of its own, and `findingmodel` issue 34 asks for that client and its ontology search protocol to move into the shared `oidm-common` package as infrastructure, described as a hard break requiring an environment-only API key.[^fm-issue-34] +The `findingmodel` enrichment pipeline performs ontology and anatomic searches concurrently, a completed phase.[^enrichment-plan] It uses its own BioOntology client. Issue 34 requests moving that client and search protocol to `oidm-common`, with a hard break requiring an environment-only API key.[^fm-issue-34] -No document in `findingmodel` or `findingmodels` links to this repository, and no OIDM package depends on it today. It is best read as the layer that the coding and enrichment work is converging toward rather than one it already runs on. See [the repository map](/repositories/repository-map.md) for where it sits among the other repositories. +Neither `findingmodel` nor `findingmodels` links to this repository, and no OIDM package depends on it. Integration is a direction, not an implemented dependency. See [the repository map](/repositories/repository-map.md). [^readme]: med-ontology-lookup README [^models]: Concept and provider failure models, med-ontology-lookup diff --git a/knowledge/semantic-foundation/terminologies/ontologies-used.md b/archive/knowledge/semantic-foundation/terminologies/ontologies-used.md similarity index 88% rename from knowledge/semantic-foundation/terminologies/ontologies-used.md rename to archive/knowledge/semantic-foundation/terminologies/ontologies-used.md index d44032d..9a9cbc6 100644 --- a/knowledge/semantic-foundation/terminologies/ontologies-used.md +++ b/archive/knowledge/semantic-foundation/terminologies/ontologies-used.md @@ -1,10 +1,10 @@ --- type: Reference title: Ontologies used -description: Every external terminology OIDM touches, who governs it, the shape of its identifiers, the field and structure that carries it, and which lookup tool resolves it. +description: External terminology identifiers, OIDM fields, code counts, and lookup support. tags: [semantic-foundation, terminologies, reference, coding] status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:30:00Z } +generated: { by: codex/gpt-6, at: 2026-09-21T20:53:02Z } sources: - id: radlex resource: https://radlex.org/ @@ -67,7 +67,7 @@ sources: # How to read this -OIDM mints its own identifiers for two things only: [finding models](/glossary/finding-model.md) and their [attributes](/glossary/attribute.md), under the `OIFM` and `OIFMA` schemes. Everything else it points at. This page lists every external terminology that appears somewhere in the OIDM repositories, says who governs it, what its identifiers look like, which field carries it, and whether the [`molu`](/semantic-foundation/terminologies/med-ontology-lookup.md) lookup tool can resolve it. +OIDM assigns `OIFM` identifiers to [finding models](/glossary/finding-model.md) and `OIFMA` identifiers to their [attributes](/glossary/attribute.md). Other codes come from external terminologies. This inventory records their governance, formats, uses, and [`molu`](/semantic-foundation/terminologies/med-ontology-lookup.md) support. Counts are from the corpus and datasets at the commits pinned in the sources. Finding model counts are occurrences of an [index code](/glossary/index-code.md) across all 2,382 definitions, not distinct concepts. @@ -87,7 +87,7 @@ Counts are from the corpus and datasets at the commits pinned in the sources. Fi # RadLex -The radiology lexicon published by the RSNA, and the terminology OIDM leans on hardest. It supplies the primary key for [anatomic locations](/glossary/anatomic-location.md) in the form of a [RadLex ID](/glossary/radlex-id.md), and it is the second most common index code system in the finding model corpus, with 25,765 occurrences.[^fm-corpus] Presence values across the corpus are coded with it, for example `RID28472` for present and `RID28473` for absent. +RSNA's radiology lexicon supplies primary [anatomic location](/glossary/anatomic-location.md) keys as [RadLex IDs](/glossary/radlex-id.md). With 25,765 occurrences, it is the corpus's second most common index code system.[^fm-corpus] Presence values include `RID28472` for present and `RID28473` for absent. The ontology ships as an OWL file in the RSNA repository, which the next-generation vocabulary analysis identifies as the authoritative artifact going forward.[^radlex-owl][^current-understanding] That analysis also records the licence position: RadLex is "actively governed, and freely licensed for commercial and non-commercial use."[^current-understanding] In OIDM data the `system` string is `RADLEX`; in FHIR encodings the system URI is `http://www.radlex.org`.[^fhir-sample] Details of the relationship in both directions, including the open RSNA issue to fold the OIDM anatomic set into RadLex, are in [RadLex integration](/semantic-foundation/anatomic-locations/radlex-integration.md). @@ -129,7 +129,7 @@ RadElement is the one terminology in this table that OIDM both consumes and feed # Radiology Gamuts Ontology -An ontology of radiologic differential diagnosis lists, published at gamuts.net.[^gamuts] It plays two roles at once. As a coding system, `GAMUTS` appears 1,935 times in the corpus, essentially once per Gamuts-derived definition.[^fm-corpus] As a contributing organization it holds the [organization code](/glossary/oidm-organization-code.md) `GMTS`, which is the middle segment of 1,933 of the 2,382 finding model identifiers. See [content catalog](/semantic-foundation/finding-models/content-catalog.md) for what that content is and [finding model content direction](/roadmap/finding-model-content-direction.md) for where it is headed. +Published at gamuts.net, the Radiology Gamuts Ontology contains differential diagnosis lists.[^gamuts] `GAMUTS` occurs 1,935 times as an index code system, about once per derived definition.[^fm-corpus] Its [organization code](/glossary/oidm-organization-code.md), `GMTS`, appears in 1,933 of 2,382 finding identifiers. See [content catalog](/semantic-foundation/finding-models/content-catalog.md) and [finding model content direction](/roadmap/finding-model-content-direction.md). # DICOM Controlled Terminology diff --git a/assets/brand/anatomic-locations/logo-black.png b/assets/brand/anatomic-locations/logo-black.png new file mode 100644 index 0000000..26f3613 Binary files /dev/null and b/assets/brand/anatomic-locations/logo-black.png differ diff --git a/assets/brand/anatomic-locations/logo-black.svg b/assets/brand/anatomic-locations/logo-black.svg new file mode 100644 index 0000000..e0a8020 --- /dev/null +++ b/assets/brand/anatomic-locations/logo-black.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/brand/anatomic-locations/logo-color.png b/assets/brand/anatomic-locations/logo-color.png new file mode 100644 index 0000000..00abda9 Binary files /dev/null and b/assets/brand/anatomic-locations/logo-color.png differ diff --git a/assets/brand/anatomic-locations/logo-color.svg b/assets/brand/anatomic-locations/logo-color.svg new file mode 100644 index 0000000..84bc2cb --- /dev/null +++ b/assets/brand/anatomic-locations/logo-color.svg @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/brand/anatomic-locations/logo-white.png b/assets/brand/anatomic-locations/logo-white.png new file mode 100644 index 0000000..488887a Binary files /dev/null and b/assets/brand/anatomic-locations/logo-white.png differ diff --git a/assets/brand/anatomic-locations/logo-white.svg b/assets/brand/anatomic-locations/logo-white.svg new file mode 100644 index 0000000..baf394a --- /dev/null +++ b/assets/brand/anatomic-locations/logo-white.svg @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/brand/anatomic-locations/mark.png b/assets/brand/anatomic-locations/mark.png new file mode 100644 index 0000000..9be7505 Binary files /dev/null and b/assets/brand/anatomic-locations/mark.png differ diff --git a/assets/brand/anatomic-locations/mark.svg b/assets/brand/anatomic-locations/mark.svg new file mode 100644 index 0000000..44de4d3 --- /dev/null +++ b/assets/brand/anatomic-locations/mark.svg @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/assets/brand/oidm/logo-black.png b/assets/brand/oidm/logo-black.png new file mode 100644 index 0000000..83aaf02 Binary files /dev/null and b/assets/brand/oidm/logo-black.png differ diff --git a/assets/brand/oidm/logo-black.svg b/assets/brand/oidm/logo-black.svg new file mode 100644 index 0000000..cb025a8 --- /dev/null +++ b/assets/brand/oidm/logo-black.svg @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/brand/oidm/logo-color.png b/assets/brand/oidm/logo-color.png new file mode 100644 index 0000000..364974c Binary files /dev/null and b/assets/brand/oidm/logo-color.png differ diff --git a/assets/brand/oidm/logo-color.svg b/assets/brand/oidm/logo-color.svg new file mode 100644 index 0000000..3044dc5 --- /dev/null +++ b/assets/brand/oidm/logo-color.svg @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/brand/oidm/logo-white.png b/assets/brand/oidm/logo-white.png new file mode 100644 index 0000000..adff8a8 Binary files /dev/null and b/assets/brand/oidm/logo-white.png differ diff --git a/assets/brand/oidm/logo-white.svg b/assets/brand/oidm/logo-white.svg new file mode 100644 index 0000000..34d4380 --- /dev/null +++ b/assets/brand/oidm/logo-white.svg @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/brand/oidm/mark.png b/assets/brand/oidm/mark.png new file mode 100644 index 0000000..5f92ad4 Binary files /dev/null and b/assets/brand/oidm/mark.png differ diff --git a/assets/brand/oidm/mark.svg b/assets/brand/oidm/mark.svg new file mode 100644 index 0000000..722388b --- /dev/null +++ b/assets/brand/oidm/mark.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md new file mode 100644 index 0000000..2bf921e --- /dev/null +++ b/docs/HANDOFF.md @@ -0,0 +1,107 @@ +# Working on this repository + +## Current checkpoint, October 22, 2026 + +Pause the knowledge-plan discussion. Original-file download and verification +tooling is committed as `21e37a0`; planning consolidation as `ea7dfeb`; website +separation as `5da01ae`; knowledge reset as `6e7546a`. All were explicitly +approved by the owner. `bootstrap` is pushed and +[PR #2](https://github.com/openimagingdata/oidm-knowledge/pull/2) is open. +Bundle CI passed; owner review and merge remain pending. + +The current task is Copilot's missing-icon-notices finding. The complete Lucide +and Feather notices are saved with the icons and embedded in their SVG metadata. +Existing diagram copies retain the notices; PNG rendering produces an accompanying +license file. Only embedded license metadata changed in the two affected website +SVGs. Source preservation and export checks pass. The owner authorized +committing and publishing the fix to PR #2. Owner review and merge remain pending. + +The temporary `reconcile-knowledge` branch and worktree have been removed at +the owner's request. Continue in this original checkout. All working files +were preserved when incorporating the first commit. The owner's Claude +session owns website changes, including supporting deployment and task +changes outside `site/`. Keep website work coordinated and separate from graph drafting. +See [the implementation plan](plans/implementation.md) for the current split. +Do not resume the older checkpoint below or begin drafting yet. + +The next step after publishing the license fix is owner review of PR #2. +GitHub rules require a PR and one approving review; +the local hook reserves merging/updating `main` for the owner. Do not direct-push +or use an API merge to bypass that boundary. Establish the combined current +state on `main` before creating worktrees for subsequent work. + +## Knowledge-review checkpoint + +After the remaining cleanup commits, resume execution-plan review from the +unanswered question below. Do not restart the organizing discussion or draft +a knowledge document yet. + +The owner has approved: + +- The Foundation schema overview under Finding Definitions as the first node, + at `knowledge/foundation-context/finding-definitions/schema-overview.md`. +- Five named sections, meaningful nested groups, navigation indexes, and one + document per substantive concept. Outline bullets do not automatically become + files; do not create empty concept stubs. +- Current owner guidance for direction, checked revision-specific sources for + implementation claims, explicit proposals and open questions, and concise + citations backed by detailed references. +- Writing fresh from checked sources before consulting the old bundle for + verified omissions. The deck-first pilot is withdrawn. + +The unanswered question is, "Is the end of that cycle the right point for your +review?" The proposed cycle covers scope and evidence, a fresh draft, the +archive check, links and documentation updates, and validation before owner +review. Its review point is not approved. Restore that context before asking +the owner to answer. Then review unfinished-design status, owner review and +validation, coverage and archive deletion, and the expansion sequence, one +focused question at a time. + +The complete nested outline and unresolved document boundaries remain in +[the implementation plan](plans/implementation.md). The owner's words are in +[organization guidance](references/knowledge-organization-owner-guidance.md). +Use Cases describe envisioned uses; Sample Applications require actual +implementations, including demos and prototypes. Their unfinished inventory +does not block the agreed first node once the execution-plan review is complete. + +The [Claude review record](references/knowledge-outline-claude-review.md) +withdraws its unsupported IDR/FHIR Condition claim. EFL/IDR relationships and +exact FHIR mappings remain open. The requested +[clinical-reconciliation research](references/clinical-information-reconciliation.md) +is complete; it does not settle Imaging Persona's design. + +The knowledge reset was the last cleanup commit. The archive move is complete. Do not +edit the old prose into replacements. Delete the temporary archive only after +useful recovery is complete and active dependencies have been removed. + +## Orientation + +The intended pipeline is private Tigris originals, detailed references, a repository-only OKF knowledge graph, and public presentation pages. The knowledge graph stays in `knowledge/`; the website builds only the independent Markdown content in `site/content/`. It does not build the knowledge bundle. No internal knowledge website is planned. + +Read [project direction](references/project-direction.md) for dated owner statements and terminology, preserved outside the disposable archive. Use [idea coverage](references/idea-coverage.md) for the inventoried concepts, known source-review corrections, and historical evidence. Both are references, not execution instructions. + +Completed imports are documented with their artifacts: [brand assets](brand/README.md), [15 board summaries](references/linked-diagrams/index.md), and [OKF tooling research](references/okf-tooling-comparison.md). The board summaries are inputs to evaluate under the eventual agreed approach. Their capture dates are retrieval dates, not idea dates. + +## Working rules + +- Preserve the record of prior owner text and figure agreements; discuss their use in the new organization rather than silently discarding or automatically migrating them. Record source-fidelity findings separately from editorial preferences and publication approval. +- Check current working-tree changes before edits and preserve concurrent work. Do not infer file ownership or agent assignments from historical session names. +- Use committed CDE files at a pinned revision. Do not cite uncommitted decisions or commit another repository's work to make a citation available. +- Follow the implementation plan's source/sensitivity rules. Never read or summarize reviewer correspondence. Keep originals and private review records out of Git. +- Put generated previews in ignored `scratch/review/`, exposed by `.peruseshow`. Original source artifacts live in `~/data/oidm-knowledge-sources`; the ignored `sources` symlink preserves repository-local paths. Accepted diagram sources and SVGs remain tracked with their pages; follow the [diagram instructions](../tools/diagrams/README.md). +- Do not treat an agent review as human sign-off. Use root `task verify -- ` only for an actual project-lead review of knowledge documents. Website pages use `task site:verify`; the website has its own production review gate. See its README for the implemented requirements. +- Do not commit without explicit permission for that commit. Update the development log for engineering work and the changelog for reader-facing changes. Keep completed detail out of the current work queue. + +## Sources and private review records + +Source repositories are cloned under `~/`; their relevant files and historical pins are described by the idea coverage and project-direction references. Recheck a source's committed state before making current implementation claims. + +Original manuscripts, decks, extracted text, the board captures, the source report, and private review dispositions live in `~/data/oidm-knowledge-sources`, accessible through the ignored `sources` symlink. The collection is also in the `oidm-knowledge-sources` Tigris bucket. All 307 remote objects passed size/SHA-256 comparison. The private inventories retain complete integrity/classification records. The tracked [source manifest](sources-manifest.json) selects 118 artifacts in 25 source groups; use the [source instructions](sources.md) for pull/check commands. The default recovery excludes private review records and older research captures. On another checkout, configure an ignored `sources` symlink to the authorized local collection after obtaining the files. The review files use `sources/review/drafts-.md` and corresponding response files. The unfiled upstream issue draft also remains there. Generated top-level figure iterations have moved to `scratch/review/iterations/`; former dashboard renders are in `scratch/review/renders/`. + +The exam-type source workbook is `~/exam-types/sources/LoincRsnaRadiologyPlaybook.xlsx`. Use `LongCommonName` for exam names; do not infer that the `core-playbook-dev` sheet is a reviewed standard. + +## Validation and site + +Run `task check` for strict OKF conformance, the house checker, and source-manifest validation. Run `task sources:check` separately against the private local collection. [Writing the new knowledge graph](knowledge-authoring.md) describes reconstruction and metadata conventions. New references must keep working links and source IDs. Index descriptions match the target frontmatter. + +The root task file includes `site/Taskfile.yml` under the `site` namespace. Use `task site:check`, `task site:build`, `task site:serve`, `task site:preview`, `task site:deploy`, `task site:deploy:staging`, and `task site:deploy:prod` for the website. Its checks, review tools, and production review gate are independent of the knowledge bundle. Read [site instructions](../site/README.md) for implemented behavior. Local configuration does not prove deployed contents or credentials. diff --git a/docs/brand/README.md b/docs/brand/README.md new file mode 100644 index 0000000..2392302 --- /dev/null +++ b/docs/brand/README.md @@ -0,0 +1,58 @@ +# Brand assets + +Original assets retrieved from `t3://oidm-public/logos/` on 2026-09-30. [The manifest](./manifest.json) records each source URI, local path, size, and SHA-256. Files were renamed locally but their contents were preserved. + +## What the guide establishes + +The supplied [2023 style guide](./style-guide-2023.pdf) uses the earlier name **Open Radiology Data Model**. It has two pages covering the logo font and colors. The imported full OIDM logos say **Open Imaging Data Model**. + +| Item | Guide | Implication for the site | +|---|---|---| +| Logo font | Arial Rounded MT Bold, page 1 | Use the supplied artwork to preserve the lettering. This is not a specification for website headings or body text. | +| Light blue | `#49b5ea`, Pantone 298 C, page 2 | A candidate accent color, also present in the OIDM SVGs. | +| Dark blue | `#0033cc`, RGB 0, 51, 204, Pantone 2728 C, page 2 | A candidate link and emphasis color on light backgrounds. Both projects' artwork uses it. | + +**Color discrepancy:** page 2 prints light blue as RGB **4, 149, 234**, which is `#0495ea`, alongside hex **#49b5ea**, which is RGB **73, 181, 234**. The OIDM SVG fills use `#49B5EA`. Use that artwork-backed value for proposed web accents; the original guide remains unchanged. Some paths also use `#0133CC`, a near-match to the dark blue, preserved in the original files. + +The guide gives no website layout, body typography, spacing, minimum logo size, clear-space, or dark-mode rules. It supplies no font files or webfont license. SVG lettering is outlined, so displaying these logos requires no font installation. + +## Assets to use + +All paths below are relative to [`assets/brand/`](../../assets/brand/). Each filename is available in both `.svg` and `.png`. + +| Project | Full logo variants | Square mark | SVG proportions | +|---|---|---|---| +| OIDM | `oidm/logo-color`, `logo-black`, `logo-white` | `oidm/mark` | Full logo 800 × 197.89; mark 720 × 720 | +| Anatomic Locations | `anatomic-locations/logo-color`, `logo-black`, `logo-white` | `anatomic-locations/mark` | Full logo 538.02 × 179.27; mark 720 × 720 | + +Prefer SVG for the website and diagrams. Transparent PNGs support tools that require raster input: OIDM logos are 2000 × 495, Anatomic Locations logos 2000 × 665, and both marks 720 × 720. The square marks came from the supplied favicon files. Their usefulness at very small sizes should be checked when installing a site favicon. + +The selection omits duplicate print/editing formats, JPGs, pre-sized raster copies, overview illustrations, backgrounds, and the graveyard. The vector originals cover resizing without adding those copies. + +## Suggested application + +These are implementation suggestions, not additional rules from the guide: + +- Use the color full logo for project identification on light backgrounds, the white full logo on dark backgrounds, and black for monochrome output. Preserve proportions and allow space around the artwork. +- Use the square marks beside project names in diagrams. Keep the name visible when the mark alone would be ambiguous. Do not use a project logo as a generic symbol for an anatomy or data-model concept. +- Use dark blue for candidate light-theme links or emphasis. Keep light blue for accents and larger graphic areas. Calculated contrast against white is about 8.95:1 for dark blue and 2.31:1 for light blue, so they are not interchangeable text colors. Check the actual background when applying either. +- Keep the current reading fonts until typography is addressed separately. The existing Quartz theme is in `site/quartz.config.yaml`; importing these assets does not apply a new theme. + +## Use in site content and diagrams + +Publishing these files on the website from `assets/brand/` is part of the site build work in [the site roadmap](../../site/ROADMAP.md); until that lands, site pages do not reference them. + +For a diagram builder using `tools/diagrams/excalib.py`: + +```python +image("oidm-logo", x, y, 200, 200 * 197.89 / 800, + "../../assets/brand/oidm/logo-color.svg") +image("anatomic-locations-mark", x, y, 48, 48, + "../../assets/brand/anatomic-locations/mark.svg") +``` + +Use distinct positions for each element. The helper resolves these paths relative to its own directory and embeds the SVG bytes into the Excalidraw file. Omit the `color` argument to preserve brand colors. Follow the [diagram source-of-truth rule](../../tools/diagrams/README.md#source-of-truth-rule) when adding a logo to an existing graphic. + +## Import validation + +Completed 2026-09-30. The local Quartz build emitted all 16 imported assets unchanged. All 17 source-file checksums matched the manifest, and all eight SVGs embedded unchanged through `excalib.image()`. Both bundle validators passed. The import did not change the theme or diagrams or deploy the site. diff --git a/docs/brand/manifest.json b/docs/brand/manifest.json new file mode 100644 index 0000000..c529710 --- /dev/null +++ b/docs/brand/manifest.json @@ -0,0 +1,107 @@ +{ + "retrieved": "2026-09-30", + "files": [ + { + "path": "assets/brand/oidm/logo-color.svg", + "source": "t3://oidm-public/logos/Open Imaging Data Model/Open Imaging Data Model logo.svg", + "bytes": 29836, + "sha256": "6872de52cfdda7bafa4778f9f60a3cf2986cea1ed62a45985731cf7663b169c0" + }, + { + "path": "assets/brand/oidm/logo-color.png", + "source": "t3://oidm-public/logos/Open Imaging Data Model/Open-Imaging-Data-Model-logo.png", + "bytes": 49644, + "sha256": "3b95bc08f6ceb041e6e097d424fbcdf0b80f323bf5ee353b88c096e5443987b4" + }, + { + "path": "assets/brand/oidm/logo-black.svg", + "source": "t3://oidm-public/logos/Open Imaging Data Model/Open Imaging Data Model logo black.svg", + "bytes": 29360, + "sha256": "b27765f299d3f171952f27c068ee0fdb8b1b4bb578624a59b6e36df66d30c712" + }, + { + "path": "assets/brand/oidm/logo-black.png", + "source": "t3://oidm-public/logos/Open Imaging Data Model/Open-Imaging-Data-Model-logo-black.png", + "bytes": 42289, + "sha256": "089e8f9d2e0c90a3a4df9bb5e1051441e329dce7f59604c707b7260cc76d8ec4" + }, + { + "path": "assets/brand/oidm/logo-white.svg", + "source": "t3://oidm-public/logos/Open Imaging Data Model/Open Imaging Data Model logo white.svg", + "bytes": 29995, + "sha256": "0a99b4ed2091e99dfb5a376b865bf348365fdb7923f96c239c4a877e50d9df63" + }, + { + "path": "assets/brand/oidm/logo-white.png", + "source": "t3://oidm-public/logos/Open Imaging Data Model/Open-Imaging-Data-Model-logo-white.png", + "bytes": 48393, + "sha256": "98a4771e17b0ddb0423d066778561d1c406c88320b9bf577735f2ba7fafc8ef8" + }, + { + "path": "assets/brand/oidm/mark.svg", + "source": "t3://oidm-public/logos/Open Imaging Data Model/Open Radiology Data Model Project favicon.svg", + "bytes": 11504, + "sha256": "bbea5bf9eb403b7669783b560a32c8c3a43da791c95e24e61e61b21d9a78d183" + }, + { + "path": "assets/brand/oidm/mark.png", + "source": "t3://oidm-public/logos/Open Imaging Data Model/Open-Radiology-Data-Model-Project-favicon.png", + "bytes": 18533, + "sha256": "bc7f99373c4e8367d60ad634e0b6c83d9cb16b6a971f711e6d600d318acd7d4a" + }, + { + "path": "assets/brand/anatomic-locations/logo-color.svg", + "source": "t3://oidm-public/logos/Anatomic Locations/AnatomicLocations logo.svg", + "bytes": 22668, + "sha256": "c0a83e076d969d616c809354b658df5b463989ed3b3d5985b282a359cfcab759" + }, + { + "path": "assets/brand/anatomic-locations/logo-color.png", + "source": "t3://oidm-public/logos/Anatomic Locations/AnatomicLocations-logo.png", + "bytes": 36000, + "sha256": "5e49ad69407de57963b45f0eaf6f229988b1ebed556c450789f7b76e2c429862" + }, + { + "path": "assets/brand/anatomic-locations/logo-black.svg", + "source": "t3://oidm-public/logos/Anatomic Locations/AnatomicLocations logo black.svg", + "bytes": 22318, + "sha256": "ca728f7b1c1f8b53bbe08d8efc20aebb6ae0f5e23b4fd96776ec910e63b711cb" + }, + { + "path": "assets/brand/anatomic-locations/logo-black.png", + "source": "t3://oidm-public/logos/Anatomic Locations/AnatomicLocations-logo-black.png", + "bytes": 31188, + "sha256": "87908be4a2fbe08d3ff5649db194db9cd78778d1d28b91062b177bbe54ff6108" + }, + { + "path": "assets/brand/anatomic-locations/logo-white.svg", + "source": "t3://oidm-public/logos/Anatomic Locations/AnatomicLocations logo white.svg", + "bytes": 22647, + "sha256": "4eb007fee8228e65f62165c1659719242bec8177f53ad81c10fd84731bdf6d3d" + }, + { + "path": "assets/brand/anatomic-locations/logo-white.png", + "source": "t3://oidm-public/logos/Anatomic Locations/AnatomicLocations-logo-white.png", + "bytes": 35044, + "sha256": "e1284e1b4f63de7e18b9d1b093210018122de4f7fbd6f00daa89a42f5a55aa20" + }, + { + "path": "assets/brand/anatomic-locations/mark.svg", + "source": "t3://oidm-public/logos/Anatomic Locations/AnatomicLocations favicon.svg", + "bytes": 3129, + "sha256": "acd4c6467bf638c07825d902fbc807ebae3a237f095c5dedf4698f336b5833c6" + }, + { + "path": "assets/brand/anatomic-locations/mark.png", + "source": "t3://oidm-public/logos/Anatomic Locations/AnatomicLocations-favicon.png", + "bytes": 11523, + "sha256": "444b7ca951c491e79cc0e41a8fdd7795dcadaa0c973b85b51f2af98b4f224524" + }, + { + "path": "docs/brand/style-guide-2023.pdf", + "source": "t3://oidm-public/logos/Open Radiology Data Model Project style Guide.pdf", + "bytes": 69726, + "sha256": "f0d08539d4a5f1a78bda59a7bc7bb6237571a594a20b2c91fb8cd670c6e9b37f" + } + ] +} diff --git a/docs/brand/style-guide-2023.pdf b/docs/brand/style-guide-2023.pdf new file mode 100644 index 0000000..bf957e6 Binary files /dev/null and b/docs/brand/style-guide-2023.pdf differ diff --git a/docs/knowledge-authoring.md b/docs/knowledge-authoring.md new file mode 100644 index 0000000..e392894 --- /dev/null +++ b/docs/knowledge-authoring.md @@ -0,0 +1,27 @@ +# Writing the new knowledge graph + +These instructions govern fresh reconstruction after the knowledge reset. +They do not authorize drafting now. The owner must complete the remaining +execution-plan review before concept writing begins. The old generated bundle +is temporary recovery material in `archive/knowledge`, not a prose template. + +The repository-only OKF graph lives in `knowledge/`. Follow the owner's nested conceptual outline and reconstruction method in [the implementation plan](plans/implementation.md). + +Write each concept from an empty file, using relevant sources and processed references. After drafting, check `archive/knowledge` for useful omissions and source leads. Verify recovered claims before adding them. The archive's wording, hierarchy, and file boundaries are not templates for the new graph. + +## Documents and connections + +- Write one substantive concept per Markdown document. Decide its scope from the outline and content, not from old files. +- Use plain-English titles and preserve the outline's parent context. Explain relationships in prose with Markdown links to related concepts. +- Record facts, stated goals, proposals, and unresolved questions with their actual status. Do not resolve disagreements by inference. +- Source substantive claims. Use pinned repository revisions and precise source locators. Preserve the source-handling rules in the implementation plan. +- Keep processed source references in `knowledge/references/`. They must be checked against sources; archived reference prose is not automatically suitable for reuse. +- Keep outward-facing page work in `site/`, planning in `docs/`, and shared assets outside the knowledge graph. + +## OKF metadata and navigation + +Each concept needs YAML frontmatter with `type`, `title`, `description`, and `generated: { by, at }`. Use a type supported by `tools/check_bundle.py`, such as Concept, Reference, or Data Structure. New agent-written documents have `status: draft`. Record sources in the `sources` list, with an `id` and `resource` for each entry; use matching footnotes for claim attribution. A human verification entry records an actual owner review, not an agent's assessment. + +Directory `index.md` files list their documents and subdirectories with matching descriptions. Only the bundle-root index carries frontmatter, containing `okf_version: "0.2"`. Record bundle changes newest first in `knowledge/log.md` under dated headings. + +Run `task check` for deterministic OKF validation, the house checker, and source-manifest validation. Commits require explicit permission each time. diff --git a/docs/plans/implementation.md b/docs/plans/implementation.md new file mode 100644 index 0000000..910583f --- /dev/null +++ b/docs/plans/implementation.md @@ -0,0 +1,764 @@ +# Source-to-pages implementation plan + +Updated 2026-10-22. This remains the single planning record. The owner has approved moving the old bundle to `archive/knowledge` and reconstructing the graph from fresh documents in `knowledge/`. The conceptual outline remains under collaborative development. Do not create parallel restructure plans or dashboards. + +## Current task: preserve icon license notices + +The four cleanup commits are complete, including the knowledge reset in +`6e7546a`. `bootstrap` is published in [PR #2](https://github.com/openimagingdata/oidm-knowledge/pull/2), +awaiting owner review and merge. Knowledge drafting remains paused. + +Copilot identified missing Lucide and Feather notices in the diagram icons. +The fix is complete and checked. The owner authorized committing and publishing +it to PR #2. Owner review and merge remain pending. + +1. Complete: save the complete upstream license with the vendored icons and embed its + notices as nonvisual SVG metadata so copies retain them. +2. Complete: update existing diagram copies without changing artwork. Limit website + asset edits to embedded license metadata. Preserve notices in future + SVG exports and provide accompanying notices for PNG exports. +3. Complete: check source and export preservation and update the diagram + instructions and handoff/log. Publish the owner-approved fix to PR #2. + +Verification: all nine icons retain complete notices through recoloring and +embedding; a rendered diagram retains all seven Lucide notices and produces +the PNG license file. All 15 edited icon/diagram files differ only in license +metadata, and the before/after PNG renders are byte-identical. Repository and +website checks pass. No concept drafting. + +## Completed cleanup: separate the accumulated changes into commits + +The owner paused the content-plan discussion to reconcile the accumulated +working-tree changes. Finish this work before resuming the unanswered review +question or drafting knowledge documents. + +1. Original-file download and verification commands: complete in `21e37a0`, + now on `bootstrap`. The owner approved this first commit. It contains the + manifest, commands, tests, CI checks, and instructions. All original + working-file contents were preserved when incorporating it. +2. Planning and reference consolidation: complete in `ea7dfeb`, committed + October 22 with the owner's explicit permission after Astra's independent + review and follow-up confirmation. Approved decisions and the unanswered + drafting question are preserved; the knowledge reset and website work + remain outside that commit. +3. Website separation: complete in `5da01ae`, committed October 22 with the + owner's explicit permission after coordination with Claude. It contains + independent site content/build/review tools, root task integration, and + site-only documentation. All remaining working changes were preserved. +4. Knowledge reset: complete in `6e7546a`, committed with the owner's explicit + permission. The archive, fresh index/log, and graph documentation are + published with the other cleanup commits in PR #2. Do not start concept drafting. + +The owner's Claude session owns the website changes, including deployment +configuration and website task routing outside `site/`. Split shared root +files by purpose; do not include website code in the remaining graph commit. + +The temporary `reconcile-knowledge` worktree and branch have been removed at +the owner's request. Continue in this original checkout. The tab created for +the worktree was already closed when checked. No website files were changed. +Keep this plan, the handoff, references, and logs aligned with each proposed +commit and the final state. Each remaining commit needs separate permission. + +### Bring the website and bundle changes onto main, October 22 + +The owner requested direct coordination with the existing Claude session +through Herdr. Prepare the remaining work in this shared checkout, without +creating new branches or worktrees for cleanup. + +1. Agree on the commit boundary with Claude. Claude prepares website-owned + files and reports checks; Codex handles shared root documentation and task + integration. Only one agent may change the shared index at a time. +2. Prepare the website commit first, including the root routing needed for + `task site:...`, website deployment configuration, and removal of the old + knowledge-to-site preparation script. Validate its exact proposed snapshot + with the original bundle still present. Obtain permission for that commit. +3. Prepare the knowledge reset last: verify archive preservation and retained + owner guidance, fix active citations, and check the fresh index/log and final + documentation. Website code remains outside this change. Obtain permission + for that commit. Do not begin knowledge drafting. +4. Publish `bootstrap` and open a new pull request after commit/push approval. + Remote `main` is an ancestor of `bootstrap`; PR #1 was closed without merging. + Effective repository rules require a pull request and one approving review. + The local pre-push hook reserves updates to `main` for the owner. Do not + bypass it through an API merge or force-push. A regular owner-completed merge + preserves the named cleanup commits; rebase/squash would alter that history. +5. Check the combined committed state, account for any remaining changes, and + update the handoff and logs. Establish current `main` before proposing + separate branches or worktrees for subsequent graph and website work. + +The owner separately approved both commits and publication. `bootstrap` is +pushed and PR #2 is open. Bundle CI passed. Owner review and merge remain pending. + +Claude agreed to this boundary through Herdr on October 22. Claude owns `site/`, +`.github/workflows/deploy.yml`, and deletion of `tools/prepare_site_content.py`. +Codex owns shared root edits and serializes staging. The full remaining root +Taskfile integration belongs to the website commit. README, changelog, handoff, +and log changes are split by purpose; contributor and archive-state changes +belong to the graph commit. No deferred comments feature or new site functionality +is included in this cleanup. + +Claude verified dev/staging checks, a site build, and a staging build containing +only website files and shared brand assets. The production check correctly +rejects all 12 unstamped pages. Codex verified the archive and active bundle +with both validators. Merging the final state runs bundle CI and the staging +build workflow; staging deployment requires the configured Cloudflare secrets. +It does not deploy production or grant page-review stamps. + +## Organizing decisions and withdrawn approach + +The project lead considers the generated knowledge bundle ‘basically DOA’: useful material is buried in unsupported, unreadable generated prose. Treat it as material to mine critically for relevant, supported ideas. Its structure and apparent coverage are not a foundation to preserve by default. + +The owner has approved the five-section organization, document boundaries, starting node, and source approach described below. Preserve those decisions. The node-drafting workflow and remaining execution-plan aspects are still under review. Once repository reconciliation is finished, resume that review one aspect at a time, with short responses and one focused question. Read recorded owner guidance before asking the owner to repeat it. Do not begin drafting yet. + +The assistant-selected SIIM 2026 deck-first pilot is withdrawn. That dated presentation is one source among others, not the starting point or organizing authority. Completed planning cleanup and source-storage/tooling work remain complete. Historical maps and agent reviews preserve context, not authorization to resume the withdrawn sequence. + +## Intended pipeline + +Implement the pipeline **private Tigris sources → detailed references → repository-only OKF knowledge graph → public presentation pages**. The October 2 clarification removes the separate internal website from scope. The intended graph and presentation remain separate layers. This does not endorse the existing generated bundle or settle its replacement organization. + +| Layer | Destination | Contract | +|---|---|---| +| Original evidence | Private `oidm-knowledge-sources` bucket; local collection at `~/data/oidm-knowledge-sources` | Preserve originals, extraction artifacts, checksums, source dates, and sensitivity. Track a reviewed `docs/sources-manifest.json`, not raw files or private local inventory metadata. | +| Processed summaries | `knowledge/references/` | Detailed OKF references with source locators, claims, qualifications, and permitted or redrawn figures. | +| Knowledge graph | `knowledge/` | Comprehensive, interconnected idea documents citing references. Publicly readable in Git, but not a separate website. | +| Presentation | `site/content/` | Curated explanations for outsiders. Separate track, planned in `site/ROADMAP.md`; only this directory and its intended assets feed the public site. | + +The owner approved five sections: Foundation Context, Data Structures, SDKs, Use Cases, and Sample Applications, with an Overview introducing their connections. Meaningful nested groups and substantive concept documents are approved; individual document scopes and unfinished outline sections remain under discussion. Repository artifacts supply evidence, and the presentation can summarize and cite fuller knowledge accounts. + +Supporting material has distinct homes: + +- [Project direction](../references/project-direction.md) preserves dated owner statements and source-review observations used by existing citations. +- [Current owner guidance](../references/knowledge-organization-owner-guidance.md) preserves the original nested outline and later SDK, Applications, terminology, and archive instructions in the owner's words. +- [Claude's outline review](../references/knowledge-outline-claude-review.md) preserves the full initial agent review. Its proposals are distinct from owner decisions and refer to an earlier version of the outline. +- [Idea coverage](../references/idea-coverage.md) accounts for the 87 inventoried ideas and links their historical evidence. It is a reference, not another work queue. +- [Board summaries](../references/linked-diagrams/index.md), [brand guidance](../brand/README.md), and the [tooling comparison](../references/okf-tooling-comparison.md) retain completed research and import results. +- [Clinical-information reconciliation research](../references/clinical-information-reconciliation.md) records primary sources and open questions for Imaging Persona, distinct from the specific SDK capability for longitudinal finding linking. +- `docs/HANDOFF.md` provides working instructions. `DEV_LOG.md` records completed work; `CHANGELOG.md` records reader-facing changes. +- Ignored `scratch/review/` holds review renders and working research. `.peruseshow` exposes that directory locally. Accepted diagram sources and assets remain tracked beside their content. + +## Current state + +| Work | State | What remains | +|---|---|---| +| Plan consolidation and temporary-file cleanup | Complete in `ea7dfeb`, owner-approved October 22 | Preserve the outline, references, and review checkpoint during the remaining two commits. | +| Original source collection | All 307 remote objects match local sizes/SHA-256; manifest and recovery tools complete | 25 source groups / 118 selected artifacts are recoverable; selection does not establish content priority or approval. | +| Board capture | Complete: 15 boards, native exports and metadata retained | Content use awaits the agreed approach; capture dates are not idea dates. | +| Brand import | Complete: 16 assets, style guide, checksum manifest | Theme application is deferred. Preserve assets through the presentation build change. | +| Knowledge graph | Reset prepared in the working tree; archive move and fresh index/log remain outside the planning-consolidation commit | Commit the reset last. Drafting remains paused pending the remaining plan review. | +| Presentation content | Moved to `site/` on 2026-10-05 | Separate track; see `site/ROADMAP.md`. | +| Site | Independent website and root task integration committed as `5da01ae` | Preserve it while completing the graph reset, then publish the combined branch through a PR. | + +This plan records content agreement separately from publication approval. A review by an agent, or an agreed paragraph, does not itself supply the project's human verification stamp. + +## Root task integration with the separate website, October 6 + +Authorized by the owner-approved site-session handoff. This is separate from the pending review of the knowledge tree's physical organization. Do not edit `site/` or stage another agent's changes. + +This records the October 6 work in the shared working tree. Its code changes +and command-routing documentation belong to the website session's separate +commit; they are not included in the planning-consolidation commit. + +- [x] Include `site/Taskfile.yml` under the `site` namespace in the root task file; remove duplicate website tasks and hostname variables from the root. +- [x] Keep root verification and review tasks for the OKF graph in `knowledge/`, with descriptions and default review-list scope reflecting that role. Retain `tools/verify.py`. +- [x] Update root documentation to describe the website's implemented `site/content/` input, independent checks and review tools, and `task site:...` commands. +- [x] Check task discovery and dry-run routing without deploying or stamping reviews; run root and site validation, then update the plan and logs for the final state. + +Complete. Task 3.54.0 discovers the included namespace, following [Task's include documentation](https://taskfile.dev/docs/guide/includes). Root `task check` passes with zero errors or warnings; `task site:check` passes for 12 pages. Eight dry runs confirm website command routing and separate knowledge and website verification tools. Root review listing defaults to `knowledge/`. Updated documentation links resolve, and all 42 site-owned paths match the recorded baseline. No deployment, review stamping, site edits, or commit occurred. + +## Completed work + +The records below describe completed cleanup and source infrastructure. Finish separating the accumulated changes into commits before resuming the organizing discussion. + +### 0. Consolidate this repository's planning records + +- [x] Write this replacement plan before reorganizing the records. +- [x] Integrate the pipeline, current decisions, presentation map, and remaining work here. +- [x] Extract cited owner guidance into a reference and combine the idea inventories into one coverage reference. +- [x] Delete superseded and completed plans after preserving their useful evidence and results. +- [x] Move temporary previews to ignored `scratch/review/`; expose that area through `.peruseshow` and preserve accepted assets. +- [x] Repair references; update the handoff, README, contributor/site guidance, bundle log, development log, and changelog. +- [x] Run strict OKF validation, the house checker, documentation-link checks, and ignore/Peruse checks. Review the final document set. + +Done means `docs/plans/` contains this one plan, obsolete execution instructions are removed, and citations still reach their evidence. Consolidation does not establish an approved content organization. + +For the separate October 19 consolidation commit, retain `knowledge/plans/` +with the rest of the old bundle. Its removal and any bundle citation repairs +belong to the knowledge reset. The implementation plan here governs current +work; the old bundle's build plan remains historical evidence. + +#### Account for the removed planning files + +The removed records remain available in Git at `21e37a0`. Their current homes +and dispositions are listed below. Historical source links use pinned commits +so deleting a plan does not silently change the cited evidence. + +| Removed records under `docs/plans/` | Preserved material or disposition | +|---|---| +| `2026-09-22-layout-plan.md`, `2026-10-01-sources-and-layers.md`, `restructure-around-project-ideas.md`, `restructure-joint-notes.md`, `idea-placement.md` | Current decisions and the nested outline are in this plan. Owner statements and source qualifications are in `docs/references/project-direction.md`; historical placement and source entries are indexed in `docs/references/idea-coverage.md`. Superseded execution instructions are withdrawn. | +| `idea-inventory-semantic.md`, `idea-inventory-structures-and-uses.md` | All 87 inventoried ideas have entries in `docs/references/idea-coverage.md`, linked to their historical source entries. The inventory is an omission check, not an execution queue. | +| `2026-09-30-brand-assets.md` | Import results are retained in `docs/brand/README.md`; assets, original style guide, and checksums stay in their established locations. | +| `2026-10-01-linked-diagrams.md` | Capture results and qualifications are retained in `docs/references/linked-diagrams/index.md`; all 15 summaries remain. | +| `2026-10-01-okf-tooling-comparison.md`, `2026-10-01-okf-tooling-review.md` | Completed research remains in the corresponding `docs/references/okf-tooling-*.md` reports. No tool migration is authorized. | +| `lean-content-pass.md`, `lean-content-full-review.md`, `lean-content-coverage.tsv` | Historical review results remain in `DEV_LOG.md`; the complete coverage table moves to `docs/references/lean-content-coverage.tsv`. Those editorial passes do not approve the rejected bundle. | +| `repository-review.md`, `restructure-codex-drafts.md`, `restructure-dashboard.md` | Historical assessments, draft-review results, and dispositions remain in `DEV_LOG.md`, the project-direction reference, and Git history. The dashboard and obsolete assignments are retired. Private review records stay outside Git. | +| `renders/*.png` | All 29 tracked previews have identical copies in ignored `scratch/review/renders/`. `.peruseshow` exposes the review directory locally; accepted assets stay tracked. | + +The owner's October outline and subsequent clarifications remain verbatim in +`docs/references/knowledge-organization-owner-guidance.md`. Claude's review and +its later correction remain in `docs/references/knowledge-outline-claude-review.md`. +The clinical-reconciliation research remains a reference, not a design decision. +This consolidation does not reopen approvals or approve the unanswered node-review +workflow. + +October 19 checks pass on an exported copy of the proposed commit: strict OKF +conformance, house checks, and manifest validation report zero errors or +warnings. All 115 existing concepts and the old bundle's planning files remain +unchanged. All 169 local documentation links, eight pinned Git source paths, +and 87 inventory source anchors resolve. The historical coverage table and +all 29 tracked preview copies match their originals byte for byte. Owner +guidance, the corrected review record, and the full conceptual outline are +unchanged. Website and knowledge-reset paths are excluded from staging. +Commit permission remains pending. + +Astra independently reviewed the staged consolidation on October 19 and judged +it ready with minor fixes. The application inventory is now explicitly ongoing +work rather than a prerequisite for the agreed first node. The site state row +now identifies website-session work outside this commit, rather than assuming +its task namespace exists. The handoff's repeated checkpoints were shortened, +and the staged contributor instructions no longer require a migration ledger. +The review found no significant lost decision. This is agent review, not owner +approval of the plan's remaining questions or permission to commit. + +Validation completed October 2: both bundle validators pass with zero errors or warnings; 404 local documentation links resolve; all 116 moved previews match their recorded checksums; the Quartz build succeeds. Peruse's local parser confirms the review allowlist and ancestor traversal; its running server was unreachable from this environment, so live UI verification remains unperformed. + +### 1. Establish recoverable source storage + +Complete, 2026-10-02. [Source instructions](../sources.md), [manifest](../sources-manifest.json), and `tools/sources.py` define the workflow. + +- [x] Relocate all 307 files to `~/data/oidm-knowledge-sources`, verify local checksums, and preserve existing paths through the ignored `sources` symlink. +- [x] Download all 307 remote objects to an isolated directory and verify every size and SHA-256 against the local inventory. No remote files changed. +- [x] Classify the full collection. The tracked manifest selects 25 source groups and 118 artifacts. Retain 152 older screenshots, 31 private review files, five working-research files, and one private capture index outside the default source manifest. +- [x] Record source IDs, organizations/dates where known, status, sensitivity, handling rules, and artifact keys, sizes, hashes, added timestamps, roles, and parent relationships. Unknown dates remain unknown; board material defaults to internal handling until publication evidence is available. +- [x] Implement `task sources:manifest`, `task sources:pull`, and `task sources:check`. Pull verifies bytes before adding files and refuses to overwrite differing local content. Manifest validation and recovery tests run in CI without private files or credentials. +- [x] Test recovery of all 118 artifacts into an empty directory using a separate copy of the tool and manifest. A second pull skipped all files. Eight tests cover invalid paths/lineage, corrupt or failed transfers, conflicts, symlinks, and concurrent destination creation. +- [x] Check access evidence: bucket settings disable object ACLs; anonymous HEAD requests to a deck, board capture, and private review object returned 403. This is sampled access verification, not an exhaustive policy audit. No access settings changed. +- [x] Document authentication, local path overrides, source selection, compatibility links, manifest maintenance, and the distinction between storage verification and content approval. Update entrypoints and logs; repository checks pass. + +Private `.local-inventory.json`, `.remote-inventory.json`, `.remote-verification.json`, and `.classified-inventory.json` account for the full collection outside Git. `docs/sources-manifest.json` covers the selected source artifacts only. Whole-packet manuscript files remain restricted containers; hash verification did not inspect reviewer correspondence. + +## Reconstruction method and temporary archive, October 6 + +The owner selected `archive/knowledge` as the temporary home for the old generated bundle. Preserve its current files and working-tree edits when moving it. Build the new graph in `knowledge/`, following the owner's nested outline. The outward-facing pages in `site/` remain a separate workstream. + +### Write fresh, then check for omissions + +1. Define the concept and its place in the owner's outline. +2. Examine the relevant original sources and processed references, identifying supported current thinking and unresolved questions. +3. Write a new knowledge document from an empty file. Do not use an archived document as a template or edit it into the replacement. +4. Check the archived material afterward for relevant ideas or source leads omitted from the fresh draft. Verify anything recovered against its sources before adding it. +5. Connect the new document to related concepts and review substantive questions with the owner, one at a time. + +The archived prose, file boundaries, and hierarchy have no organizing authority. Existing source-storage and recovery tools remain complete. Processed references added to `knowledge/references/` must be source-grounded; moving the archive does not approve its generated reference prose. + +### Delete the archive when recovery is complete + +`archive/knowledge` is temporary. Delete it after the worthwhile, relevant, supported material has been recovered or its source leads incorporated into the new graph. Do not retain it as a permanent second knowledgebase. + +Before deletion, review recovery coverage, preserve recorded owner guidance outside the disposable archive, and ensure no active document depends on an archived file. Record deliberately excluded or unsupported material where needed to explain the recovery review; do not copy discarded prose merely to preserve volume. The owner explicitly requested this final deletion. Commits still require separate permission. + +The first three completed items below record working-tree preparation on +October 6. They remain outside this planning-consolidation commit and must be +checked again when preparing the knowledge reset. + +- [x] Move the existing bundle intact to `archive/knowledge` and verify file preservation: 138 files, 1,181,586 bytes, all SHA-256 checksums unchanged. +- [x] Establish a clean `knowledge/` with its index and change log; no inherited concept prose. +- [x] Update repository instructions and preserve recorded owner guidance in `docs/references/project-direction.md`, outside the disposable archive. +- [ ] Develop the remaining outline and reconstruct knowledge documents using the method above. +- [ ] Review recovery coverage and remove active dependencies on the archive. +- [ ] Delete `archive/knowledge` once the useful material has been recovered; update documentation for the final state. + +## Proposed knowledge-node outline, October 5 + +Status: discussion draft, compiled at the project lead's request for Claude review. Foundation Context and Data Structures follow the owner's supplied outline. SDKs combine the assistant's proposed outline with the owner's subsequent additions. The proposed boundaries between knowledge nodes are assistant suggestions. They do not define a clinical schema or assign file paths. The owner has clarified that Use Cases describe envisioned uses and Sample Applications describe actual implementations, including demos and prototypes. The former Applications grouping is now the working Use Cases outline; the Sample Applications inventory remains to be developed. + +### Organizing contract + +- Organize by concepts and their relationships, reflecting current thinking. Source artifacts and historical development do not determine the conceptual map. Source dates, pinned revisions, and qualifications still support individual claims. +- Distinguish this repository's OKF knowledge nodes from the clinical graph objects those nodes describe. For example, an OKF node about Measurement explains that clinical definition type; it does not imply one OKF file per clinical measurement definition. +- Each proposed substantive node has one primary home and links to related nodes elsewhere. Section introductions are navigation and synthesis knowledge documents. A public page can distill several knowledge nodes; a knowledge node is not automatically a public page. +- The carefully crafted presentation pages are valuable and are being developed separately by the owner and another agent. Preserve their content and recorded agreements. This outline does not authorize replacing, moving, or editing them. +- Keep shared definitions distinct from patient-specific assertions, and link the two. Keep descriptive characterization distinct from assessment. Preserve proposals, uncertainty, and differing representations within their conceptual knowledge documents. +- Use the owner's current working abbreviation **FDe** for assertion subjects/finding definitions, including diagnoses and other subjects. The exact membership of this umbrella, particularly assessment definitions, is an open modeling question. +- The owner now explicitly describes object references as pointers to definitions, anatomy, and attribute subjects. This supersedes the older prohibition on that word for this discussion; the graphs remain interconnected. + +### Foundation Context + +The hierarchy below preserves the owner's outline. Indentation supplies the context for each topic. Topics identify the knowledge to capture; their final division into individual OKF files remains under discussion. + +* Introduction: Interconnected Knowledge + - Shared definitions and relationships, separate from assertions about particular findings in a patient's imaging history. + - How Foundation Context connects to those assertions. +* Assertion Subjects / Finding Definitions, including diagnoses and other subjects, abbreviated FDe + - Overview of the knowledge graph schema + - Taxonomy of assertion subjects: findings, diagnoses, assessments, and other subjects + - Distinctions between subject kinds and their definition types. + - Assessment definitions describe evaluation criteria; descriptive finding definitions describe what is being evaluated. + - Properties of FDes + - Names, synonyms, definitions, identifiers, external codes, and applicable metadata. + - Relationships between FDes + - Taxonomic, manifestation, causation, association, progression, component, and assessment relationships. + - Properties of the relationships themselves. + - Categorical attributes of FDes: Data Elements + - Categorical descriptors, their permitted values, and their connections to finding definitions. + - Measurements of FDes + - Kinds of quantities used to characterize findings, such as length or CT density, and how Measurement definitions connect to finding definitions. + - Examples of actual graphs + - Supported definition-graph examples illustrating the schema and relationships. + - Identify proposed example content separately from existing graph content. + - OIFM and ACR/RSNA CDE repositories and their relationship + - Collections of the same kind of definition content, with different review and release expectations. + - Shared-schema direction and cross-linked identities. + - Definition versioning and lifecycle + - A versioning scheme for managing content updates. + - Likely deprecation of older nodes with pointers to updated or replacing nodes. + - Pointers may refer to specific versions of definitions. + - The owner has established these concepts; the detailed scheme remains to be developed. + - Using LLMs to drive creation of the system + - Discovery of proposed definitions and associated information from report corpora. + - Enrichment, content-guideline checks, and human review. + - Connect to the SDK knowledge documents of reusable authoring capabilities. + - Proposed authoring tool: evolution of FindingModelForge + - Requirements for authoring and reviewing definitions and relationships. + - Current content-guideline ideas and their ongoing development by the community through content creation and review. + - Guidelines evolve with authoring experience; they are not a finished prerequisite for creating content. + - Connect to reusable SDK capabilities and the application knowledge document to be developed later. +* Anatomy + - Anatomic locations: curation and initial additions + - Containment and part-of relationships. + - Sex-specific anatomy, laterality, and other curated information. + - Incorporating curated anatomic locations into RadLex + - Integration of the project's curated locations and relationships. + - Proposed added metadata, including tissue type. + - Distinguish proposed additions from implemented RadLex content. + - Relationships to other ontologies + - SNOMED CT, TA2, FMA, and LOINC. + - Explain the meaning and scope of mappings without assuming equivalence. +* Exam types + - RadLex/LOINC Radiology Playbook + - Exam definitions and existing building blocks. + - Exam families + - Preferred high-level examination families and their organization. + - Review evidence from the owner's `../exam-types` project. +* Overall integration + - FDe definitions and anatomic scope + - Which anatomy a definition applies to, distinct from the actual location of a particular finding. + - FDe definitions and exam types + - Connections between definitions, modalities, and examination types. + - Anatomic locations and exam types + - Anatomy always, usually, or possibly included in an examination. + - Connect to the proposed application for smarter selection of relevant priors. + - FDes associated with non-imaging diagnoses and procedures + - Relationships to clinical diagnoses and procedures, including surgeries. + - External codes and qualified associations that help retrieve relevant patient history. + - A likely association in Foundation Context does not establish a fact about a particular patient. + +### Data Structures + +This hierarchy preserves the owner's four main groups. The overall FHIR conversion knowledge document is added separately, following the owner's subsequent clarification. Specific representations remain nested under the structures they represent. + +* Introduction: Developing imaging data structures + - Explain the proposed structures and their connections to Foundation Context. + - Ongoing community definition of these structures is an important thread of work. + - Implementers contribute requirements and refinements grounded in real use of these ideas. + - Distinguish current proposals, implemented representations, and unresolved design questions. +* Assertions / Observations + - Overview: an application-layer data structure + - Assertions about particular findings and other defined subjects. + - Their connection to shared Foundation Context definitions. + - What's contained + - Pointers to FDe definitions and to anatomic locations. + - Distinguish the identity of a definition from the identity of a particular assertion. + - Pointers may identify specific definition versions; connect to Foundation content versioning. + - Attributes + - Pointers to sub-assertions with DataElement and Measurement subjects. + - Values, units, presence, and uncertainty. + - A measured value describes a particular finding, such as a pulmonary nodule's diameter of 8 mm. + - The exact sub-assertion representation remains to be reviewed. + - Sub-components + - Component assertions and their identities, values, and relationships to parent assertions. + - Connections to component constraints in the corresponding definitions. + - FHIR representation + - Observation objects, components, and connections. + - Representation of attributes, sub-assertions, measured values, and provenance. + - Connect to the overall bidirectional conversion philosophy and the SDK conversion capability. + - Provenance and review + - AI-generated assertions. + - Technologist- or modality-generated assertions, including possible DICOM SR sources. + - LLM extraction from report text. + - LLM generation from radiologist interaction. + - Radiologist review of AI-based tools, supporting evidence, and corrections. +* Exam Finding List / Imaging Diagnostic Report + - Exam information + - Pointer to exam type information. + - Possible protocol information. + - Other exam-performance information and pointers to DICOM data. + - Collection of Assertions / Observations + - Organization into Findings and Impression sections. + - Relationships between assertions + - Findings and diagnoses. + - Sub-components and associations. + - Connections from findings to impression assertions. + - Distinguish relationships asserted about particular findings from general relationships between Foundation definitions. + - FHIR Imaging Diagnostic Report representation + - Exam information, assertion collections, sections, and connections. + - Clarify the relationship between the application structure and the external representation rather than assuming identical objects. + - Connect to the overall conversion philosophy and the SDK conversion capability. +* Imaging Problem List / Patient Imaging History + - Finding-based organization + - Connections to exam-level assertions and their evidence. + - Basic applications and use as context information + - Longitudinal tracking + - Finding identity across examinations and association of observations. + - Change, derived status, and succession. + - Keep unresolved identity and status choices visible. + - Describe how the imaging history represents these connections; link to the separate SDK node on making the connections. + - FHIR representation + - Proposed representation of finding identity and longitudinal histories. + - Exact resources and profiles remain open. + - Connect to the overall conversion philosophy and the SDK conversion capability. +* Imaging Persona + - Patient facts relevant to imaging, organized in a standard way + - Incorporates the Imaging Problem List and integrates wider patient history. + - Information to include and how to represent it in the Imaging Persona + - A knowledge node identifying the pieces of imaging-relevant patient information to retrieve and their proposed representations and relationships. + - Developing these patient concepts and data structures is the first step toward defining the Imaging Persona. + - The work is currently at a high, imprecise level. Record candidate information and open representation questions without presenting a settled schema. + - Broader reconciliation of clinical information is a clinical-informatics research question. Use the [focused research reference](../references/clinical-information-reconciliation.md) to inform the discussion; its suggested implications do not settle the representation. + - Exam-planning information + - MRI safety, allergies, body habitus, and other relevant facts. + - Clinical history extraction + - Imaging-relevant oncology history, treatments, operative notes, and other clinical records. + - Connect to the SDK capability for LLM-based clinical-history extraction. + - Pathology results and diagnoses + - Connections to Imaging Problem List elements. + - Other non-imaging diagnoses possibly associated with imaging findings + - Foundation associations help identify relevant documented history. + - Retain the distinction between likely associations and actual patient facts. + - Availability via FHIRcast + - Proposed context integration. + - Review what FHIRcast supplies and how the underlying data is retrieved. +* Bidirectional FHIR conversion: overall philosophy + - Relationship between application structures and FHIR representations. + - Conversion in both directions, preserving meaning, identities, relationships, evidence, and provenance. + - Handling information without a direct equivalent and making conversion losses explicit. + - Connect to the specific Observation, Exam Finding List, and Imaging Problem List representation knowledge documents above. + +### SDKs + +This hierarchy follows the collaborative SDK outline. It describes reusable capabilities and connects them to the concepts they operate on. Named packages appear with the capabilities they implement. The proposed grouping does not establish which capabilities are already implemented. + +SDKs describe underlying reusable library code. Use Cases describe envisioned uses of that code; Sample Applications describe software actually implemented, including simple demos and prototypes. + +* Introduction: Using OIDM in applications + - Access shared knowledge and manipulate patient data structures. + - Support authoring and maintenance of Foundation Context. + - Identify available implementations and proposed capabilities. +* Accessing Foundation Context + - Finding-definition search and identity resolution + - Retrieve definitions by identifier; search names, synonyms, and properties. + - Resolve collection identities, cross-links, and definition versions. + - Access specific definition versions and follow deprecation or replacement pointers under the scheme to be developed. + - Traversing the definition graph + - Relationships between FDes. + - Data Elements, Measurements, and assessment definitions. + - Anatomy access + - Lookup, containment, laterality, and external ontology mappings. + - Exam access + - Exam-family lookup and anatomy-coverage queries. + - External terminology mappings + - Retrieve mappings and their qualifications and provenance. +* Authoring and maintaining Foundation Context + - Definition and relationship authoring + - Create, edit, and validate definitions and relationships. + - Support human review and maintain cross-links between OIFM and ACR/RSNA CDE identities. + - LLM-assisted generation and enrichment + - LLM-assisted content-guideline checking + - Check scope, naming, attributes, and relationships against the community's evolving content guidelines. + - FDe discovery from radiology report corpora + - Extract proposed definitions and associated attributes, measurements, anatomy, and relationships. + - Identify coverage gaps and candidates for human review. + - External ontology authoring tools + - Search ontologies and inspect concepts and relationships. + - Propose and validate mappings while retaining qualifications and provenance. + - Human review, versioning, and provenance + - Support the proposed content-update, deprecation, and replacement scheme without assuming its detailed design is settled. +* LLM agents: From report text to imaging data + - Automated extraction of assertions, attributes, measurements, anatomy, and relationships. + - Organization of Findings and Impression content. + - Resolution of extracted concepts against Foundation Context. + - Supporting report text and generation provenance. + - Automated validation, correction, and radiologist review. + - Concrete implementation to examine: the imaging-problem-list extraction work. +* Longitudinal linking of imaging findings + - Proposed dedicated SDK knowledge node for identifying observations of the same imaging finding across a patient's imaging history and making the appropriate connections. + - Capture the project's preliminary work on this specific problem, with source evidence and remaining questions. + - Describe the reusable library capability for establishing and maintaining those connections. + - Link to the Imaging Problem List's representation of finding histories. + - Keep this specific linking problem distinct from the broader reconciliation of diagnoses, procedures, and other clinical information for Imaging Persona. +* Working with patient data structures + - Observations and Exam Finding Lists + - Create, inspect, validate, and update application objects. + - Manage attributes, sub-assertions, components, and relationships. + - Associate assertions with exam information. + - Preserve identities, evidence, and review decisions. + - Imaging Problem Lists + - Assemble and query finding-based histories. + - Use the longitudinal-linking capability and support finding identity, change, and status. + - Retrieve underlying observations and evidence. + - Imaging Persona + - Assemble and query the standard organization of patient facts relevant to imaging. + - Incorporate imaging history, safety information, diagnoses, treatments, procedures, and pathology. + - Extract and normalize relevant clinical facts using LLM agents. + - Clinical notes, operative reports, and pathology reports. + - Retrieve relevant patient facts using Foundation Context associations. + - Connections between FDes, clinical diagnoses, procedures, and external codes. + - The owner's example is cholecystectomy history and related procedure and diagnosis codes. + - The owner named LOINC and CPT for the procedure example, and ICD, LOINC, and SNOMED CT for diagnoses. These are code-system leads to investigate, not verified code assignments. + - Verify the appropriate code systems rather than inventing codes or treating mappings as equivalence. + - Preserve evidence and distinguish documented facts from likely associations. + - Whether to identify missing information to seek remains unresolved. +* Bidirectional FHIR conversion capabilities + - Convert Observations to FHIR and back. + - Convert Exam Finding Lists to FHIR and back. + - Convert Imaging Problem Lists to FHIR and back. + - Use the overall philosophy and the specific representation knowledge documents under Data Structures. + - Preserve identities, relationships, measured values, and provenance; expose unsupported information and losses. + +### Use Cases + +This working outline describes envisioned uses, combining the assistant's proposed grouping with the owner's additions. Implemented software belongs under Sample Applications and can illustrate these uses. The descriptions of green findings and object-oriented reporting capture the owner's current guidance; supporting board material remains to be checked. The outcome-tracking subdivisions are assistant proposals responding to the owner's request. The detailed boundaries and individual use-case documents remain to be developed. + +* Authoring and browsing Foundation Context + - FindingModelForge and its proposed evolution. + - Finding-definition catalog and graph exploration. + - Anatomy browsing site. + - Explore anatomic locations, their relationships, and associated information. + - Exam browsing interface. + - Explore the project's view of exams, including exam families and their linked anatomy. +* Turning reports into structured imaging information + - Report extraction, coding, and persistence application. + - Radiologist review and correction interface. + - Extraction evaluation tools. +* Viewing and using imaging history + - Imaging Problem List viewers. + - Anatomy-based navigation through findings and their histories. + - Proposed smarter selection of relevant prior examinations using exam families and linked anatomy. + - Proposed Imaging Persona interface incorporating relevant clinical history. +* Reporting assistance + - Green findings. + - Start report drafts with the subset of established findings that will always carry forward from prior reports. + - This is not copying the entire last report. Identifying which findings belong in that subset is a substantive question. + - The owner identifies an implemented vendor prototype demonstrated at RSNA; record that example under Sample Applications. + - Locate and review the older Excalidraw board identified by the owner before adding source-derived detail. + - Object-oriented reporting. + - Create and work with structured finding objects during interpretation and reporting. + - Include creation of those objects from within the PACS image-viewing interface. + - Connect to the assertion structures and SDK capabilities used to create and manipulate them. + - Context-aware reporting tools and plugins. + - Rendering structured assertions as report text. +* Outcome tracking + - Finding-to-outcome tracking. + - Connect findings to later imaging, biopsy, pathology, diagnoses, and treatment. + - Recommendation and follow-up tracking. + - Track recommended actions, completion, overdue follow-up, and unresolved findings. + - Radiology-pathology correlation. + - Connect the imaged finding, sampled lesion, and pathology result. + - Feedback to radiologists. + - Show subsequent outcomes for findings and assessments they reported. + - Outcome dashboards and audits. + - Diagnostic yield, assessment performance, and outcomes across a practice. + - Monitoring AI-generated findings against subsequent evidence. + - Connect these applications to longitudinal finding identity, Imaging Persona's clinical information, and Foundation definitions. +* Demonstrating exchange between systems + - Existing structured-data demonstrations. + - Proposed workflows connecting imaging, AI, reporting, and clinical systems. + +Each use-case knowledge document explains the envisioned use and its connections to Foundation Context, Data Structures, SDKs, and any implemented Sample Applications. + +### Sample Applications + +Actual implementation is the dividing line between Use Cases and Sample Applications. Simple demonstrations and prototypes count as Sample Applications. Each example describes what was implemented, its status, and the use cases and SDK capabilities it illustrates. + +* Green findings vendor prototype + - The owner reports that a reporting software vendor implemented the feature as a prototype and demonstrated it at RSNA. + - Connect the implemented example to the reporting-assistance use case above. + - The vendor, demonstration date, and implementation details remain to be documented from appropriate evidence. + +The rest of the Sample Applications inventory remains to be developed from actual implementations, including the imaging-problem-list extraction work already identified above. Do not label proposed browsers or future tools as implemented applications. + +### Connections to review across the first three sections + +These are relationships between OKF knowledge documents, expressed in prose and links. They do not prescribe clinical edge names. + +- **Defines / references:** FDe, anatomy, exam, DataElement, and Measurement knowledge documents connect to the patient structures that reference them. +- **Describes / assesses:** finding characterization connects to assessment definitions and patient assessment assertions without merging them. +- **General association / patient evidence:** Foundation associations guide relevant-history access; only patient evidence establishes a fact about that patient. +- **Structure / capability / implementation:** data structures connect to the SDK capabilities that manipulate them and, later, named implementations and applications. +- **Guideline / check / human decision:** content guidelines connect to LLM checking capabilities and authoring/review records. +- **Mapping / conversion:** each FHIR mapping knowledge document connects to its bidirectional conversion capability and the overall conversion philosophy. + +### Open boundaries and recorded review + +Claude reviewed the initial first-three-section outline. The [full review](../references/knowledge-outline-claude-review.md) is saved separately, including proposed missing nodes and merges. It predates the owner's corrections and Applications additions; its technical claims need source verification and its proposals are not approved changes. The questions submitted for that review were: + +1. Does FDe need separate knowledge documents for subject taxonomy, assessment definitions, and assessment assertions, and are the distinctions clear? +2. Are shared properties, identifiers/versions, scope, relationship properties, and terminology mapping sufficiently explicit? +3. Does the proposed DataElement/Measurement sub-assertion structure preserve the owner's wording without pretending it is a settled implementation? +4. Are EFL and Imaging Diagnostic Report related clearly without assuming they are identical? +5. Does Imaging Persona cover relevant wider history and Foundation-guided access, with likely associations distinct from patient facts? +6. Is the FHIR philosophy/mapping/capability separation useful, and what fidelity or identity questions need recording? +7. Are any candidate nodes merely headings, duplicates, or too broad, and which substantive concepts are missing? + +Review is advisory. Incorporating a review is separate from owner approval. Do not reorganize content or edit the crafted pages as part of this review. + +### Work status + +- [x] Record the owner's first two section outlines and the SDK discussion in this existing plan. +- [x] Make proposed OKF knowledge nodes and cross-section relationships explicit. +- [x] Request and collect Claude review in Herdr pane `wA:p5`; recommendations remain under discussion. +- [ ] Discuss review findings and unresolved boundaries with the owner, one decision at a time. +- [x] Record the proposed Applications branch, including the owner's anatomy/exam browsers, green findings, PACS object creation, and outcome-tracking additions. +- [x] Record the owner's Use Cases versus Sample Applications distinction, the Imaging Persona information-and-representation node, and ongoing community development of data structures and content guidelines. +- [x] Record the owner's preliminary content-versioning concept and proposed dedicated SDK node for longitudinal finding linking. +- [x] Collect current clinical-informatics research on broader clinical-information reconciliation for Imaging Persona; retain primary sources and distinguish research findings from project decisions. +- [ ] Discuss relevant reconciliation findings as the Imaging Persona information and representation work develops. +- [ ] Refine Use Cases and inventory implemented Sample Applications as ongoing content work. Completing that inventory is not a prerequisite for the agreed first node or the remaining execution-plan review. + +The five sections and the owner's subsequent additions are recorded above with their hierarchy. Use Cases has a working outline; Sample Applications currently names the green findings prototype and awaits a fuller implementation inventory. Both remain unfinished. The owner's original wording and Claude's initial full review are preserved in separate references. The archive move was authorized and completed under the reconstruction method; substantive node boundaries and remaining review decisions are still under discussion. No publication changes or commits are authorized by this outline. + +## Proposed execution plan for the new knowledge tree, October 6 + +Status: written at the owner's request; review the aspects below one at a time before executing this sequence. The owner has agreed to the Foundation schema overview as the first node and approved the tree structure, document-boundary approach, and evidence and attribution approach. The remaining implementation choices are proposals awaiting discussion. The existing conceptual outline and fresh-writing method remain the foundation. No new knowledge documents or directories have been created by this planning step. + +The goal is a readable, source-supported, interconnected OKF knowledge tree reflecting current thinking. It must preserve the owner's conceptual hierarchy while making proposals and unfinished design work explicit. The former deck-first pilot and edit-in-place migration remain withdrawn. Source storage, verification, and recovery are complete; use those tools when needed rather than rebuilding them. Keep all work under `site/` outside this plan, and preserve concurrent working-tree changes. Commits require separate explicit permission. + +### First step: save and review this plan + +- [x] Write the execution proposal into this existing planning record. +- [x] Agree on the first node with the owner: the Foundation knowledge graph schema overview under Finding Definitions. +- [ ] Review the subsequent expansion sequence with the owner. +- [x] Review the tree structure and knowledge-document boundaries; approved by the owner. +- [x] Review source selection and claim support; approved by the owner. +- [ ] Review the drafting, archive-checking, and linking workflow. +- [ ] Review how unfinished design and implementation status are represented. +- [ ] Review the owner-review and validation process. +- [ ] Review coverage completion and archive deletion. + +Present only the current aspect in conversation, with a recommendation and one focused question. Record each answer here, including changes to later aspects. Do not ask the owner to reconfirm recorded decisions. Plan agreement authorizes the agreed execution; substantive questions arising in drafts still return to the owner one at a time. + +### Starting point and sequence + +Agreed first document: the overview of the Foundation knowledge graph schema, under Assertion Subjects / Finding Definitions. The owner accepted this starting point in the plan review. Explain the kinds of shared definitions and how their relationships fit together. Keep patient assertions identifiable as examples of how definitions are used. Leave detailed subject taxonomy, properties, relationships, Data Elements, and Measurements to their connected documents. + +Use this first document to establish an acceptable level of detail, source attribution, and graph connections. Revise it after the related documents exist. Do not make completing the application inventory or settling every schema question a prerequisite for starting. + +Proposed subsequent sequence: + +1. Expand Foundation Context through FDe subject kinds, properties, relationships, Data Elements, and Measurements. Then cover anatomy, exam types, their integration, the repositories, versioning, and authoring. Keep the owner's nested outline visible. Develop the Foundation introduction as these concepts become concrete. +2. Build Data Structures around assertions, Exam Finding Lists, and Imaging Problem Lists. Include the overall FHIR philosophy and structure-specific mappings. Capture Imaging Persona's information-selection and representation work as an open development thread, informed by clinical-reconciliation research. Explain ongoing community development in the section introduction. +3. Build SDK documents for reusable library capabilities, including Foundation access and authoring, LLM extraction, longitudinal finding linking, patient-structure manipulation, and bidirectional FHIR conversion. Link to the structures and definitions they operate on. +4. Refine Use Cases and inventory actual Sample Applications. Verify what each implementation does and its current status, including simple demos and prototypes. Keep the green findings vendor prototype as an owner-reported implementation until further details have source support. Connect examples to the uses they demonstrate. + +This is a dependency-based default, not a requirement to finish an entire section before connecting it to another. Add the related node needed to explain a concept when useful. Adjust the sequence with the owner when evidence or a substantive modeling question warrants it. + +### Tree structure and knowledge-document boundaries + +Agreed primary directories are `knowledge/foundation-context/`, `knowledge/data-structures/`, `knowledge/sdks/`, `knowledge/use-cases/`, and `knowledge/sample-applications/`. Checked processed references live in `knowledge/references/`. The owner approved preserving meaningful nested groups, using navigation indexes for grouping topics, and creating individual substantive concept documents as their content develops. + +Preserve meaningful parent topics in subdirectories, such as finding definitions, anatomy, exam types, and Imaging Persona. Create directories and their indexes as substantive content is added. Do not fill the tree with empty concept documents. + +One OKF document explains one substantive concept. An outline bullet can be a document, a subsection, or a navigation grouping; the source-supported content determines that boundary. An OKF node describing Measurement is not an individual clinical Measurement definition. Use plain-English titles and descriptive filenames, with no coded node references. + +Before drafting a node, state its title, outline location, purpose, scope, and intended connections in this plan. Keep the current node's entry here rather than creating another planning document. SDK nodes explain reusable library functionality; Sample Applications nodes explain particular implemented software. Cross-link shared concepts instead of repeating their explanation in every section. + +The path for the agreed first node is `knowledge/foundation-context/finding-definitions/schema-overview.md`. Individual document scopes will be refined during drafting under the agreed approach. Source selection and claim support have also been approved; the drafting, archive-checking, and linking workflow is the current review aspect. + +### Source selection and claim support + +Approved by the owner: use current owner guidance for intended direction, identify proposals, support implementation claims with checked sources at recorded revisions, preserve open questions and conflicting representations, and keep detailed source analysis in references with concise citations in concept prose. An idea can be included before implementation when its status is clear. + +For each node, first read the relevant recorded owner guidance. Then identify original sources and checked processed references by relevance to the concept. Source artifacts, repository names, and historical pages do not determine the tree. + +Pin repository evidence to committed revisions and specific files or sections. Recheck current implementation claims against current committed source. Use official standards documentation and primary research when needed; retain versions, dates, and limitations. Verify source availability before claiming that an implementation or technical detail has been examined. + +Create or revise a processed reference only when it adds useful source detail. Keep source quotations and technical specifics there when they would interrupt the conceptual document. Cite substantive claims through reference links and matching source footnotes. Distinguish owner statements, implementation evidence, agent interpretation, and proposed design. Dates qualify the evidence; historical development does not become the organizing principle. + +Follow the existing source-handling rules. Never inspect reviewer correspondence. Keep originals and private material outside Git. Paraphrase manuscripts under review, and confirm board publication status before reproducing originals. An unavailable source leaves the associated claim unresolved; use another supported source or ask for the missing information without inventing it. + +### Drafting, archive checking, and graph connections + +Review checkpoint recorded October 14: this aspect is not yet approved. The last question to the owner was, "Is the end of that cycle the right point for your review?" It refers to completing the scope, evidence, fresh draft, archive check, graph connections, documentation updates, and validation before presenting the node. Await that answer before advancing the plan review. The owner has already approved writing fresh before consulting the archive; do not ask to reapprove that principle. + +Repeat this workflow for one node at a time: + +1. Define the node's scope and place in the outline; identify the relevant evidence. +2. Write the concept from an empty file using that evidence. Explain the idea, meaningful relationships, supported examples, and unresolved questions. Use headings that fit the concept rather than forcing every document into the same prose template. +3. After the fresh draft exists, inspect relevant archived material for worthwhile omissions and source leads. Verify each proposed addition against its evidence. Do not reuse archived wording or adopt its file boundaries. +4. Add meaningful links to existing related nodes and update those nodes when the reciprocal connection helps understanding. Keep links to unwritten topics as plain-English future work until their target files exist. +5. Update directory indexes, source references, the bundle log, and this plan for the current batch. Check the draft and bring its substantive review question to the owner. + +For each node's archive check, record briefly in this plan what useful material was recovered, what remains unresolved, and what was excluded when the reason matters. This supports the final coverage review without creating another knowledgebase or copying discarded prose. + +### Unfinished design and implementation status + +Treat unresolved concepts as useful knowledge when their intent and open questions can be stated clearly. Do not manufacture complete schemas, APIs, clinical truth-resolution rules, or community consensus. + +In particular, keep Foundation versioning and replacement links, assessment representation, Imaging Persona's patient concepts, content guidelines, and exact FHIR mappings at their actual level of development. Separate the project's preliminary longitudinal finding-linking work from broader clinical reconciliation. Explain continuing community refinement grounded in implementation experience. + +Use Cases describe envisioned uses. Sample Applications require an actual implementation, including demos and prototypes. Identify what exists, what is proposed, and what has been verified without equating an old prototype with a current supported product. + +### Owner review and validation + +The assistant researches, drafts, checks, and proposes resolutions. The owner reviews the substantive content and decisions. Present one bounded issue or document portion at a time, including a recommendation and the evidence necessary to assess it. Provide a file link; do not paste a whole draft into the conversation. Explicit corrections to an authorized draft are applied and recorded promptly. + +All new agent-written knowledge documents start with `status: draft`. Follow [knowledge-authoring instructions](../knowledge-authoring.md) for `type`, title, description, generation metadata, sources, and footnotes. Human verification records an actual review of the document, not agreement with the outline or an agent's review. Do not apply verification metadata merely because one subsection was discussed. + +Run `task check` after each content batch. Also inspect local links, directory-index descriptions, claim attribution, and terminology. Conformance checks establish document structure, not factual support. Run checks appropriate to any changed tooling; this plan does not require site-generation or deployment work. Fix check failures and maintain the relevant documentation in the same batch. + +Update this plan and `knowledge/log.md` as nodes are added or changed. Update `DEV_LOG.md` for completed work and `CHANGELOG.md` only for changes relevant to outside readers. Keep the handoff and authoring instructions aligned with the actual state. A batch is ready for review when it has readable content, working connections, source support, explicit unresolved questions, and passing checks. + +### Coverage completion and archive deletion + +Review coverage against the owner's outline and later recorded guidance. Use the historical idea inventory only as an omission check, after fresh concepts exist. Coverage means relevant supported ideas have a clear home and useful connections; it is not a target file count or a requirement to settle every design question. + +Before deleting the temporary archive, confirm that useful material and source leads have been recovered or deliberately excluded, recorded owner guidance survives outside it, and no active documents depend on it. Preserve source originals and recovery tooling. Then delete `archive/knowledge` under the owner's existing instruction and update the plan, archive instructions, handoff, references, and relevant logs. + +Finish with a documentation review that reflects the actual reconstructed tree, remaining design questions, and archive state. Mark completed reconstruction work accurately; keep continuing community design work visible. No commit is part of this sequence without the owner's explicit permission for that commit. + +## Presentation pages and figures + +Everything outward-facing (the agreed pages, their figures, the project lead's page guidance, and the site build) lives in `site/`, with its own roadmap, current task, and history (`site/ROADMAP.md`, `site/CURRENT_TASK.md`, `site/HISTORY.md`). It is a separate track from the knowledge graph and outside its scope. The page map and page and figure status are in `site/ROADMAP.md`; the project lead's page guidance, verbatim, in `site/guidance.md`. + +## Content and source rules + +- Distinguish stated goals, proposals, implemented examples, and human approval. Do not invent motivation, benefits, consensus, or resolutions to disagreements. Scope absence claims to reviewed sources. +- Repository code, plans, prompts, skills, and design records are eligible sources. Preserve their actual status, date, pinned commit, and explicit provenance labels such as OWNER or CLAUDE DEFAULT. +- No OIDM source is a formal project specification. Use current terms: Foundation Context, Patient Context, and the owner's working FDe terminology. Observation Type remains unadopted. The owner uses pointers for application-object references; describe the broader graphs as interconnected. +- OIFMs and CDEs are the same kind of content in different collections, intended to share the new graph schema. Migration is direction, not a completed implementation. SDKs support authoring and maintenance as well as use. +- Organizations only. Keep the reporting vendor and the organizations behind the 47/31 collections generalized. Paraphrase manuscripts under review; never quote them or read/summarize reviewer correspondence. Internal boards contribute schema ideas, without people, live share links, meeting histories, or vendor logistics. +- Read CDE sources only from committed files at a pinned revision. Decisions S53–S67 were recorded as uncommitted; verify their state before using them, without committing upstream work as part of this plan. +- Figures use Excalidraw and the [diagram conventions](../../tools/diagrams/README.md). Inspect rendered output before review. Keep exact owner-accepted versions distinct from candidates. + +## Open decisions and deferred work + +Remaining graph work includes developing Use Cases, deciding substantive document boundaries, and addressing relevant review findings one decision at a time. Page wording, figure acceptance, and website release decisions belong to the separate site workstream in `site/ROADMAP.md`. + +Source-manifest handling is documented in the completed storage work and source instructions. It does not determine the organizing principle for recovered content. + +Defer search-engine adoption, OKF toolkit replacement, broad theme work, comments/feedback tools, and a typed-edge website graph. Preserve the completed tooling reports. Upstream issue drafts and source-repository link-back changes remain separate work for the project lead's review. CI credentials and production domain configuration are deployment prerequisites, not blockers to local organization. + +## Maintaining this plan + +Maintain the agreed fresh-writing method and current-state table here as reconstruction proceeds. Keep the conceptual outline and remaining decisions current, and retain the reset and completed-work record. Keep source evidence in references, review artifacts in their appropriate tracked/private homes, and historical implementation detail in `DEV_LOG.md`. Update documentation in the same batch as the files it describes. Remove obsolete instructions rather than appending another contradictory section. + +Before closing a batch, validate links, indexes, provenance, and any changed build behavior. Review `README.md`, `CONTRIBUTING.md`, `docs/HANDOFF.md`, relevant reference/authoring instructions, and both logs for the final state. Commits require the project lead's explicit permission each time. diff --git a/docs/references/clinical-information-reconciliation.md b/docs/references/clinical-information-reconciliation.md new file mode 100644 index 0000000..cfc3f33 --- /dev/null +++ b/docs/references/clinical-information-reconciliation.md @@ -0,0 +1,61 @@ +# Clinical-information reconciliation for Imaging Persona + +Researched October 6, 2026, at the owner's request. This is a focused search of primary sources, not a systematic review. It records research findings and proposed implications for the knowledge outline, not an agreed Imaging Persona schema. + +The owner distinguishes the broader clinical-informatics problem from the project's preliminary work identifying observations of the same imaging finding across a patient's imaging history. The latter has a proposed dedicated SDK node in [the implementation outline](../plans/implementation.md). + +## Problems to distinguish + +Our interpretation of the sources is that three questions need separate treatment: + +- Do records describe the same clinical event or condition? +- Does a difference represent clinical change, differing specificity, disagreement, or a correction? +- Which information should an application use now, and on what evidence or review decision? + +The sources below offer representation elements, reconciliation considerations, and experimental methods. They do not establish an automatic solution applicable to all clinical information. + +## Primary sources + +### IHE reconciliation profile + +[Reconciliation of Clinical Content and Care Providers, revision 3.2](https://www.ihe.net/uploadedFiles/Documents/PCC/IHE_PCC_Suppl_RECON_Rev3.2_TI_2016-11-11.pdf), November 11, 2016. The [current IHE PCC listing](https://profiles.ihe.net/PCC/index.html) still identifies this revision under Trial Implementation, rather than Final Text. + +Section X.4.2 considers original identifiers, coded concepts, timing, anatomy, and information sources when reconciling records. Timing can distinguish recurrent events while repeated mentions of a chronic condition or past surgery may describe one instance. Differences in anatomic specificity can also describe the same condition. The profile considers overlapping, conflicting, and superseded information and records who reconciled information, when, and which sources were considered. + +Its examples of FHIR structures reflect the profile's older FHIR context. They are not current R5 mappings to copy into this project. + +### FHIR representation elements + +[FHIR R5 Condition](https://hl7.org/fhir/R5/condition.html), published March 26, 2023, distinguishes clinical status from verification status. Resolution of a disease, refutation of a diagnosis, and an erroneous entry have different meanings. Onset, abatement, recorded date, and evidence are useful elements to examine. This does not prescribe adjudication of conflicting records. + +[FHIR R5 Provenance](https://hl7.org/fhir/R5/provenance.html), published March 26, 2023, records activities and agents involved in producing or revising resources and the sources used. References can identify particular resource versions. Recording the origin of information supports investigation of its reliability; it does not itself determine clinical truth. + +These are version-specific references for research, not a decision that the project will use R5 exclusively. + +### Structured clinical-document consolidation + +[Hosseini and colleagues, 2017 author manuscript](https://scholarworks.indianapolis.iu.edu/server/api/core/bitstreams/a9045c3b-3cf5-40c9-80ae-5f76012f11a2/content), also indexed in [PubMed](https://pubmed.ncbi.nlm.nih.gov/28903073/). + +The study tested consolidation of problems, medications, and allergies from Continuity of Care Documents. Missing semantic codes and differences between coded and narrative information hindered matching. The authors distinguished conflict detection from the harder problem of conflict resolution. This is evidence from a particular consolidation system, not a general performance guarantee. + +### Temporal reasoning with LLMs + +[TIMER](https://www.nature.com/articles/s41746-025-01965-9), September 26, 2025, peer-reviewed research. + +The study examines LLM reasoning across longitudinal records and reports improvements with time-aware instruction tuning. Its findings include recency bias in the evaluated models. It motivates explicit evaluation of temporal interpretation, rather than assuming that an extracted statement has been assigned the correct clinical time. It does not establish a completed clinical-reconciliation method. + +### Narrative and structured-data consistency + +[Towards Error-Free EHRs: Reasoning-Intensive Consistency Verification Between Clinical Notes and Structured Tables in Electronic Health Records](https://arxiv.org/html/2605.26463v1), May 26, 2026, preprint describing EHR-Inspector. + +The method extracts entities and temporal context, searches structured tables, and checks narrative consistency. The analysis distinguishes conflicting values from missing supporting records, although its benchmark uses a binary consistency label. Its small note sample, preprocessed MIMIC data, and use of LLM-based evaluation limit conclusions about general clinical use. Its labels are not an Imaging Persona truth model. + +## Proposed implications for this project + +Keep broader reconciliation linked to Imaging Persona's node about information to retrieve and how to represent it. Keep the specific longitudinal finding-linking capability under SDKs, connected to the Imaging Problem List's representation of the resulting history. + +Questions to develop with the community include how to retain source statements and evidence, proposed links, corrections, disagreements, and reviewed conclusions. Missing documentation needs to remain distinguishable from an explicit negative statement. The representation of those distinctions is still open. + +Evaluate extraction, linking, temporal interpretation, and conflict handling separately. Successful extraction of a statement does not establish that its identity, timing, or relationship to conflicting information is correct. + +The distinctions and implications above are agent interpretations for discussion. The owner's decisions remain in [the owner-guidance reference](knowledge-organization-owner-guidance.md). diff --git a/docs/references/idea-coverage.md b/docs/references/idea-coverage.md new file mode 100644 index 0000000..1d93796 --- /dev/null +++ b/docs/references/idea-coverage.md @@ -0,0 +1,106 @@ +# Idea coverage and evidence + +Reference inventory, consolidated 2026-10-02 from the September 22 investigation. This is coverage evidence, not a task queue. Current work and presentation destinations live in the [implementation plan](../plans/implementation.md). + +The 87 ideas retain their `Sem-` and `Use-` IDs. The linked historical entries contain pinned repository evidence, related ideas, and qualifications. Their source dates and proposal status remain significant; they do not establish present-day implementation or project-wide agreement. + +Corrections from the completed source-fidelity reviews govern reuse: + +- Use-A5: derived status can be stored. Viewer v2 stores `viewerMetadata.statusByFindingId`; preserve each source's vocabulary. +- Use-B5: keep the webinar's families distinct from manuscript groupings. Quality surveillance and research are separate webinar families; breast imaging is a separate planned example. +- Use-B9: boards describe proposed workflows. Cite the corrected Sample Applications draft's public team evidence for demonstrations that actually occurred. +- Use-A20: the formal-model SDK is proposed. Do not call the extraction platform its precursor without supporting evidence. +- Use-A7 belongs primarily with Observation provenance; IPL corrections and merge/split behavior stay with the IPL account. Use-A13, A14, and A22 remain with IPL. +- Use-A17 and A26 belong with Exam Finding List; Sem-B8 and B9 belong with OIFM content; Sem-C6 and C7 describe the finding-model SDK's local practices. + +The historical inventories contain material unsuitable for direct publication, including quoted manuscript passages. Follow current paraphrase and sensitivity rules; use the corrected drafts and private review dispositions before reuse. Do not copy their prose wholesale into new references or pages. + +| ID | Idea | Explanatory home | Historical evidence | +|---|---|---|---| +| Use-C1 | OIDM's stated purpose | Introduction | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#c1-oidms-stated-purpose) | +| Use-C2 | The relationship among the parts: three layers, and the DOM analogy | Introduction | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#c2-the-relationship-among-the-parts-three-layers-and-the-dom-analogy) | +| Use-C3 | Structure first, standards after; and how agreement is supposed to happen | Introduction | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#c3-structure-first-standards-after-and-how-agreement-is-supposed-to-happen) | +| Use-C4 | Nothing is formally defined yet | Introduction | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#c4-nothing-is-formally-defined-yet) | +| Sem-F1 | Anatomic scope, not location | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#f1-anatomic-scope-not-location) | +| Sem-F4 | The finding model relationship registry | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#f4-the-finding-model-relationship-registry) | +| Sem-F5 | The relationship family and the derived differential | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#f5-the-relationship-family-and-the-derived-differential) | +| Sem-F6 | Measurement, method, and interpretation are three things | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#f6-measurement-method-and-interpretation-are-three-things) | +| Sem-A9 | Assessment schemes modeled apart from what they assess | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a9-assessment-schemes-modeled-apart-from-what-they-assess) | +| Sem-A10 | Associated findings versus components | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a10-associated-findings-versus-components) | +| Use-A15 | Foundation context: four per-finding dimensions that tell an agent what to expect | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a15-foundation-context-four-per-finding-dimensions-that-tell-an-agent-what-to-expect) | +| Sem-F7 | Finding models as the CDE workbench | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#f7-finding-models-as-the-cde-workbench) | +| Sem-F8 | Measuring coverage by running real reports against the vocabulary | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#f8-measuring-coverage-by-running-real-reports-against-the-vocabulary) | +| Sem-B2 | Corpus as a merge of uneven provenance streams | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b2-corpus-as-a-merge-of-uneven-provenance-streams) | +| Sem-B7 | Exam-oriented finding sub-taxonomies | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b7-exam-oriented-finding-sub-taxonomies) | +| Sem-B8 | Convert and merge, with a fixed direction | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b8-convert-and-merge-with-a-fixed-direction) | +| Sem-B9 | Content repository proposes changes upstream to the library | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b9-content-repository-proposes-changes-upstream-to-the-library) | +| Sem-B3 | Stub creation and iterative improvement | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b3-stub-creation-and-iterative-improvement) | +| Sem-B4 | Triage before create | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b4-triage-before-create) | +| Sem-B5 | Review in three tiers, with context isolation | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b5-review-in-three-tiers-with-context-isolation) | +| Sem-B6 | Prompts as loadable fragments, sized against measured degradation | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b6-prompts-as-loadable-fragments-sized-against-measured-degradation) | +| Sem-C4 | Focused assignment agents, advisory audit, and null as an answer | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#c4-focused-assignment-agents-advisory-audit-and-null-as-an-answer) | +| Sem-C5 | Human review is the only authority | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#c5-human-review-is-the-only-authority) | +| Sem-A1 | Finding model | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a1-finding-model) | +| Sem-A2 | What counts as a finding | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a2-what-counts-as-a-finding) | +| Sem-A3 | Specificity, splitting, and the right level of abstraction | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a3-specificity-splitting-and-the-right-level-of-abstraction) | +| Sem-A4 | Negative assertion as a first-class finding | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a4-negative-assertion-as-a-first-class-finding) | +| Sem-A5 | Description and diagnosis both count | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a5-description-and-diagnosis-both-count) | +| Sem-A6 | Presence and change from prior | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a6-presence-and-change-from-prior) | +| Sem-A7 | Synonyms as the matching surface | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a7-synonyms-as-the-matching-surface) | +| Sem-A8 | Naming conventions | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#a8-naming-conventions) | +| Sem-B1 | Identifiers that carry provenance | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#b1-identifiers-that-carry-provenance) | +| Sem-C1 | Lean published models versus the full metadata schema | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#c1-lean-published-models-versus-the-full-metadata-schema) | +| Sem-C2 | Each metadata field is defined by what it excludes | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#c2-each-metadata-field-is-defined-by-what-it-excludes) | +| Sem-C3 | Index codes must be exact | Foundation Context / Finding models and CDEs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#c3-index-codes-must-be-exact) | +| Sem-D1 | Spatial containment as a single-parent tree to the whole body | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d1-spatial-containment-as-a-single-parent-tree-to-the-whole-body) | +| Sem-D2 | Containment and part-of are different relations | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d2-containment-and-part-of-are-different-relations) | +| Sem-D3 | Laterality as explicit triads | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d3-laterality-as-explicit-triads) | +| Sem-D4 | Synthetic post-coordinated terms | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d4-synthetic-post-coordinated-terms) | +| Sem-D5 | Measuring what existing ontologies actually populate | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d5-measuring-what-existing-ontologies-actually-populate) | +| Sem-D6 | An overlay on RadLex, not a competitor | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d6-an-overlay-on-radlex-not-a-competitor) | +| Sem-D7 | The missing is-a and structure-type layer | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d7-the-missing-is-a-and-structure-type-layer) | +| Sem-D8 | Curation rules that make the codes usable | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d8-curation-rules-that-make-the-codes-usable) | +| Sem-D9 | A reference layer, not a labeling algorithm | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#d9-a-reference-layer-not-a-labeling-algorithm) | +| Sem-F3 | Contributing terms back, and the two-phase proposal loop | Foundation Context / Anatomic locations | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#f3-contributing-terms-back-and-the-two-phase-proposal-loop) | +| Sem-E1 | Preferred high-level exam entries over the Playbook | Foundation Context / Exam types | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#e1-preferred-high-level-exam-entries-over-the-playbook) | +| Sem-E2 | Exam-to-anatomy inclusion edges and the imaging region | Foundation Context / Exam types | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#e2-exam-to-anatomy-inclusion-edges-and-the-imaging-region) | +| Sem-F2 | Terminology roles, and search recall as the gate | Foundation Context / Standards the foundation layers over and cites | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#f2-terminology-roles-and-search-recall-as-the-gate) | +| Use-A1 | Observation as the atomic unit of an imaging result | Data Structures / Observation | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a1-observation-as-the-atomic-unit-of-an-imaging-result) | +| Use-A10 | Presence is binary; confidence and hedging are a separate axis | Data Structures / Observation | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a10-presence-is-binary-confidence-and-hedging-are-a-separate-axis) | +| Use-A11 | Pertinent negatives are first-class entries | Data Structures / Observation | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a11-pertinent-negatives-are-first-class-entries) | +| Use-A12 | Every finding carries a standardized anatomic location, assigned by explicit rules | Data Structures / Observation | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a12-every-finding-carries-a-standardized-anatomic-location-assigned-by-explicit-rules) | +| Use-A23 | Normality is recorded as an absent abnormality | Data Structures / Observation | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a23-normality-is-recorded-as-an-absent-abnormality) | +| Use-A24 | Coding aims at convergence, so generalizing is allowed and specializing is not | Data Structures / Observation | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a24-coding-aims-at-convergence-so-generalizing-is-allowed-and-specializing-is-not) | +| Use-A25 | Finding type, anatomic site, and presence are independent axes | Data Structures / Observation | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a25-finding-type-anatomic-site-and-presence-are-independent-axes) | +| Use-A2 | Two-level architecture: Exam Finding List, then Imaging Problem List | Data Structures / Exam Finding List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a2-two-level-architecture-exam-finding-list-then-imaging-problem-list) | +| Use-A3 | The faithful-translation constraint on the Exam Finding List | Data Structures / Exam Finding List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a3-the-faithful-translation-constraint-on-the-exam-finding-list) | +| Use-A9 | One finding may be described by several observations in one report, and by different codes across reports | Data Structures / Exam Finding List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a9-one-finding-may-be-described-by-several-observations-in-one-report-and-by-different-codes-across-reports) | +| Use-A17 | Report sections beyond Findings are unmodeled | Data Structures / Exam Finding List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a17-report-sections-beyond-findings-are-unmodeled) | +| Use-A26 | A named taxonomy of extraction failures, checked by an independent reviewer | Data Structures / Exam Finding List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a26-a-named-taxonomy-of-extraction-failures-checked-by-an-independent-reviewer) | +| Use-A4 | Entry identity is the finding code plus the anatomic site | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a4-entry-identity-is-the-finding-code-plus-the-anatomic-site) | +| Use-A5 | Derived status and source-specific status vocabularies | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a5-status-is-derived-from-the-observation-history-never-stored--and-three-vocabularies-disagree) | +| Use-A6 | The succession link | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a6-the-succession-link) | +| Use-A7 | Per-observation provenance and review status, and corrections as provenance operations | Data Structures / Observation; IPL corrections | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a7-per-observation-provenance-and-review-status-and-corrections-as-provenance-operations) | +| Use-A8 | Entity resolution is the open problem; graduated-confidence linking is the proposal | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a8-entity-resolution-is-the-open-problem-graduated-confidence-linking-is-the-proposal) | +| Use-A13 | Anatomic-compatibility reconciliation is unresolved, and the sample data shows the cost | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a13-anatomic-compatibility-reconciliation-is-unresolved-and-the-sample-data-shows-the-cost) | +| Use-A14 | Permanent findings, and the need to rank entries before display | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a14-permanent-findings-and-the-need-to-rank-entries-before-display) | +| Use-A18 | Recommendation structure, and a live-versus-closed axis on IPL entries | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a18-recommendation-structure-and-a-live-versus-closed-axis-on-ipl-entries) | +| Use-A19 | A finding as a persistent tracked entity with a durable identifier | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a19-a-finding-as-a-persistent-tracked-entity-with-a-durable-identifier) | +| Use-A22 | The re-documentation burden: measured evidence for the Imaging Problem List | Data Structures / Imaging Problem List | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a22-the-re-documentation-burden-measured-evidence-for-the-imaging-problem-list) | +| Use-A16 | Imaging Persona and the reporting data context object model | Data Structures / Imaging Persona | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a16-imaging-persona-and-the-reporting-data-context-object-model) | +| Use-A21 | Application-layer data structures, distinct from transport expressions | Data Structures / Structures versus transport | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a21-application-layer-data-structures-distinct-from-transport-expressions) | +| Sem-C6 | Documents record judgment; the schema is the specification | SDKs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#c6-documents-record-judgment-the-schema-is-the-specification) | +| Sem-C7 | Two databases from one source commit | SDKs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#c7-two-databases-from-one-source-commit) | +| Sem-C8 | Three retrieval modes over the corpus | SDKs | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-semantic.md#c8-three-retrieval-modes-over-the-corpus) | +| Use-A20 | Proposed formal data models and generated schemas | SDKs / Imaging Problem List SDK | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#a20-a-formal-system-of-data-models-with-generated-schemas) | +| Use-B5 | Source-specific application families and longitudinal uses | Use Cases / Imaging history | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b5-six-downstream-application-families-for-the-imaging-problem-list) | +| Use-B6 | Breast imaging: one entity across assessments, and an automated MQSA audit | Use Cases | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b6-breast-imaging-one-entity-across-assessments-and-an-automated-mqsa-audit) | +| Use-B7 | Outcome tracking, follow-up completion, and radiology-pathology correlation | Use Cases | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b7-outcome-tracking-follow-up-completion-and-radiology-pathology-correlation) | +| Use-B8 | The reporting assistance framework: a context object model plus a plugin container | Use Cases | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b8-the-reporting-assistance-framework-a-context-object-model-plus-a-plugin-container) | +| Use-B11 | The use case catalog and the six value categories | Use Cases | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b11-the-use-case-catalog-and-the-six-value-categories) | +| Use-B1 | The Imaging Problem List viewer, and anatomy as presentation only | Sample Applications | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b1-the-imaging-problem-list-viewer-and-anatomy-as-presentation-only) | +| Use-B2 | The report extraction, coding, persistence and review platform | Sample Applications | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b2-the-report-extraction-coding-persistence-and-review-platform) | +| Use-B3 | Coding is an independent job, decoupled from extraction | Sample Applications | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b3-coding-is-an-independent-job-decoupled-from-extraction) | +| Use-B4 | Honesty-first evaluation of extraction | Sample Applications | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b4-honesty-first-evaluation-of-extraction) | +| Use-B9 | Demonstrated exchange and separately proposed board workflows | Sample Applications / Rendering, search, and exchange | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b9-a-multi-vendor-interoperability-demonstration-exchanging-cde-labeled-fhir-observations) | +| Use-B10 | Local-only operation for protected health information | Sample Applications | [Source entry](https://github.com/openimagingdata/oidm-knowledge/blob/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans/idea-inventory-structures-and-uses.md#b10-local-only-operation-for-protected-health-information) | diff --git a/docs/references/knowledge-organization-owner-guidance.md b/docs/references/knowledge-organization-owner-guidance.md new file mode 100644 index 0000000..6e5f9c7 --- /dev/null +++ b/docs/references/knowledge-organization-owner-guidance.md @@ -0,0 +1,143 @@ +# Owner guidance for the knowledge graph organization + +Recorded October 6, 2026, from the current organizing conversation. The quotations below preserve the owner's words, including abbreviations and spelling. They are distinct from the assistant's additions in the implementation plan and from Claude's review proposals. The working outline is in [the implementation plan](../plans/implementation.md). + +## Original outline for the first two sections + +The owner supplied this outline. SDKs, Applications, and Use Cases were headings to develop collaboratively. + +```text +FOUNDATION CONTEXT +* Introduction: Interconnected Knowledge (as separate from patient-specific assertions) +* Assertion Subjects/Finding Definitions (including diagnoses, etc., abbrev FDe) + - Overview of the knowledge graph schema + - Taxonomy of "assertion subjects": findings, diagnoses, assessments, etc (abbrev FDe) + - Properties of FDe + - Relationships BETWEEN FDes + - Categorical attributes of FDes: Data Elements + - Measurements of FDEs + - Examples of some actual graphs + - OIFM and CDE repositories (and relationship) + - Using LLMs to drive creation of the system + - Proposed authoring tool (evolution of FindingModelForge) +* Anatomy + - Anatomic locations: curation, first additions ("contained by", sex-specific, laterality, etc.) + - Incorporation into RadLex: including proposed added metadata such as tissue type + - Relationships to other ontologies (SNOMED CT, TA2, FMA, LOINC) +* Exam types + - RadLex/LOINC Playbook + - Exam families (see ../exam-types) +* Overall integration + - FDe definitions: "anatomic scope" (relationship to anatomy) + - FDe definitions: exam types + - Anatomic locations <-> exam types (and link to a proposed application: smarter relevant priors) + - FDes associated with non-imaging diagnoses (and procedures, such as surgeries?) + +DATA STRUCTURES +* Assertions/Observations + - Overview, focusing on this is an application-layer data structure + - What's contained: POINTERS to FDe, to an anatomic location + - Attributes: pointers to sub-assertions with DataElement and Measurement subjects + - Sub-components + - FHIR representation: Objservation, components, connections + - Provenance: AI generated, technologist/modality generated (DICOM SR?), LLM-extracted from report text, LLM-generated from radiologist interaction, radiologist review of AI-based tools +* "Exam Finding List" / Imaging Diagnostic Report + - Exam information + - Pointer to exam type information + - Maybe information on protocol, etc. + - Other exam performance information: pointers to DICOM data? + - Collection of Assertions/Observations (noting sections: Findings, Impression) + - More relationships between assertions: findings/diagnoses, sub-components, associations, finding -> impression + - FHIR Imaging Diagnostic Report represetation +* Imaging Problem List / Patient Imaging History + - Finding-based organization + - Basic applications, use as context information + - Longitudinal tracking + - FHIR representation--? +* Imaging persona + - Incorporates IPL + - Exam-planning information (MRI safety, allergies, body habitus) + - Clinical history extraction (e.g., imaging-relavant oncology history, operative notes) + - Pathology results -> diagnoses, interconnected to IPL elements + - Other non-imaging diagnoses POSSIBLY associated with imaging findings + - Availability via FHIRCast + +SDKs +... + +APPLICATIONS +... + +USE CASES +... +``` + +## SDK additions and clarifications + +> For patient data structures KEY is using LLM agents to do automated extraction of report text to data structures (see imaging-problem-list extraction work) +> Also for the data structures, DESPERATELY need to be able to go back and forth from FHIR representations. + +> The patient data structures needs to ahve SOMETHING about the larger patient history information. +> The "imaging Persona" concept--the patient facts RELEVANT TO IMAGING organized in a standard way +> I think FHIR converstion is probably its own OVERALL page on the philosophy, and then SPECIFICs on the Observation object itself, the EFL, and the IPL. + +In response to including LLM extraction from clinical notes, operative reports, and pathology: + +> Yes, INCLUDING the idea that the FOUNDATION CONTEXT can have appropriate information about LIKELY associates (e.g., history of cholecystictomy [LOINC, CPT] code and diagnosis [ICD, LOINC, SNOMED CT], and the finding and diagnoses definitions in our system and how to access all of that IN THE CONTEXT of the patient) + +> For authoring/maintaining, also mention LLM-assistance for enforcing guidelines for this content +> Also mention an LLM-based approach to extract proposed FDes and associated information from corpuses (corpi?) of radiology reports +> Also include explicit tools on connections to external ontologies (helps in authoring) + +## Knowledge nodes, precision, and hierarchy + +> Also, I'm concerned here that we're not considering this in the context of, "What are the ACTUAL KNOWLEDGE GRAPH NODES we're creating here?" + +On the subject of a measurement: + +> It's not an assertion about a PATIENT. There's nothing "8 mm" about a PATIENT. It's about a FINDING! + +On coded references and unnecessary detail: + +> Oh, that shorthand with those references like this is UNACCEPTABLE. ENGLISH, please--comprehensible. + +> Why all that noise about FHIR in the Measurement entry? + +> You have a page called "Incorporation into RadLex"?! Um, maybe there's some context missing?! + +> And you've flattened the structure I provided you! What happened to the actual levels of the outline? No wonder I can't understand the context of these things--they were part of an outline layer that you just THREW AWAY! + +## Applications additions + +> Also need an explicit anatomy browsing site, and probably in interface for looking at our view of the exams (exam families, etc) with the linked anatomy. +> For "reporting assistance", we can mention the "green findings" application (it's an older one--you can see some mention of it in one of the older Excalidraw boards). The basic idea was that there are a BUNCH of fixed findings in radiology reports, and every draft of a report should START with those things we KNOW are going to be in the report because they were in a prior report. (Which is NOT everyhting that was in the last report, it's an interesting issue!) But there is a DEFINITE subset of findings that will ALWAYS carry forward. +> But we should get into the idea as well of "object-oriented reporting" and include the idea of the objects being created from within the PACS imaging viewing interface. + +> I'd also like to get into applications around outcome tracking. + +## Patient concepts, community development, and implemented examples + +> What do YOU think is actually missing in terms of "patient concepts"? Right now, that work has only been done at a high, fairly imprecise level. Figuring out the imaging-relevant patient concepts and data structures is the first step in the thread of work that leads to defining the Imaging Persona. + +> "Content guidelines" are, again, something that the community will continue to develop over time. We have some IDEAS about these currently, which you have access to, but these are EXPECTED to be part of iterating on the ongoing content creation process. + +> I don't understand what "definitions explain the concepts" would actually look like. I do agree that we need to separate out the underlying LIBRARY code (SDK) from APPLICATIONS (Applications or Use Cases). + +> I think the "actually implemented" is the right dividing line between Use Case and Sample Application, but even simple demos count as Sample Applcations. And the whole "green findings" thing was an actual prototype feature implemented by one of the reporting software vendors and demonstrated at RSNA. + +> Yes, there's an node in the Imaging Persona area about the pieces of information we're going to pull and how we're going to represent them there specifically. +> +> In the introduction to data structures in general, we can make the point that one of the important threads of work is the ongoing definition of these data structures by the community, especially by those implementing solutions using these ideas who can ground their contributions in real use. + +## Versioning and longitudinal finding linking + +> 1. THere needs to be a versioning scheme for managing content updates, likely deprecation with pointers to updated or replacing nodes. And we can also have the idea of pointers referring to specific versions of the definitions. These ideas need to be further fleshed out, but that's the concept. +> 2. In terms of reconciling clinical information, this is clearly a significant question in clinical informatics. (In fact, it would be good to dispatch a research subagent to do some lookup on the current state of it.) We've done some preliminary work SPECIFICALLY on the issue of identifying observations of imaging findings across an imaging history, and making the appropriate connections. THAT SPECIFIC ISSUE of longitudinal tracking of findings across time should probably be its own node, probably in SDK, don't you think? + +## Fresh reconstruction and temporary archive + +> I worry that if we take an edit-in-place approach here, we'll end up anchored on existing garbage rather then clean thinking. + +> How about just "archive/knowledge" and build INTO the plan that we're going to nuke it when we've gotten the pieces that matter out of it? + +The implementation plan records the fresh-writing method and required final archive deletion. It also distinguishes the separately developed outward-facing pages under `site/` from the internal graph. diff --git a/docs/references/knowledge-outline-claude-review.md b/docs/references/knowledge-outline-claude-review.md new file mode 100644 index 0000000..475f673 --- /dev/null +++ b/docs/references/knowledge-outline-claude-review.md @@ -0,0 +1,176 @@ +# Claude review of the initial knowledge-node outline + +Captured October 6, 2026, from the Claude agent in Herdr pane `wA:p5`. This is the full agent reply with terminal whitespace and table borders normalized. It reviewed the initial October 5 outline, before the owner's subsequent corrections and Applications additions. Its coded labels refer to that earlier outline. These are agent findings and proposals, not owner decisions, current implementation facts, or instructions to change the organization. Technical claims require source verification before use. + +## Subsequent review and correction + +Recorded at the October 14 restart checkpoint from the later review in the same conversation. This is a summary of Claude's later assessment, not a second verbatim transcript or owner approval. + +Claude considered the restored hierarchy and the distinction between shared definitions and finding assertions sound. Its remaining suggestions concerned the Applications/Use Cases boundary, patient concepts needed by the proposed uses, content guidelines and identity/versioning, and repeated material across conceptual, SDK, and application documents. + +Claude explicitly withdrew its earlier claim that Imaging Diagnostic Report was necessarily the IHE profile and encoded positive findings as FHIR Condition. It had relied on a single CDE repository note without checking the primary supplement. Do not use that earlier claim to determine the EFL/IDR relationship or FHIR mappings; both remain questions to investigate. + +The owner's subsequent guidance governs the active outline: patient-concept development is unfinished work leading toward Imaging Persona; content guidelines evolve through community authoring; SDKs cover library capabilities; and actual implementation distinguishes Sample Applications, including demos and prototypes, from Use Cases. The green findings feature was an implemented vendor prototype demonstrated at RSNA, according to the owner. The owner also supplied the preliminary content-versioning concept and proposed a dedicated SDK node for longitudinal finding linking. These statements are preserved in [owner guidance](knowledge-organization-owner-guidance.md) and reflected in [the plan](../plans/implementation.md). + +## Initial review reply + +I reviewed the October 5 outline. I read docs/HANDOFF.md, the outline, project-direction.md, idea-coverage.md, the draft headings (not their bodies) and the CDE repo's IDR note. +I edited nothing and read no reviewer correspondence. Where I cite the owner, I give the date of the recorded statement; everything else is my proposal. + +Top-line + +1. The scope is mostly right, but some nodes are duplicated and some substantive ideas have no home. FC-02/FC-04, FC-12/FC-13/SDK-06–08 and DS-18–20/SDK-15–17 overlap. Several +inventoried ideas are missing (listed under each section). +2. The two-graph interconnection has no primary home. The owner put it at the centre of Data Structures: "most ESPECIALLY in the Observation layer, and how there are two graphs +and they work together" (Sept 22). The outline splits it between FC-01 and DS-01, and DS-01 is really about structures versus transport. +3. DS-07 pairs EFL with an external standard as if they were peers. The "Imaging Diagnostic Report" is the IHE IDR Phase II supplement (public-comment draft, 2026-03-04, per +ACR-RSNA-CDEs/notes/ihe-idr-extract.md). It is a FHIR encoding profile, not an OIDM structure, so it belongs in the mapping account (DS-19). That note also shows IDR encodes +positive findings as FHIR Condition and negatives as Observation. That bears directly on DS-17/DS-18. + +Foundation Context + +ID: FC-02 / FC-04 +Finding: Near-duplicates: both cover the "kinds of subject" distinction. Subtype hierarchy is already in FC-06 (taxonomy edges). +Recommendation: Merge into FC-02 using the schema's node classes (FindingClass, Diagnosis, Grouping; AssessmentScheme membership left open) and the owner's "defines, not is-a" +rule (Sept 30). Ask the owner what their "Taxonomy" heading meant. If it meant content organization, repurpose FC-04 to the MGB exam-oriented sub-taxonomies (Sem-B7). The owner +called those "the immediate direction for all of the content" (Sept 21), and they currently have no node. +ID: gap +Finding: No node for moving from document-oriented definitions to the graph. The owner called this urgent (Sept 22) and gave the direction "likely using RDF/OWL… more ergonomic +data structures… for SDKs and via an API" (Sept 30). +Recommendation: New FC node: schema evolution and migration. It separates current from intended and covers the OIFM document format, the findingmodel metadata rewrite +(feature/metadata-cleanup) and the RDF/OWL direction. FC-03 stays the schema overview. +ID: gap +Finding: No node for the content build-out, which the owner placed explicitly in Foundation Context (Sept 22). This covers the Gamuts corpus and its replacement, content +pipelines, and the build-out ideas Sem-B2, B3, B4 and F7. +Recommendation: New FC node: content build-out and coverage. +ID: gap +Finding: Content guidelines have no home. The "Guideline / check / human decision" connection points at nothing. The guidelines exist in inventoried ideas Sem-A2, A3, A7 and A8, +plus Groupings "usually used in the negative" (Sept 30). +Recommendation: New FC node: definition content guidelines. SDK-07 checks against it. +ID: gap +Finding: No node for identity, versioning and lifecycle. Identifiers carry provenance (Sem-B1); there are OIFM and RDE IDs; cross-links persist after promotion (owner, Sept +27–29). SDK-02 names "definition versions" with no FC source. Patient assertions need to say which definition version they reference. +Recommendation: Split this out of FC-05 as its own node. FC-11 links to it for cross-collection identity. +ID: FC-05 +Finding: Should carry the metadata-rewrite fields (body region, subspecialty, etiology, time course, age, sex, modalities), plus Sem-C1 and C2. +Recommendation: Record as an open question, not a decision. The owner said time course and etiology "may need to be a property of the edge connecting it to a diagnosis" (Sept +30). +That conflicts with the node-level fields on the findingmodel branch. +ID: FC-06 +Finding: Missing "Deriving the differential." It is a heading on the owner-reviewed relationships draft, and the owner said the distinction "enable[s] inference" (Sept 30). Edge +properties should name the specificity scale (pathognomonic / highly suggestive / suggestive). +Recommendation: Add inference as a section of FC-06, plus an SDK capability (see SDK-03). +ID: FC-08 +Finding: Sem-F6 ("measurement, method, and interpretation are three things") is missing. Measurements are typed by kind of quantity, not explicit units (owner, Sept 29); the +"permissible units" wording should follow that. +Recommendation: Add both. +ID: FC-12 / FC-13 +Finding: One idea in three homes: FC-12, FC-13 and SDK-06/07/08. FC-13 has no substance apart from Forge's requirements. +Recommendation: Refocus FC-12 on the authoring and review workflow: triage, three-tier review, human review as the only authority (Sem-B4, B5, C4, C5), and OIFM-to-CDE promotion. + +Fold FC-13's requirements into FC-12. Forge itself goes to Sample Applications. +ID: FC-16 +Finding: Scoped to anatomy only, but external codes also appear in FC-05 and FC-22. Mapping semantics (Sem-C3 "index codes must be exact", Sem-F2 terminology roles) apply across +all three axes. +Recommendation: Generalize: external terminologies and mapping semantics for definitions, anatomy and exams, with anatomy detail as a section. This answers review question 2. +ID: FC-15 +Finding: Sem-D7 ("missing is-a and structure-type layer") is the motivation for adding tissue type. +Recommendation: Cite it there. +ID: FC-20 +Finding: The MGB sub-taxonomies are exam-oriented, so they link definitions to exam types. +Recommendation: Cross-link to FC-04 (if repurposed) or the content node. + +Data Structures + +ID: gap +Finding: The two-graph interconnection, plus worked patient-side examples. The owner's Sept 22 method item 6 assigns them: the CDE pyelonephritis report shows both graphs; IPL +example2 is patient-side only. +Recommendation: New DS node: patient graph and Foundation interconnection, using those worked examples. Narrow FC-01 to the Foundation side. Keep the owner's latest "pointers" +wording (Oct 5) alongside the Sept 24 "interconnection that attaches knowledge to facts." +ID: DS-02 +Finding: Should present the related representations side by side: the CDE report graph, IPL Observation, and the OpenImagingDataModel lineage. These are "related representations… + +never the same thing serialized" (Sept 22), and "no specification exists" (Sept 21). The IDR note also records that the CDE committee moved away from the word "observation" +because of FHIR overlap. +Recommendation: Keep the title "Assertions / Observations" but record that terminology collision. +ID: DS-03 / DS-04 +Finding: Unclear boundary. Sem-A10 ("associated findings versus components") is the source distinction. +Recommendation: One sentence each: attribute sub-assertions have DataElement or Measurement subjects; components have FDe-subject child assertions. If no source separates them +further, merge into one "sub-assertions" node. Keep the owner's sub-assertion wording marked as proposed (review question 3). +ID: DS-03 +Finding: Negatives and normality are missing: Sem-A4, Use-A10, A11, A23. +Recommendation: Name presence, absence, normality and Grouping negatives explicitly. +ID: gap +Finding: Recommendations and follow-up: Use-A18, the live-versus-closed axis, and outcome tracking (Use-B7). +Recommendation: New DS node: recommendations and follow-up, linked to DS-10, DS-11 and assessment assertions. +ID: gap +Finding: Patient assessment assertions (ASSESSED_BY, owner Sept 30) have no DS home, although review question 1 asks about them. +Recommendation: Make this a section of DS-06 rather than a node, unless sources support more. +ID: DS-06 / DS-09 +Finding: Both own finding-to-impression links. +Recommendation: DS-09 owns section organization, DS-06 owns relation types; remove the duplicate. +ID: DS-07 +Finding: See top-line 3. Use-A3 (faithful translation) and Use-A17 (sections beyond Findings unmodeled) belong here. +Recommendation: Retitle to Exam Finding List. Move IDR to DS-19 as an external alignment target. +ID: DS-11 +Finding: Identity within one report (Use-A9: several observations of one finding) differs from identity across exams. +Recommendation: State both. Carry the three source status vocabularies side by side (Sept 21 rule). +ID: DS-12 / DS-16 +Finding: The Persona is a single larger graph underpinned by Foundation Context, not data "alongside" it (owner, Sept 24). Use-A16 and Use-B8 (the reporting context object model) + +are its source. FHIRcast synchronizes context; it does not carry data. +Recommendation: Link DS-12 to the context object model. Fold DS-16 into DS-12, or into the interoperability family under DS-17, as a qualified section. +ID: DS-13 +Finding: Probably thin. +Recommendation: Make it a section of DS-12 unless sources support a node. +ID: DS-15 +Finding: Overlaps DS-14. Its distinctive content is radiology–pathology correlation (Use-B7). +Recommendation: Retitle Pathology correlation and patient diagnoses. Keep its rule that likely associations are not patient facts. +ID: DS-17 +Finding: Records only FHIR. The owner's Sept 21 decision names FHIR and DICOM as transport, and DICOM SR is an input in DS-05. +Recommendation: Record DICOM SR as a known transport outside the current scope. +ID: DS-17 / DS-18 +Finding: Fidelity and identity questions to record (review question 6). +Recommendation: (a) Positive findings: Observation or IDR's Condition? (b) Sub-assertions as component versus hasMember/derivedFrom. (c) Version-pinned definition references +(canonical URL with version). (d) Provenance as the Provenance resource versus inline. (e) Longitudinal identity: FHIR has no native "same finding over time." (f) Which side is +authoritative after a round trip; extensions versus declared loss. Prior art to cite: FHIRSamples (DiagnosticReport with chained CDE-coded Observations) and the IPL-MVP FHIR +persistence note. The mapping is documented but implemented nowhere (Sept 20–22 snapshot); say so. + +SDKs (all assistant proposals) + +│ ID │ Finding │ Recommendation │ +│ │ Omits the deck's framing: determinative tools versus generative agents, and "resolve, │ │ +│ SDK-01 │ don't reinvent." Also the owner's statement that SDKs help "AUTHOR and MAINTAIN" │ Make the determinative/generative split the map's organizing axis. │ +│ │ Foundation Context (Sept 30). │ │ +│ SDK-02 │ Runtime crosswalk from external codes to FDes is not explicit (molu is the real │ Add both. │ +│ │ implementation). Sem-C8 (three retrieval modes) belongs here. │ │ +│ SDK-03 │ Inference is missing: deriving a differential, expected findings for a diagnosis, │ Add to SDK-03, or make it a new capability. │ +│ │ Grouping negatives. │ │ +│ │ Foundation coding is shared by SDK-08, SDK-09 and SDK-12. Use-B3 ("coding is an │ New SDK node: Foundation coding of extracted text (definition and │ +│ gap │ independent job, decoupled from extraction") and Use-A24 (generalizing allowed, │ anatomic-location assignment, per the IPL assignment rules). SDK-09 │ +│ │ specializing not) support a separate capability. │ keeps extraction. │ +│ gap │ Evaluation of extraction is missing: Use-B4 (honesty-first evaluation) and Use-A26 │ New SDK node, or a section of SDK-09. │ +│ │ (taxonomy of extraction failures). │ │ +│ SDK-09 / │ PHI and local-only operation (Use-B10, IPL local-model plans) are missing; this is │ Add to both. │ +│ SDK-12 │ critical for wider clinical history. │ │ +│ SDK-06 / │ The boundary should follow the determinative/generative split: mechanical validation │ Restate both on that line. │ +│ SDK-07 │ versus LLM guideline checks against the new content-guidelines FC node. │ │ +│ │ Three stubs, each just "implements DS-1x." Nothing is implemented. The owner asked for │ Collapse into one SDK node: bidirectional FHIR conversion, with │ +│ SDK-15–17 │ one philosophy account plus per-structure mappings, and DS-17–20 already provide them. │ per-structure sections. Split later only when implementations │ +│ │ │ diverge. │ +│ SDK-14 │ Matches the owner's cholecystectomy example. The "seek missing information" question is │ No change. Verify code systems (ICD-10-CM / CPT / SNOMED │ +│ │ correctly left open. │ procedures) at source, as the outline says. │ + +Separating knowledge nodes, clinical objects and pages + +- Clinical objects: I found no per-object nodes. Per-type accounts (FC-07, FC-08), example tables (FC-10) and relationship tables in FC-06 keep the separation. +- Pages: FC-03, FC-06, FC-11, FC-14 and FC-17 roughly track the crafted drafts (next-generation-schema, relationships, finding-models-and-cdes, anatomic-locations, exam-types). +That is acceptable if each node is a fuller account the page distills, not a mirror of the page. +- Using the drafts: Use them only as evidence of owner-agreed wording and as a check for missing ideas (for example "Deriving the differential"). Don't edit them or adopt their +section structure as the node structure. + +Owner decisions this surfaces (one at a time) + +1. What the "Taxonomy" heading meant: subject-kind distinctions, or the exam-oriented content taxonomies. +2. Whether time course and etiology are edge properties or definition fields. +3. Whether EFL and IDR is an alignment (my reading of the IDR note) or something the owner intends differently. diff --git a/docs/references/lean-content-coverage.tsv b/docs/references/lean-content-coverage.tsv new file mode 100644 index 0000000..a99448e --- /dev/null +++ b/docs/references/lean-content-coverage.tsv @@ -0,0 +1,134 @@ +path pass disposition before_body_words after_body_words reason +knowledge/applications/finding-and-location-coding.md 1 edited 1495 1150 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/applications/finding-model-forge.md 1 edited 1450 1044 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/applications/finding-models-site.md 1 edited 618 379 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/applications/imaging-problem-list-viewer.md 1 edited 1240 899 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/applications/index.md 1,2 edited Descriptions shortened in the first pass; reviewed again and still match their concepts. +knowledge/applications/ipl-mvp-extraction.md 1 edited 1048 840 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/applications/report-extraction-platform.md 1 edited 1948 1426 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/applications/reporting-sdk.md 1 edited 886 582 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/data-structures/anatomic-location-assignment-rules.md 2 edited 1128 1105 Tightened description and migration introduction only; source guidance and structure unchanged. +knowledge/data-structures/exam-finding-list.md 1 edited 1275 991 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/data-structures/fhir-mapping.md 2 edited 1217 1033 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/data-structures/hierarchy.md 1 edited 999 880 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/data-structures/ihe-idr-alignment.md 2 edited 1105 988 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/data-structures/imaging-persona.md 1 edited 693 456 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/data-structures/imaging-problem-list.md 1 edited 1391 1122 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/data-structures/index.md 2 edited Reviewed navigation, synchronized descriptions, and simplified introductory prose or directory labels. +knowledge/data-structures/observation.md 1 edited 1308 1062 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/data-structures/sample-data.md 2 edited 992 885 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/data-structures/technical-imaging-findings.md 2 edited 1203 1185 Tightened description and migration introduction only; source guidance and structure unchanged. +knowledge/glossary/anatomic-location.md 2 edited 340 337 Removed rhetorical setup around source quotations and shortened lineage comparison. +knowledge/glossary/anatomic-scope.md 2 edited 324 307 Shortened scope-family relationship and missing-field limitation without changing open status. +knowledge/glossary/assessment-scheme.md 2 edited 297 278 Removed repeated separation framing; retained assessment distinctions and source quotations. +knowledge/glossary/attribute-value.md 2 edited 258 243 Shortened positional-code explanation and repetitive presence cross-reference. +knowledge/glossary/attribute.md 2 edited 310 292 Tightened attribute-kind introduction and unresolved naming discussion. Tightened its description and matching index entry. +knowledge/glossary/body-region.md 2 edited 371 355 Shortened three-use explanation and removed rhetorical fallback introduction. Tightened its description and matching index entry. +knowledge/glossary/cde-element.md 2 edited 290 270 Condensed property definition and clarified component/set/value relationship. +knowledge/glossary/cde-labeled-fhir-observation.md 2 edited 310 296 Removed emphatic sample framing and shortened source attribution and open conflict wording. +knowledge/glossary/cde-set.md 2 edited 308 301 Replaced false range with named examples and shortened parent-code explanation. +knowledge/glossary/cde.md 2 edited 298 269 Removed confusion framing and shortened staging and site-post introductions. +knowledge/glossary/change-from-prior.md 2 edited 340 282 Removed repeated snapshot and attribute framing; shortened missing-attribute caveat while retaining all 124-model and value-set details. +knowledge/glossary/coding.md 2 edited 367 337 Split dense pipeline prose into sequential operations and removed judgment framing around location rules. +knowledge/glossary/contained-by-and-part-of.md 2 edited 314 304 Removed rhetorical introduction and shortened organ-system and precomputation explanations. +knowledge/glossary/contributor.md 2 edited 295 283 Simplified registry validation and JSONL support explanation. +knowledge/glossary/data-element.md 2 edited 349 318 Shortened sharing, draft-status, and terminology-conflict explanations. +knowledge/glossary/element-binding.md 2 edited 284 260 Removed unsupported safe-reuse rhetoric and hanging-property metaphor; preserved technical statements for separate factual review. Corrected the modality quotation’s subject against pinned CONTEXT.md: it describes the descriptor’s intrinsic limit. +knowledge/glossary/exam-finding-list.md 2 edited 335 323 Removed rhetorical requirements framing while preserving repeated-observation rule and provenance requirement. +knowledge/glossary/exam-type.md 2 edited 346 317 Shortened definition, scope explanation, proposal status, and missing-artifact inventory. Tightened its description and matching index entry. +knowledge/glossary/extraction.md 2 edited 383 308 Split schema and pipeline descriptions and shortened validator failure list while retaining each failure category. +knowledge/glossary/fhir-condition.md 2 edited 306 279 Shortened mapping and unimplemented-status explanations; retained IDR disagreement and all quotations. +knowledge/glossary/fhir-diagnostic-report.md 2 edited 303 280 Removed sample framing and shortened implementation-gap description. +knowledge/glossary/finding-class.md 2 edited 357 336 Removed blurred-node metaphor and shortened working-group decision and unsettled IDR difference. +knowledge/glossary/finding-model.md 2 edited 311 281 Removed extrapolation from the 2024 definition to the entire project lifetime and shortened definition-versus-instance explanation. +knowledge/glossary/finding-taxonomy.md 2 edited 366 354 Clarified name-key and parent fields and shortened taxonomy/model distinction. Tightened its description and matching index entry. +knowledge/glossary/fma.md 2 edited 231 213 Shortened cross-reference and classification introduction while retaining coverage counts and mappings. +knowledge/glossary/gamuts.md 2 edited 287 273 Removed role framing and content-spine metaphor; shortened unresolved cleanup status. +knowledge/glossary/imaging-diagnostic-report.md 2 edited 390 367 Shortened source framing and alignment-status explanation without changing IDR definitions or conflicts. +knowledge/glossary/imaging-persona.md 2 edited 241 203 Removed repeated unimplemented-status and knowledgebase self-description. +knowledge/glossary/imaging-problem-list.md 2 edited 390 332 Removed signature-structure rhetoric and shortened query, grouping, and reconciliation explanations. +knowledge/glossary/index-code.md 2 edited 322 316 Simplified field and crosswalk explanations and removed in-flight framing around the identified rewrite. +knowledge/glossary/index.md 2 edited Read all 48 entries; shortened the introduction and synchronized seven tightened descriptions while preserving alphabetical links. +knowledge/glossary/laterality.md 2 edited 395 373 Removed repeated test introduction and shortened reversible-mapping requirement. Tightened its description and matching index entry. +knowledge/glossary/loinc-rsna-radiology-playbook.md 2 edited 307 292 Split roadmap proposal into concise sentences and removed historical throat-clearing. +knowledge/glossary/loinc.md 2 unchanged 287 287 Already compact: quoted specification requirements, code examples, identifier detection, and missing-curated-list caveat each provide distinct information. +knowledge/glossary/measurement.md 2 edited 290 234 Shortened node-type explanation, grouped-measurement example, and repeated model distinction. +knowledge/glossary/observation.md 2 edited 343 330 Replaced atomic-unit metaphor and removed lineage and terminology-collision framing. +knowledge/glossary/oidm-organization-code.md 2 unchanged 245 245 Already concise: generator behavior, registry mappings, all seven codes, five corpus counts, and namespace/provenance distinction are needed. +knowledge/glossary/oidm.md 2 edited 269 236 Shortened project definition and organization-code explanation; replaced inaccurate self-description about old-name placement with history link. +knowledge/glossary/oifm.md 2 edited 247 226 Shortened canonical-format and overloaded-acronym explanation without changing fields or identifier rules. +knowledge/glossary/open-imaging-reporting-sdk.md 2 edited 261 220 Removed centerpiece rhetoric, historical introduction, and repeated knowledgebase status assurances. +knowledge/glossary/presence.md 2 edited 405 375 Removed negative-data rhetoric and repeated conflict introduction while retaining quoted absence meaning and all positional-code exceptions. Tightened its description and matching index entry. +knowledge/glossary/provenance.md 2 edited 323 291 Removed repeated provenance rationale and shortened quote-evidence mechanism while preserving source and evaluation distinctions. Tightened its description and matching index entry. +knowledge/glossary/radelement.md 2 edited 273 264 Removed authoring-order framing and distinguished documented URI forms from the proposed successor. +knowledge/glossary/radlex-id.md 2 unchanged 258 258 Already concise: RID field mappings, composite-ID exception, examples, and unrecorded ontology-version risk all add distinct information. +knowledge/glossary/radlex.md 2 edited 277 265 Removed repetitive two-direction framing around curation and upstream integration. +knowledge/glossary/snomed-ct.md 2 edited 271 232 Split long usage sentence into source-specific statements and shortened licence/credential requirement. +knowledge/glossary/tag.md 2 edited 278 263 Shortened mixed-tag example framing and retained incomplete migration status. +knowledge/glossary/technical-finding.md 2 edited 342 317 Removed emphasis around coding distinction and shortened draft/open-question language. +knowledge/glossary/umls.md 2 edited 290 240 Removed two-job framing and shortened typed-failure explanation while retaining all error categories and mapping qualifications. +knowledge/guides/authoring-guide.md 2 edited 1212 1084 Tightened instructions and metadata guidance; retained rules, examples, and type vocabulary. +knowledge/guides/index.md 2 unchanged Two concise directory entries; both descriptions match their pages. +knowledge/guides/migration-ledger.md 2 edited 3048 3027 Shortened the introduction; preserved all migration rows as provenance records. +knowledge/history/cde-template-rendering.md 2 edited 774 555 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/history/extraction-approaches.md 2 edited 1557 1312 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/history/index.md 2 edited Simplified navigation introduction and synchronized concept descriptions. +knowledge/history/lineage-repositories.md 2 edited 1601 1359 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/history/site-articles.md 2 edited 1399 1304 Tightened article summaries and authored framing; preserved all fifteen linked titles, dates, direct quotes, reported outcomes, and About-page scope. +knowledge/history/timeline.md 2 edited 1619 1607 Tightened the prologue and description; preserved every dated event table and source. +knowledge/history/use-cases.md 2 edited 1153 1062 Tightened authored framing; preserved every sourced use-case entry, attribution, category number, and table verbatim. +knowledge/index.md 2 edited Shortened draft-status and navigation instructions without losing entry points. +knowledge/log.md 2 edited Retained historical entries; added the full-review completion entry. +knowledge/overview/architecture.md 1 edited 1480 1247 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/overview/getting-involved.md 1 edited 1027 776 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/overview/index.md 2 unchanged Four descriptive navigation entries; no repeated framing or editorial commentary. +knowledge/overview/vision.md 1 edited 1173 860 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/overview/what-is-oidm.md 1 edited 851 575 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/plans/2026-09-20-knowledgebase-build-plan.md 2 edited 3890 3851 Tightened plan framing; preserved decisions, quoted goals, source map, phases, and concurrent decisions. +knowledge/plans/index.md 2 unchanged A single navigation entry matching the build plan description. +knowledge/references/anatomic-location-json-schema.md 2 edited 1695 1519 Shortened lineage and runtime explanations; preserved field tables, codes, counts, and example JSON. +knowledge/references/cde-schema-differences.md 2 edited 848 742 Tightened provenance and later-schema commentary; retained the attributed 2023 extract byte-for-byte. +knowledge/references/exam-finding-list-example.md 2 edited 1117 898 Shortened the walkthrough and synthetic-data introduction; preserved the JSON, fields, and absence distinction. +knowledge/references/finding-model-schema.md 2 edited 2027 1896 Shortened authored provenance and reconciliation framing; retained the migrated schema and constraint tables. +knowledge/references/imaging-problem-list-example.md 2 edited 1201 1024 Tightened grouping and status explanations; retained JSON, temporal rules, and synthetic-data qualifications. +knowledge/references/index.md 2 edited Shortened navigation descriptions and synchronized all eight entries. +knowledge/references/oifm-metadata-fields-extract.md 2 edited 2429 2375 Shortened provenance and release-status framing; preserved the complete attributed field reference. +knowledge/references/oifm-overview-extract.md 2 edited 2040 1967 Shortened provenance and glossary introduction; preserved the complete attributed authoring prompt. +knowledge/references/status-update-2026-01.md 2 edited 835 778 Shortened introductory framing; preserved the complete slide extract. +knowledge/repositories/index.md 2 edited Simplified navigation introduction and synchronized concept descriptions. +knowledge/repositories/repository-map.md 2 edited 1855 1768 Tightened map guidance and branch summaries; preserved the full repository table, diagram, caption, identifiers, dates, and status distinctions. +knowledge/roadmap/anatomic-locations-and-radlex.md 2 edited 1240 982 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/roadmap/exam-types.md 2 edited 947 784 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/roadmap/finding-model-content-direction.md 2 edited 1439 1245 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/roadmap/finding-model-format-evolution.md 2 edited 1572 1390 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/roadmap/index.md 2 edited Simplified navigation introduction and synchronized concept descriptions. +knowledge/roadmap/ipl-data-model-system.md 2 edited 1183 952 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/roadmap/open-questions.md 2 edited 5585 5499 Tightened only introductions; retained all 90 numbered questions and all 21 defect rows verbatim, including their uncertainty, attribution, and ownership. +knowledge/roadmap/roadmap-2026.md 2 edited 992 833 Removed filler and simplified authored explanation while preserving technical detail. +knowledge/semantic-foundation/anatomic-locations/data-model.md 2 edited 1261 1159 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/anatomic-locations/index.md 2 edited Reviewed navigation, synchronized descriptions, and simplified introductory prose or directory labels. +knowledge/semantic-foundation/anatomic-locations/laterality-conventions.md 2 edited 968 878 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/anatomic-locations/lineage-and-current-implementation.md 2 edited 1024 874 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/anatomic-locations/radlex-integration.md 2 edited 1187 948 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/anatomic-locations/tooling.md 2 edited 953 868 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/anatomic-locations/why-anatomic-locations.md 2 edited 768 580 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/common-data-elements/cde-staging.md 2 edited 1536 1319 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/common-data-elements/cdes-and-radelement.md 2 edited 1368 1255 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/common-data-elements/index.md 2 edited Reviewed navigation, synchronized descriptions, and simplified introductory prose or directory labels. +knowledge/semantic-foundation/common-data-elements/next-generation-vocabulary.md 2 edited 2073 1706 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/exam-types/existing-building-blocks.md 2 edited 1619 1399 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/exam-types/index.md 2 edited Reviewed navigation, synchronized descriptions, and simplified introductory prose or directory labels. +knowledge/semantic-foundation/exam-types/overview.md 2 edited 1164 995 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/finding-models/authoring-workflow.md 2 edited 1130 993 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/finding-models/content-catalog.md 2 edited 1126 1022 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/finding-models/enrichment-pipeline.md 2 edited 1318 1137 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/finding-models/finding-model-format.md 2 edited 1439 1285 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/finding-models/finding-models-and-cdes.md 1 edited 1280 773 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/semantic-foundation/finding-models/finding-taxonomies.md 2 edited 875 726 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/finding-models/identifiers.md 2 edited 1024 817 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/finding-models/index.md 2 edited Reviewed navigation, synchronized descriptions, and simplified introductory prose or directory labels. +knowledge/semantic-foundation/finding-models/why-finding-models.md 1 edited 1024 670 Shortened and cross-reviewed in the first pass; examples, citations, and qualifications preserved. +knowledge/semantic-foundation/index.md 2 edited Reviewed navigation, synchronized descriptions, and simplified introductory prose or directory labels. +knowledge/semantic-foundation/terminologies/index.md 2 edited Reviewed navigation, synchronized descriptions, and simplified introductory prose or directory labels. +knowledge/semantic-foundation/terminologies/med-ontology-lookup.md 2 edited 1160 978 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. +knowledge/semantic-foundation/terminologies/ontologies-used.md 2 edited 1590 1402 Tightened authored prose and description; preserved facts, qualifications, citations, tables, examples, links, and headings. diff --git a/docs/references/linked-diagrams/anatomy-and-exam-coverage.md b/docs/references/linked-diagrams/anatomy-and-exam-coverage.md new file mode 100644 index 0000000..a47260a --- /dev/null +++ b/docs/references/linked-diagrams/anatomy-and-exam-coverage.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Anatomy and exam coverage" +description: "Actual exam coverage, anatomy connections, curated content" +tags: ["foundation-context", "use-cases"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/4jmRPObEdLq/ + title: "Common Anatomic Locations" +--- + +# Anatomy and exam coverage + +The board connects anatomy content to exam definitions, finding definitions, templates, and search. Its key exam example distinguishes a CT chest region from “chest” alone: an exam can include lower neck, upper abdomen, shoulders, and other anatomy beyond its focus. This makes actual coverage a definition concern.[^board] + +The board's checklist proposes hierarchy and code verification, synonyms, part-of relationships, body-system organization, added descriptive links, and full-text/semantic search. It also records then-proposed Common identifiers, MongoDB work, and governance discussions. Most items are unchecked.[^board] + +For our work, this anchors why Foundation Context needs both an anatomy system and exam-to-anatomy connections: consumers can reason about what a study includes and find relevant data. Read it with the current anatomy and exam-type pages. Its proposal to promote SNOMED or use ACR Common IDs as primary identifiers is historical and should not override later identifier decisions. + +Pillar connections: Foundation Context; Use Cases. Related working pages: [Anatomic locations](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/anatomic-locations.md), [Exam types](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/exam-types.md), [Foundation context](../../../site/content/foundation-context/index.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/4jmRPObEdLq/original.png) · [SVG](../../../sources/linked-diagrams/4jmRPObEdLq/original.svg) · [Source contents](../../../sources/linked-diagrams/4jmRPObEdLq/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/4jmRPObEdLq/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/4jmRPObEdLq/metadata.json). + +[^board]: The captured board, especially Active Issues; Exam Types; Other big projects. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/definition-observation-example.md b/docs/references/linked-diagrams/definition-observation-example.md new file mode 100644 index 0000000..af0f3bd --- /dev/null +++ b/docs/references/linked-diagrams/definition-observation-example.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "CDE and Observation worked example" +description: "Definitions connected to instance values and clinical text" +tags: ["foundation-context", "data-structures", "sdks"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/ArAkPl5S3kD/ + title: "CDE\u2194\ufe0eObservation" +--- + +# CDE and Observation worked example + +The pulmonary-nodule example connects a CDE definition, its elements and permissible values, a particular Observation, and FHIR JSON. The Observation selects values for presence, composition, size, and location, carries identifiers and anatomy, and supplies a sentence template that renders a clinical description. The arrows make the definition-to-instance interconnection concrete.[^board] + +“Where Observation objects can come from” lists current dictation, PACS annotation, prior-report extraction, structured reporting, AI, DICOM SR, and EHR data. The board proposes wrapper implementations and import/export work involving FHIR, DICOM SR, and OMOP, together with vendor demonstrations.[^board] + +For our work, this is a compact historical example of Foundation Context informing a patient Observation and developer tools translating representations. It predates the shared graph schema and uses CDE Set/Element structures and fixed example codes. Reuse the conceptual interconnection and multiple-source idea; consult current schema and SDK documents for today's classes and interfaces. The board does not demonstrate an implemented two-graph system or certify the example as conformant with a current FHIR profile. + +Pillar connections: Foundation Context; Data Structures; SDKs. Related working pages: [Next generation schema](../../../site/content/foundation-context/next-generation-schema.md), [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md), [Sdks](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sdks.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/ArAkPl5S3kD/original.png) · [SVG](../../../sources/linked-diagrams/ArAkPl5S3kD/original.svg) · [Source contents](../../../sources/linked-diagrams/ArAkPl5S3kD/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/ArAkPl5S3kD/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/ArAkPl5S3kD/metadata.json). + +[^board]: The captured board, especially CDE Set: RDES195; Observation; Template; Where Observation objects can come from; Code System Team. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/finding-model-authoring.md b/docs/references/linked-diagrams/finding-model-authoring.md new file mode 100644 index 0000000..9f84235 --- /dev/null +++ b/docs/references/linked-diagrams/finding-model-authoring.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "OIFM authoring and content development" +description: "Inclusive definitions, iterative enrichment, authoring tools" +tags: ["foundation-context", "sdks", "sample-applications"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/A55Dpt6hley/ + title: "OIFM Repo" +--- + +# OIFM authoring and content development + +This board gathers the work of creating, enriching, reviewing, and distributing finding definitions. Its pipeline starts with findings proposed by experts, terminology sources, AI, or report text. Minimal definitions can carry presence and change attributes, then gain anatomy, additional descriptors, references, contributor information, and expert review through iteration.[^board] + +The Forge material describes importing existing definitions, editing with expert input, and using references or report examples to propose improvements. Other notes propose shared library, CLI, and front-end workflows; schema and identifier checks; contributor records; and submissions through pull requests. Measurements with shared element identifiers, locations, and relationships appear as authoring questions.[^board] + +For our work, use this as a source for inclusive content development and the tools that support it. The current finding-model/CDE hub and shared next-generation schema should frame that material. Proposed names, schema forks, document-shaped JSON/Markdown workflows, package choices, and unchecked tasks describe stages of development. They should not become current requirements merely because they appear here. Forge screenshots document a represented interface, while surrounding plans require separate evidence of completion. + +Pillar connections: Foundation Context; SDKs; Sample Applications. Related working pages: [Finding models and cdes hub](../../../site/content/foundation-context/finding-models-and-cdes.md), [Next generation schema](../../../site/content/foundation-context/next-generation-schema.md), [Sdks](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sdks.md), [Sample applications](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sample-applications.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/A55Dpt6hley/original.png) · [SVG](../../../sources/linked-diagrams/A55Dpt6hley/usable.svg) · [Source contents](../../../sources/linked-diagrams/A55Dpt6hley/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/A55Dpt6hley/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/A55Dpt6hley/metadata.json). + +[^board]: The captured board, especially Open Imaging Finding Models: Pipeline and Repo; Approach for Creation of Data Models at Scale; FindingModelForge; Schema Issues; FMF Authoring Issues. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/finding-model-rules.md b/docs/references/linked-diagrams/finding-model-rules.md new file mode 100644 index 0000000..70d4a00 --- /dev/null +++ b/docs/references/linked-diagrams/finding-model-rules.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Finding model rules and scope" +description: "Naming, anatomic scope, separate associated entities and components" +tags: ["foundation-context"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/2a3Jq3oVVty/ + title: "Open Imaging Finding Models" +--- + +# Finding model rules and scope + +This compact board records naming and modeling rules. Names expand acronyms, with compact forms retained as synonyms. Models, attributes, and values default to lower case; eponyms should be minimized and should not be preferred terms when avoidable. Attribute and value synonyms are explicit schema concerns.[^board] + +Its location rule defines where a finding could occur as a subset of anatomic-location nodes and says separate location attributes are not needed for that purpose. It asks for separate definitions for associated entities and for a component with its own detailed properties to be extracted as a separate finding, using the solid component of a mixed pulmonary nodule as the example. Modalities, life stages, sex phenotypes, and possible example repositories also appear.[^board] + +For our work, this supports reusable definition content, anatomic scope, and decomposition of complex findings. Read it with the current schema and relationships pages. The board's location statement concerns feasible scope; it should not be summarized as an instruction to encode patient location as an attribute. Its older “avoid associated findings” wording also needs to be read alongside the later relationship work. + +Pillar connections: Foundation Context. Related working pages: [Next generation schema](../../../site/content/foundation-context/next-generation-schema.md), [Relationships](../../../site/content/foundation-context/relationships.md), [Anatomic locations](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/anatomic-locations.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/2a3Jq3oVVty/original.png) · [SVG](../../../sources/linked-diagrams/2a3Jq3oVVty/original.svg) · [Source contents](../../../sources/linked-diagrams/2a3Jq3oVVty/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/2a3Jq3oVVty/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/2a3Jq3oVVty/metadata.json). + +[^board]: The captured board, especially Finding Model Rules; Finding Model Schema; lower-case note. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/imaging-context-object-model.md b/docs/references/linked-diagrams/imaging-context-object-model.md new file mode 100644 index 0000000..31db6a0 --- /dev/null +++ b/docs/references/linked-diagrams/imaging-context-object-model.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Imaging context object model" +description: "Patient/exam/report context, provenance, platform operations" +tags: ["data-structures", "foundation-context", "sdks"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/1sEx11UZ3gq/ + title: "OIDM Object Model" +--- + +# Imaging context object model + +The central map brings patient identity, orders, studies, current and prior reports, tracked observations, and other EHR information into one imaging context. Observations include identification, classification, tracking, anatomy, and coded values. Study-type branches distinguish modality, focused anatomy, included anatomy, laterality, and contrast information.[^board] + +The board's discussion proposes standard content with wrapper libraries and asks about an integrated runtime or microservices, event hooks, platform commands, report text during authoring, image connections, and provenance. It explicitly asks how to record who has seen, approved, changed, or rejected an Observation. A class sketch separately shows the then-proposed CDE and Observation structures.[^board] + +For our work, this anchors the breadth of patient and application context and the need for developer operations around it. The current Data Structures and SDK pillars can draw on those questions. “ObservationType,” nested CDE classes, and the historic standards notes retain their original context; they do not supersede the owner's current finding/diagnosis definitions or the shared graph schema. + +Pillar connections: Data Structures; Foundation Context; SDKs. Related working pages: [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md), [Sdks](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sdks.md), [Overview](../../../site/content/index.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/1sEx11UZ3gq/original.png) · [SVG](../../../sources/linked-diagrams/1sEx11UZ3gq/original.svg) · [Source contents](../../../sources/linked-diagrams/1sEx11UZ3gq/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/1sEx11UZ3gq/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/1sEx11UZ3gq/metadata.json). + +[^board]: The captured board, especially OIDM Data Context mind map; Stuff in CDE Set to get to later; For Next Time. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/imaging-problem-list.md b/docs/references/linked-diagrams/imaging-problem-list.md new file mode 100644 index 0000000..219c56b --- /dev/null +++ b/docs/references/linked-diagrams/imaging-problem-list.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Imaging Problem List" +description: "Per-exam findings, longitudinal association, integrated clinical context" +tags: ["data-structures", "use-cases"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/2jUZLu229km/ + title: "Imaging Problem List" +--- + +# Imaging Problem List + +The board develops a patient-level history of imaging findings from report-level finding lists. Its central flow is reports → Extractor → finding lists → Assembler → Imaging Problem List. The chest CT example retains explicitly absent findings as well as positive ones. The Assembler reorganizes observations by finding across exams.[^board] + +The “Maintaining the IPL” note asks whether this should be an EHR-maintained view, a dynamically regenerated and cached representation, or a service available to reporting systems and viewers. Its v2 note adds an important distinction: explicit longitudinal associations may add information beyond merely collecting reports. The appendectomy scenario and “master, integrated knowledge base” note extend the idea to operative, pathology, and other clinical information.[^board] + +For our work, this is an anchor for longitudinal patient data and context-sensitive applications: prior-finding displays, draft reporting, MRI safety, quality checks, and downstream care workflows. Read it beside the current Data Structures page and the owner's integrated Persona framing. The board's storage, FHIR representation, and vendor responsibilities are questions and proposals, not evidence of completed integration. + +Pillar connections: Data Structures; Use Cases. Related working pages: [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md), [Use cases](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/use-cases.md), [Overview](../../../site/content/index.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/2jUZLu229km/original.png) · [SVG](../../../sources/linked-diagrams/2jUZLu229km/original.svg) · [Source contents](../../../sources/linked-diagrams/2jUZLu229km/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/2jUZLu229km/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/2jUZLu229km/metadata.json). + +[^board]: The captured board, especially Imaging Problem List; Maintaining the IPL; Ingredients; Clinical Scenario. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/index.md b/docs/references/linked-diagrams/index.md new file mode 100644 index 0000000..d01f540 --- /dev/null +++ b/docs/references/linked-diagrams/index.md @@ -0,0 +1,35 @@ +# Linked diagram references + +These 15 short summaries describe the captured boards listed in the [source manifest](../../sources-manifest.json). Native SVG and PNG exports, original scene contents, searchable text, and capture metadata are stored under `sources/linked-diagrams//`. Sources remain outside Git under this repository's existing rules; the summaries are tracked here, and the live source URLs are kept only in the gitignored capture manifest. + +These captures remain source references under the [implementation plan](../../plans/implementation.md). Conversion into `knowledge/references/` awaits fresh knowledge reconstruction. Original files remain in private storage; presentation pages are a separate workstream. Retaining a capture does not approve its claims or permission to reproduce its figures. + +Read these as source references alongside the [recorded owner guidance](../project-direction.md). Historical class names, checklists, possible architectures, and demo designs retain their original status. A board is evidence of an idea or discussion; completion requires implementation evidence. The pillar connections below are navigation aids for the agreed structure. + +| Reference | Ideas to return to | Pillars | +|---|---|---| +| [Imaging Problem List](./imaging-problem-list.md) | Per-exam findings, longitudinal association, integrated clinical context | Data Structures; Use Cases | +| [OIFM authoring and content development](./finding-model-authoring.md) | Inclusive definitions, iterative enrichment, authoring tools | Foundation Context; SDKs; Sample Applications | +| [CDE and Observation worked example](./definition-observation-example.md) | Definitions connected to instance values and clinical text | Foundation Context; Data Structures; SDKs | +| [Imaging context object model](./imaging-context-object-model.md) | Patient/exam/report context, provenance, platform operations | Data Structures; Foundation Context; SDKs | +| [Finding model rules and scope](./finding-model-rules.md) | Naming, anatomic scope, separate associated entities and components | Foundation Context | +| [Structured report representation](./structured-report-representation.md) | Complex findings, report sections, source-independent representations | Data Structures; Foundation Context; SDKs | +| [Radiologist outcome feedback](./radiologist-outcome-feedback.md) | Patient/anatomy/time-based outcome feedback | Use Cases; Foundation Context; Data Structures | +| [RSNA exhibit examples](./rsna-exhibit-examples.md) | Comparable observations, multiple producers, AI discrepancies | Foundation Context; Data Structures; Use Cases | +| [OIDM big picture and application framework](./oidm-big-picture.md) | Shared context, commands, tools, content, application ecosystem | All five pillars | +| [Anatomy and exam coverage](./anatomy-and-exam-coverage.md) | Actual exam coverage, anatomy connections, curated content | Foundation Context; Use Cases | +| [Longitudinal integration discussions](./longitudinal-integration-discussions.md) | Tracked identity, per-exam findings, relevance filtering | Data Structures; Foundation Context; Use Cases | +| [Outcome and follow-up structures](./outcome-and-follow-up-structures.md) | Recommendations, later events, completion and pathways | Use Cases; Data Structures | +| [Use-case development and interaction ideas](./use-case-development.md) | User interactions, report views, spine numbering | Use Cases | +| [Participation, reference work, and demonstrations](./participation-and-demonstrations.md) | Contributing content, tools, reference work, demos | Foundation Context; SDKs; Sample Applications | +| [Pulmonary-nodule demo workflow and role tagging](./pulmonary-nodule-demo-workflow.md) | Multi-system nodule workflow, semantic annotations, element roles | Use Cases; Data Structures; SDKs; Sample Applications | + +There are 17 URL occurrences and 15 unique boards. Imaging Problem List and OIFM Repo each occur twice and share one capture and summary. The capture manifest is in `sources/linked-diagrams/manifest.json`. + +All boards were retrieved and visually reviewed on 2026-10-01. Each original PNG is a native full-board export at 1×. Three native SVG exports contain XML-invalid entities or control characters. Their untouched `original.svg` files are retained; linked `usable.svg` copies only normalize those characters. Each capture's metadata records that distinction and file checksums. The source-contents response alone lacks separately stored image binaries; the visual exports embed them. + +The earlier combined transcription in `sources/excalidraw-diagrams.md` remains available as historical research. These individual summaries were checked against the live exports and text, including corrections to the compact finding-model rules about anatomic scope and component extraction. + +## Capture validation + +Capture and summary work completed 2026-10-01. URL coverage, PNG decoding, selected SVG XML, 63 artifact checksums, and local summary links passed. Standalone reference validation reported zero errors and 50 warnings for links outside this directory; those targets were checked separately. On 2026-10-02, all 15 summaries were rechecked after removing live share links and meeting histories, and remaining discussion-history wording in four summaries was generalized. Capture dates record retrieval, not when an idea originated. Detailed-reference conversion and any restricted-figure redraws remain subject to the implementation plan. Use the [source instructions](../../sources.md) to download and verify the selected original artifacts. diff --git a/docs/references/linked-diagrams/longitudinal-integration-discussions.md b/docs/references/linked-diagrams/longitudinal-integration-discussions.md new file mode 100644 index 0000000..c7a9fe7 --- /dev/null +++ b/docs/references/linked-diagrams/longitudinal-integration-discussions.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Longitudinal integration discussions" +description: "Tracked identity, per-exam findings, relevance filtering" +tags: ["data-structures", "foundation-context", "use-cases"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/1PrQV4pohsD/ + title: "ACR OIDM\u2194\ufe0eEpic Connections" +--- + +# Longitudinal integration discussions + +The board connects coded findings, longitudinal lesion tracking, anatomy-based relevance, and EHR/reporting/viewer access. The breast-imaging sketch gives multiple study-specific findings one shared lesion identity and raises questions about current status, disappearance, and changes over time.[^board] + +The board asks who assigns tracking identifiers, whether FHIR Condition could represent a longitudinal finding, how a tracked lesion might have several classifications, and how unverified results might be managed. It proposes using exam/anatomy coverage to decide which prior lesions should be considered during a current study. The reporting-assistant example reminds the radiologist to update relevant prior findings.[^board] + +For our work, this is a source for persistent patient entities, per-exam observations, and context-based retrieval. The current Data Structures page should lead the explanation. The proposed use of Condition remains an open mapping question. The board does not establish an adopted FHIR mapping or completed cross-system deployment. + +Pillar connections: Data Structures; Foundation Context; Use Cases. Related working pages: [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md), [Exam types](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/exam-types.md), [Use cases](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/use-cases.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/1PrQV4pohsD/original.png) · [SVG](../../../sources/linked-diagrams/1PrQV4pohsD/usable.svg) · [Source contents](../../../sources/linked-diagrams/1PrQV4pohsD/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/1PrQV4pohsD/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/1PrQV4pohsD/metadata.json). + +[^board]: The captured board, especially Longitudinal Tracking; Breast Imaging Lesions Data Model. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/oidm-big-picture.md b/docs/references/linked-diagrams/oidm-big-picture.md new file mode 100644 index 0000000..8c615f4 --- /dev/null +++ b/docs/references/linked-diagrams/oidm-big-picture.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "OIDM big picture and application framework" +description: "Shared context, commands, tools, content, application ecosystem" +tags: ["all-five-pillars"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/8v07Nni5p8F/ + title: "OIDM Big Picture" +--- + +# OIDM big picture and application framework + +The board states an integrated, open platform vision for imaging tools and connects clinical content, authoring tools, standard data structures, reference software, an SDK family, and demonstrations. Its work-thread notes place iterative finding definitions, anatomy and exam coverage, report structures, program interfaces, and Python/TypeScript utilities alongside one another.[^board] + +The Reporting Assistance Framework places shared data context and reporting commands around an assisted reporting container. Inputs include patient/EHR information, reports, longitudinal findings, AI, DICOM SR, and extracted data. Proposed commands insert report text, manipulate context, request information, show images, communicate, send data downstream, or request image inference. A use-case map includes quality checks, AI integration, longitudinal reporting, references, and workflow assistance.[^board] + +For our work, this is an early anchor for how the owner's five pillars support an ecosystem of applications. Its content-development notes also connect minimal definitions, open iteration, and ACR/RSNA review. Use the current Overview to organize these ideas. Vendor lists, estimated anatomy counts, older class names, and demo names are historical board content rather than current inventories or completion evidence. + +Pillar connections: All five pillars. Related working pages: [Overview](../../../site/content/index.md), [Sdks](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sdks.md), [Use cases](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/use-cases.md), [Sample applications](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sample-applications.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/8v07Nni5p8F/original.png) · [SVG](../../../sources/linked-diagrams/8v07Nni5p8F/original.svg) · [Source contents](../../../sources/linked-diagrams/8v07Nni5p8F/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/8v07Nni5p8F/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/8v07Nni5p8F/metadata.json). + +[^board]: The captured board, especially Reporting Assistance Framework; OIDM Reporting-based Use Cases; Where / Who / What; OIDM Work Threads; OIDM Data Context Model. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/outcome-and-follow-up-structures.md b/docs/references/linked-diagrams/outcome-and-follow-up-structures.md new file mode 100644 index 0000000..6eb1a55 --- /dev/null +++ b/docs/references/linked-diagrams/outcome-and-follow-up-structures.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Outcome and follow-up structures" +description: "Recommendations, later events, completion and pathways" +tags: ["use-cases", "data-structures"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/68X9qfSP5qA/ + title: "Outcome Tracking Schema" +--- + +# Outcome and follow-up structures + +The board brings a radiology report together with future clinical events and potential actions. Events include later imaging, pathology, procedures, diagnoses, laboratory information, and disposition. Actions include notifying radiologists or others, creating registries, and work queues or dashboards. An annotation says patient factors should come from EHR and other sources separately from report information.[^board] + +The follow-up proposal describes a recommendation with an exam or protocol, timeframe, finding/target, possible conditions or options, and a citation. Completion could be recognized through linked orders, suitable exam types, and timing; an outcome exam need not be exactly the exam named in the recommendation. It connects this work to an IPL whose findings may be live or inactive.[^board] + +For our work, this supplies concrete Use Cases and data requirements for linking recommendations to later events. The cascading-use-cases note moves from individual feedback to practice/population statistics and clinical pathways. These are proposed extensions around shared patient data, not completed capabilities or a settled recommendation schema. The historical patient-factor annotation does not imply separate disconnected graphs in the current Persona. + +Pillar connections: Use Cases; Data Structures. Related working pages: [Use cases](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/use-cases.md), [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md), [Overview](../../../site/content/index.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/68X9qfSP5qA/original.png) · [SVG](../../../sources/linked-diagrams/68X9qfSP5qA/original.svg) · [Source contents](../../../sources/linked-diagrams/68X9qfSP5qA/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/68X9qfSP5qA/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/68X9qfSP5qA/metadata.json). + +[^board]: The captured board, especially Future Clinical Event; Actions; Recommendation Structure; Recognizing Follow-up Exams; Issue of use cases cascading; Imaging Problem List. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/participation-and-demonstrations.md b/docs/references/linked-diagrams/participation-and-demonstrations.md new file mode 100644 index 0000000..40f9866 --- /dev/null +++ b/docs/references/linked-diagrams/participation-and-demonstrations.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Participation, reference work, and demonstrations" +description: "Contributing content, tools, reference work, demos" +tags: ["foundation-context", "sdks", "sample-applications"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/6dKAnPexyN8/ + title: "ACR OIDM" +--- + +# Participation, reference work, and demonstrations + +The board describes a project defining methods and tools for platforms within the imaging ecosystem. Around ACR Data Science Institute and OIDM branding, it groups sponsoring/identity, logistics, volunteer contributions, reference implementations, and demo projects.[^board] + +Volunteer topics include definition/review, indexing, anatomy, exam-to-anatomy connections, use cases, software, documentation, manuscripts, and standards work. Reference components range from anatomy and exam types to definitions, Observations, tracked Observations, studies/reports, and EHR items. Named demo ideas are templating for the SIIM Hackathon, pulmonary nodules for RSNA IAIP, and outcome tracking.[^board] + +For our work, this is an anchor for how people can build content, tools, and demonstrations around the foundation. It complements the SDK and Sample Applications pillars. The board names work areas and demo projects but does not establish completion, formal sponsorship arrangements, available funding, or current institutional commitments. Those claims need their own sources. + +Pillar connections: Foundation Context; SDKs; Sample Applications. Related working pages: [Sdks](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sdks.md), [Sample applications](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sample-applications.md), [Foundation context](../../../site/content/foundation-context/index.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/6dKAnPexyN8/original.png) · [SVG](../../../sources/linked-diagrams/6dKAnPexyN8/original.svg) · [Source contents](../../../sources/linked-diagrams/6dKAnPexyN8/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/6dKAnPexyN8/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/6dKAnPexyN8/metadata.json). + +[^board]: The captured board, especially Sponsoring; Logistics; Volunteer Opportunities; Reference Implementations; Demo Projects. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/pulmonary-nodule-demo-workflow.md b/docs/references/linked-diagrams/pulmonary-nodule-demo-workflow.md new file mode 100644 index 0000000..332b9ff --- /dev/null +++ b/docs/references/linked-diagrams/pulmonary-nodule-demo-workflow.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Pulmonary-nodule demo workflow and role tagging" +description: "Multi-system nodule workflow, semantic annotations, element roles" +tags: ["use-cases", "data-structures", "sdks", "sample-applications"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/2ZpQ4nXotjV/ + title: "Pulm Nodule Demo Project" +--- + +# Pulmonary-nodule demo workflow and role tagging + +The RSNA/ACR AI in Practice demo board proposes exchanging pulmonary-nodule descriptions among AI, PACS, reporting, and EHR tools using CDE-labeled FHIR Observations and FHIRcast. Its seven-step workflow carries AI output through review and reporting, brings prior nodules into the current context, associates observations over time, and returns structured results and text to the EHR.[^board] + +“Smart Annotations” asks a viewer to infer the relevant definition from its tool, anatomy, and permissible measurements, then collect additional descriptors. “CDE 'Role' Standard Tagging” proposes standard codes for element roles such as presence, location, and types of size, so a consumer can find the relevant descriptor without hard-coding a different identifier for each set.[^board] + +For our work, this anchors a concrete interoperation use case and the role-tagging proposal already cited in Relationships. Who accepts edits, assigns tracking identity, and communicates those changes remains open on the board. Treat this as a demo design; the Sample Applications pillar needs separate implementation evidence before describing any named vendor workflow as built. + +Pillar connections: Use Cases; Data Structures; SDKs; Sample Applications. Related working pages: [Relationships](../../../site/content/foundation-context/relationships.md), [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md), [Sdks](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sdks.md), [Sample applications](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sample-applications.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/2ZpQ4nXotjV/original.png) · [SVG](../../../sources/linked-diagrams/2ZpQ4nXotjV/original.svg) · [Source contents](../../../sources/linked-diagrams/2ZpQ4nXotjV/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/2ZpQ4nXotjV/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/2ZpQ4nXotjV/metadata.json). + +[^board]: The captured board, especially Complete Workflow; Questions; Basic Principles; Smart Annotations; CDE 'Role' Standard Tagging. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/radiologist-outcome-feedback.md b/docs/references/linked-diagrams/radiologist-outcome-feedback.md new file mode 100644 index 0000000..9bc5131 --- /dev/null +++ b/docs/references/linked-diagrams/radiologist-outcome-feedback.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Radiologist outcome feedback" +description: "Patient/anatomy/time-based outcome feedback" +tags: ["use-cases", "foundation-context", "data-structures"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/5QMQ1J2FYT3/ + title: "Radiologist Outcome Feedback" +--- + +# Radiologist outcome feedback + +The outcome-feedback proposals ask for later pathology, imaging, or procedures relevant to a patient and body part, either as a notification or a digest of cases a radiologist read within a chosen timeframe. A proposed interface captures requests from the current study, shows new results and cases being awaited, and lets the user choose alert preferences.[^board] + +Anatomy tags on pathology specimens and exam-type coverage help find relevant imaging. Prostate and MSK examples connect pathology extraction, patient identity, anatomy, exam metadata, and time intervals. The biopsy/result sketch keeps cases in a waiting state until an appropriate pathology result arrives, then extracts data and notifies the relevant radiologists.[^board] + +For our work, this is a concrete application idea built from shared anatomy, exam definitions, patient events, and longitudinal association. It supplies a stronger anchor than a generic claim about outcome dashboards. The proposed notification tools, matching rules, and architecture diagrams do not by themselves establish that a sample application was built. + +Pillar connections: Use Cases; Foundation Context; Data Structures. Related working pages: [Use cases](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/use-cases.md), [Anatomic locations](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/anatomic-locations.md), [Exam types](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/exam-types.md), [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/5QMQ1J2FYT3/original.png) · [SVG](../../../sources/linked-diagrams/5QMQ1J2FYT3/original.svg) · [Source contents](../../../sources/linked-diagrams/5QMQ1J2FYT3/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/5QMQ1J2FYT3/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/5QMQ1J2FYT3/metadata.json). + +[^board]: The captured board, especially Proposal for Outcome Tracking; Prostate RadPath; MSK RadPath; Path Result ↔ Biopsy Procedure. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/rsna-exhibit-examples.md b/docs/references/linked-diagrams/rsna-exhibit-examples.md new file mode 100644 index 0000000..3db5411 --- /dev/null +++ b/docs/references/linked-diagrams/rsna-exhibit-examples.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "RSNA exhibit examples" +description: "Comparable observations, multiple producers, AI discrepancies" +tags: ["foundation-context", "data-structures", "use-cases"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/19Qjzb9AmAT/ + title: "Figures for RSNA Edu Exhibit" +--- + +# RSNA exhibit examples + +The exhibit panels illustrate how definitions, Observation values, FHIR representations, and generated clinical sentences fit together. Pulmonary nodule is the expanded example, with presence, composition, size, and location connected to the corresponding components. Other examples include acute aortic syndrome, stroke, and diverticulitis.[^board] + +A source panel feeds one Observation representation from dictation/reporting, DICOM SR, exam metadata, current and prior report extraction, longitudinal findings, and AI. The AI-monitoring panel compares two descriptions of the same finding: the radiologist's nodule is 6.0 mm with a composition value, while the model's is 7.5 mm and omits composition.[^board] + +For our work, these are useful teaching examples of shared semantics making data from different producers comparable. The AI discrepancy is a specific Use Cases illustration, not a reported performance result. The panels use an earlier component-based definition representation. They can inform a new explanation of the graph approach without being presented as the current schema or as proof of an operational monitoring application. + +Pillar connections: Foundation Context; Data Structures; Use Cases. Related working pages: [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md), [Next generation schema](../../../site/content/foundation-context/next-generation-schema.md), [Use cases](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/use-cases.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/19Qjzb9AmAT/original.png) · [SVG](../../../sources/linked-diagrams/19Qjzb9AmAT/original.svg) · [Source contents](../../../sources/linked-diagrams/19Qjzb9AmAT/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/19Qjzb9AmAT/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/19Qjzb9AmAT/metadata.json). + +[^board]: The captured board, especially Figure 1; Figure 2; Figure 3; Figure 4; Figure 5. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/structured-report-representation.md b/docs/references/linked-diagrams/structured-report-representation.md new file mode 100644 index 0000000..22982ad --- /dev/null +++ b/docs/references/linked-diagrams/structured-report-representation.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Structured report representation" +description: "Complex findings, report sections, source-independent representations" +tags: ["data-structures", "foundation-context", "sdks"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/9Jgo8oKswiE/ + title: "Structured Report Representation" +--- + +# Structured report representation + +The board's notes ask how to represent more than simple findings: technique, the radiologist's understanding of patient history, comparisons, complex subparts, causative relationships, grouped findings, broad anatomic negatives, diagnoses, differentials, recommendations, and communication. They distinguish these needs through concrete examples and a chest CT report/finding-list pair.[^board] + +The board calls for appropriate FHIR representation patterns and a handbook for choosing among them. It says representations should be interchangeable across sources, including modality data, AI, technologists, radiologists, and extraction from prior reports. Further notes propose experiments, stronger definition content, and clinical demonstration scenarios.[^board] + +For our work, this is a source for the Observation layer's structural requirements and developer guidance. The common target representation is the reusable idea. Exact FHIR patterns, impression structures, probabilities, grouping, and recommendation forms remain questions on this board. The newer two-graph and shared-schema work supplies the current organizing context. + +Pillar connections: Data Structures; Foundation Context; SDKs. Related working pages: [Data structures](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/data-structures.md), [Relationships](../../../site/content/foundation-context/relationships.md), [Sdks](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/sdks.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/9Jgo8oKswiE/original.png) · [SVG](../../../sources/linked-diagrams/9Jgo8oKswiE/original.svg) · [Source contents](../../../sources/linked-diagrams/9Jgo8oKswiE/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/9Jgo8oKswiE/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/9Jgo8oKswiE/metadata.json). + +[^board]: The captured board, especially Report Finding List. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/linked-diagrams/use-case-development.md b/docs/references/linked-diagrams/use-case-development.md new file mode 100644 index 0000000..e64348c --- /dev/null +++ b/docs/references/linked-diagrams/use-case-development.md @@ -0,0 +1,26 @@ +--- +type: Reference +title: "Use-case development and interaction ideas" +description: "User interactions, report views, spine numbering" +tags: ["use-cases"] +status: draft +generated: { by: codex/gpt-6/2026-10-01-linked-diagrams, at: 2026-10-01 } +sources: + - id: board + resource: sources/linked-diagrams/1lt7q7M1bEz/ + title: "OIDM Use Case Brainstorming" +--- + +# Use-case development and interaction ideas + +The board organizes possible value across reporting efficiency, care-team communication, operations/quality/safety, research, public health, and education. It also groups application ideas by reporting, workflow, AI pipelines, data exploration/outcomes, and multiple report views. The circled question asks how users would interact with each use case.[^board] + +The spine-numbering example makes that interaction concrete: capture prior knowledge from operative or treatment notes, let a surgeon specify anatomy, and allow reporting/viewing tools to map between preferred numbering schemes. Other notes propose use-case metadata, contribution guidelines, stages of development, and an entry index. The “Use Case Definitions” box is empty.[^board] + +For our work, this supports a list-oriented Use Cases pillar grounded in what a person does and which information the tool needs. The detailed assisted-reporting map is also present on the Big Picture board. Keep these ideas as source anchors; the proposed repository/catalog mechanics should not become the organizing purpose of the knowledgebase, and an empty placeholder should not be treated as a defined application. + +Pillar connections: Use Cases. Related working pages: [Use cases](https://github.com/openimagingdata/oidm-knowledge/blob/9725105/knowledge/drafts/use-cases.md), [Overview](../../../site/content/index.md). + +Source captured 2026-10-01: [PNG](../../../sources/linked-diagrams/1lt7q7M1bEz/original.png) · [SVG](../../../sources/linked-diagrams/1lt7q7M1bEz/usable.svg) · [Source contents](../../../sources/linked-diagrams/1lt7q7M1bEz/scene-contents.json) · [Text elements](../../../sources/linked-diagrams/1lt7q7M1bEz/text-elements.json) · [Capture metadata](../../../sources/linked-diagrams/1lt7q7M1bEz/metadata.json). + +[^board]: The captured board, especially Table 1: Value Categories; how do we imagine user interactions; Spine numbering/transitions; Artifacts; Use Case Definitions. Inspect the full-resolution PNG/SVG for layout and embedded material; text-elements.json records searchable text with element IDs and coordinates. Capture date does not establish when an undated proposal was written. diff --git a/docs/references/okf-tooling-comparison.md b/docs/references/okf-tooling-comparison.md new file mode 100644 index 0000000..6a9db0b --- /dev/null +++ b/docs/references/okf-tooling-comparison.md @@ -0,0 +1,69 @@ +# Choosing OKF skills and search tools + +Reviewed 2026-10-01. Follow-up to [the origins review](okf-tooling-review.md). Keep OKF; compare tooling without changing the installation. + +## The current skills are defensible, but not proven best + +The September 20 development log records a comparison between scaccogatto's v0.2 toolkit and the v0.1 skill linked from okf.md. That establishes a version-based selection. It does not establish a comprehensive comparison or prove that the first search result drove the decision. + +Current primary-source comparison: + +| Candidate | Evidence and fit | +|---|---| +| [scaccogatto/okf-skills](https://github.com/scaccogatto/okf-skills) | Our installed toolkit. OKF 0.2 author/maintain/consume, migration, deterministic validator, graph rendering, and history backfill. Uses PyYAML. Good fit with our Python/uv tooling and existing tasks. Current upstream also has MCP, a CI action, plugin hooks, and backfill agent definitions. | +| [lorsabyan/okf-skill](https://github.com/lorsabyan/okf-skill) | Credible v0.2 alternative. Validator, trust/freshness reports, index generator, tests against pinned Google sample bundles, and scheduled spec-drift checks. More validation coverage in the trial below. Its dependency-free parser deliberately handles a YAML subset rather than full YAML. Its skill imposes relative-only links and index-organizing conventions that differ from our current policy. | +| [serradura/okf](https://github.com/serradura/okf) | Broader v0.2 toolkit: separate validate/lint commands, ranked search, trust/status filtering, multi-bundle registry, graph, CLI/library, and optional MCP/TUI. A serious candidate for a unified toolchain, but introduces Ruby or Docker and replaces more of our workflow. Reviewed docs, not executed here. | +| [parkscloud/okf-author](https://github.com/parkscloud/okf-author) and [catancs/okf-skill](https://github.com/catancs/okf-skill) | Current documentation still targets v0.1. Their authoring defaults do not address our v0.2 trust/provenance workflow. No reason to switch to them for this bundle. | +| [copperbox/okf-mcp](https://github.com/copperbox/okf-mcp) | Extensive multi-bundle navigation, remote loading, packing, repair, and authoring tools. Current documentation describes v0.1 validation and timestamp/citations write conventions. Read capabilities merit separate evaluation; avoid adopting its write path for our v0.2 workflow without verification. | + +Recommendation: retain the current authoring and visualization skills provisionally. Evaluate lorsabyan's validator alongside ours before deciding whether to replace validation. Evaluate serradura if we want one tool for multi-bundle search, filtering, lint, and graph navigation. Installing overlapping authoring skills together risks conflicting instructions. + +## What the validator trial actually shows + +I ran the [lorsabyan fixture corpus](https://github.com/lorsabyan/okf-skill/tree/main/benchmark) against our installed validator, current scaccogatto upstream at `8e3187875e66051bb52f91a5ed27342e2c3208da`, and lorsabyan's current validator. The corpus contains 19 deliberately problematic bundles and 10 clean bundles. It was authored by one candidate's maintainer and is a small test, not independent proof of overall superiority. + +| Validator | Problem cases reported without crashing | Clean cases accepted without additional reports | +|---|---|---| +| Installed scaccogatto | 13/19 | 10/10 | +| Current scaccogatto | 13/19 | 10/10 | +| lorsabyan | 19/19 | 10/10 | + +To make the comparison meaningful, I excluded scaccogatto's missing-title and missing-tags warnings, which occur in the unchanged baseline too. Both tools then have a silent baseline. The corpus itself was unchanged. Lorsabyan's clock was pinned to July 30; scaccogatto's checker does not report calendar staleness. The scores count any report, not necessarily the correct diagnosis. Scope differs: scaccogatto intentionally leaves some curation checks to other tools, and our house checker already covers index coverage and stale dates. + +Both scaccogatto versions crash when PyYAML parses an impossible unquoted date. Other unreported cases cover missing indexes, passed stale dates, an undefined/unattributed footnote, an attested computation with no computation, and a dead path-valued field. The crash needs a fix; broader checking needs a policy choice. It is not enough to update the upstream pin and assume these are resolved. + +On the actual bundle, our validator passes with 129 concepts and zero issues. Lorsabyan reports zero errors and 15 warnings for source locators written relative to the repository rather than the bundle. Those are path-contract differences to resolve before replacing the validation gate. Strict mode fails on those warnings. + +The alternative index generator's `--check` proposes changes to all 18 indexes. Inspection shows that it reconstructs headings and recognized list entries but drops surrounding prose. Its preservation promise covers entries, not entire documents. Our root index contains introductory and review-status prose. Do not run this generator in write mode on our indexes without a preservation fix. + +## Useful upstream additions, in priority order + +1. **Pinned updates and drift detection.** Record the toolkit revision, refresh the canonical spec source, and borrow the [lorsabyan scheduled drift-check pattern](https://github.com/lorsabyan/okf-skill/blob/main/README.md#maintenance). Comparing specification content matters because upstream changed timestamp guidance without changing `okf_version`. +2. **Reuse upstream fixtures and tests.** Keep generic conformance covered by upstream tests; test our policy separately. Include the impossible-date case when evaluating an update. Report or contribute the generic validator fix upstream rather than maintaining an unnoticed local fork. +3. **Use the validator's existing JSON output.** It is already installed. CI can consume the report rather than parse terminal output. The [upstream composite action](https://github.com/scaccogatto/okf-skills/blob/main/action.yml) packages validation and a JSON report. It would simplify wiring, not add new validation capability. Pin its commit if adopted. +4. **Complete backfill packaging when needed.** Our backfill skill references `agents/event-analyzer.md` and the `okf:bundle-weaver` agent. Those definitions are not in the installed payload. Vendor the matching definitions or use the reviewed full plugin for a history reconstruction. Do not run backfill on this established bundle just to use the feature. +5. **Expose an upstream MCP reader only for clients that benefit.** The [scaccogatto server](https://github.com/scaccogatto/okf-skills/blob/main/servers/okf_mcp.py) offers search/read/neighbors, including internal source edges. Codex already has file tools; adding MCP does not automatically improve retrieval. For remote clients, configure the actual `knowledge/` path instead of the default `.okf/`. +6. **Trial native OKF filtering for team queries.** Serradura's search supports OKF `--trust` and `--status` directly. That is useful for questions constrained to reviewed content and deserves comparison with a generic search index. + +The full plugin's enforced-upkeep hook only checks that the knowledge log changed alongside code. It does not prove that affected concepts were updated accurately. Our house checker also rejects its root-index `upkeep` field. Adopting that hook is a separate workflow decision, not a prerequisite for better tooling. + +## QMD is a candidate, not the selected search tool + +There are 257 files across `knowledge/` and `docs/` at the review snapshot, including non-Markdown assets. This is small enough to establish a file-search baseline before adding a model-backed index. + +| Need | First candidate | Why / limit | +|---|---|---| +| Known names, exact identifiers, citation IDs | `rg` plus designed indexes | Already available, searches current files, and gives paths/lines. Use as the baseline. | +| Neighbors, backlinks, trust/status constraints | Native OKF tooling | Preserves graph and metadata semantics. Compare serradura and the existing toolkit's MCP reader. | +| Natural-language questions that use different vocabulary | [QMD](https://github.com/tobi/qmd) | Local BM25, vector retrieval, reranking, CLI/MCP, and an existing `qmd bench` command. Quality on OIDM is unmeasured. | +| Section navigation with frontmatter filters | [doctree-mcp](https://github.com/joesaby/doctree-mcp) | BM25 plus heading-tree navigation and frontmatter facets without embedding models. Its own competitive claims are not independent benchmark evidence. | +| Searching original PDFs, decks, websites, and repositories | [docs-mcp-server](https://github.com/arabold/docs-mcp-server) | Broader ingestion formats and sources. More relevant than Markdown-only retrieval if raw-source access is central. Extraction quality, source locators, and indexing scope need testing. | +| People searching the public website | Quartz search | Already integrated. A local agent-search tool is not a replacement for the public-site search experience. | + +QMD's full pipeline downloads three local models totaling about 2 GB. Index refresh and embedding refresh must be managed. Keyword-only mode avoids the model pipeline, but then the comparison is against existing exact search and lighter BM25 tools. + +More importantly, [QMD metadata filtering](https://github.com/tobi/qmd#metadata-filtering) requires an opt-in `qmd.metadata` block with flat typed values. It does not automatically derive trust or freshness from our nested `verified` and `sources` fields. An adapter could produce those filters, but that would add code. Keep retrieval separate from the decision about whether a claim is verified and current. + +Choose search with real tasks: exact terminology, paraphrased concepts, questions requiring multiple linked pages, source lookup, reviewed-only constraints, and absent-answer cases. Label the expected evidence files first. Compare the current agent/file workflow, native OKF search, and QMD or doctree; add broad-source ingestion only if required. Measure evidence recall, citation correctness, refresh after an edit, resource use, and whether extra tool calls help. QMD already provides a benchmark runner, so there is no reason to invent one before trying it. + +No search engine was installed or benchmarked in this review. The validator trial does not establish retrieval quality or authoring quality. The right search choice remains open until its intended consumer and actual failures are clear. diff --git a/docs/references/okf-tooling-review.md b/docs/references/okf-tooling-review.md new file mode 100644 index 0000000..621f997 --- /dev/null +++ b/docs/references/okf-tooling-review.md @@ -0,0 +1,52 @@ +# OKF tooling origins and reuse options + +Reviewed 2026-10-01. This is an investigation and recommendation, not a tooling migration. Research used Exa, Ref, upstream documentation, GitHub metadata, and direct file comparisons. Technical-writing and unslop skills guided the report. + +The [follow-up comparison](okf-tooling-comparison.md) evaluates other v0.2 skills, runs validator fixtures, and qualifies the initial QMD recommendation. This origins review establishes where our tools came from; it does not establish that they are the best available. + +## We adopted the format and core tools + +OKF comes from Google Cloud. Its current canonical home is [GoogleCloudPlatform/open-knowledge-format](https://github.com/GoogleCloudPlatform/open-knowledge-format). The [old knowledge-catalog README](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/README.md) explicitly says its OKF directory is a frozen snapshot and directs users to the new repository. Our vendored specification still names the old location. + +The four installed skills come from [scaccogatto/okf-skills](https://github.com/scaccogatto/okf-skills): author/maintain/consume, validate/migrate, visualize, and backfill. The development log records their selection on September 20. All 13 files under `.agents/skills/` match upstream commit `d8393f329c97836980566c1cf4e4aa4fe47dc111` byte for byte. That was upstream's latest commit before September 21. This establishes that the installed files are upstream copies with no local edits. + +Upstream main was `8e3187875e66051bb52f91a5ed27342e2c3208da`, dated September 28, at review time. Nine installed files differ from that newer revision. Those differences reflect upstream updates, not a local fork. Keep future imports pinned and record their source revision. + +## What is specific to this repository + +| Component | Origin and coupling | +|---|---| +| `.agents/skills/` | Unmodified upstream generic OKF tooling. Works on other bundles. | +| `tools/check_bundle.py` | Local policy, adapted from ACR-RSNA-CDEs. Fixes the allowed document types, requires title/description/generated metadata, demands index coverage, checks links, and scans for email addresses and locally denylisted terms. Default paths are project-specific. | +| `tools/verify.py` | Local implementation of a reusable review action. Writes verification and stable status; defaults to `knowledge/` and the project lead's actor identity. | +| `site/okf-meta-plugin/` | Local Quartz integration for generic OKF fields. It has no imaging-domain model. | +| `tools/prepare_site_content.py` | Local adapter that normalizes Markdown links for our Quartz build. The transformation is reusable; its default paths are local. | +| `Taskfile.yml`, CI, site config, deployment | Project wiring around external tools. | +| `knowledge/` organization and content | Project-specific terminology, five pillars, references, authoring policies, and review decisions. | + +The strict house rules are stronger than OKF conformance. OKF deliberately accepts unknown concept types and missing optional metadata. Our repository chooses a controlled vocabulary and requires richer metadata. Replacing its checker with a generic OKF validator would remove those policies. + +## Existing tools worth using + +| Tool | Fit and recommendation | +|---|---| +| [okf-skills](https://github.com/scaccogatto/okf-skills/blob/main/README.md) | Best direct fit, already installed. Current upstream also offers a composite CI action and a read-only MCP server with search, read, and graph-neighbor tools. The MCP server existed by the installed revision but was not included in our skill-only installation. Review newer validator/spec/template updates and import a pinned revision. Use MCP when a client cannot access the files directly. | +| [Google's reference implementation](https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/README.md) | Canonical spec, a reference producer using ADK/Gemini, BigQuery metadata ingestion plus web enrichment, and a graph viewer. The README calls the producer a proof of concept. Its data-catalog workflow does not directly replace our manuscript, diagram, and repository-source curation. | +| [Quartz](https://quartz.jzhao.xyz/) | Already provides the website, search, backlinks, and graph. Keep reusing it. Our OKF metadata component is a small integration rather than a home-grown site generator. | +| [QMD](https://github.com/tobi/qmd) | Strong adjacent candidate for agent retrieval. Indexes existing Markdown and offers local keyword/vector search, reranking, CLI output, and MCP. It can supplement OKF without changing the document format. It does not replace provenance validation or human review. Evaluate only if retrieval is a concrete problem. | +| [Basic Memory](https://github.com/basicmachines-co/basic-memory) | More extensive Markdown knowledge-management and MCP workflow, including graph traversal and semantic search. Its [knowledge format](https://docs.basicmemory.com/concepts/knowledge-format) adds permalinks, categorized observations, and typed wikilink relations. Those conventions require a compatibility trial against our ordinary Markdown links and OKF metadata. It is a larger workflow change than QMD; do not assume it is a drop-in replacement. | + +The direct OKF ecosystem is young. Google's reference producer is explicitly experimental, and okf-skills is pre-1.0. Broader Markdown tools provide more retrieval and publishing functionality, but their support for Markdown does not establish OKF trust or attestation semantics. Popularity alone does not establish suitability. + +## Recommended direction + +Keep OKF and the upstream skills. Update the vendored source references and skills through a reviewed, pinned import. Keep project policy distinct from the generic validator; reduce duplicated metadata checks where upstream already covers them. Continue using Quartz for presentation. Consider QMD for retrieval before building a custom search service. Basic Memory deserves a trial only if we want its broader shared-memory workflow. + +The following differences need attention during that follow-up: + +- Current upstream accepts ISO 8601 timestamps in `stale_after`, source `last_modified`, and usage windows, while our older skill/spec uses date-only guidance. The local house checker accepts only date strings for `stale_after`, so an upstream update needs coordinated policy and documentation review. +- `site/okf-meta-plugin/dist/components/index.js` only renders `verified` arrays. OKF also permits a single mapping. +- The same plugin treats a date as stale strictly after that day. Our vendored spec and house checker use on-or-after semantics. +- The house checker permits only `okf_version` in root-index metadata, while the upstream plugin uses an additional `upkeep` opt-in. That is a policy incompatibility if we adopt the plugin's enforced-upkeep mode. + +These are findings, not changes made by this review. No tool installation, bundle rewrite, commit, or publication occurred. No reader-facing changelog entry is needed. diff --git a/docs/references/project-direction.md b/docs/references/project-direction.md new file mode 100644 index 0000000..4e0abd3 --- /dev/null +++ b/docs/references/project-direction.md @@ -0,0 +1,183 @@ +--- +type: Reference +title: Project direction and source-review record +description: Dated project-lead guidance and the source-review snapshot retained from the original planning records. +tags: [provenance, project-direction] +status: draft +generated: { by: codex/gpt-6, at: 2026-10-02T21:35:54Z } +sources: + - id: owner-sessions + resource: scope:project-lead-planning-sessions-2026-09-20-through-2026-10-02 + title: Project-lead statements recorded in the original build, layout, and layer plans + - id: original-records + resource: https://github.com/openimagingdata/oidm-knowledge/tree/7f5e816aae6defedf675ba0cd517fbe3219cab41/docs/plans + title: Planning records before consolidation +--- + +# Project direction and source-review record + +This reference preserves evidence cited by the knowledge pages. It is not an execution plan. Dates identify when a statement was recorded; source-review observations are historical and require rechecking before claims of current implementation. The single implementation plan is maintained outside the bundle in `docs/plans/implementation.md`. + +Later decisions govern current work. The October pipeline retains the knowledge bundle separately from presentation pages. The October 2 clarification makes the bundle repository-only, superseding the proposed internal website. The old page-replacement instructions, page budgets, agent assignments, and deployment instructions are retired. + +## The project lead's stated goals, September 20–21 + +Recorded verbatim from the 2026-09-20 request that started this work, so that documents citing `scope:grilling-session-2026-09-20` have a written source. Only spelling was normalized. + +- **Open Imaging Finding Models.** "Definition formats, needs overhaul for increased metadata and interesting relationship/graph work coming from CDEs." "Tooling for working with these, using them, probably needs corresponding overhaul." "Actual content efforts." +- **ACR/RSNA Common Data Elements.** "Should dovetail with the OIFM work." +- **FindingModelForge.** "An authoring tool for creating OIFMs / CDEs." +- **Imaging Problem List.** "A format for representing a patient's imaging history." "Define appropriate data structures for representing both observations in radiology reports and their relationships." "Manage them (and appropriately collate them) across the patient's imaging history." "Design interesting applications that use this tool and demonstrate how this works." "Create standard data structure formats, etc. for working with these." +- **Anatomic Locations.** "There are repos, and this will be blended in with RadLex." "Also needs tooling for people working with them." +- **Exam types.** "Needs wrapper tooling around the knowledge graph represented by RadLex LOINC playbook." "Need to define the PREFERRED high-level entries like 'CT Chest', 'MRI Brain', 'X-ray Knee'." "Need to TIGHTLY wrap up with Anatomic Locations, including edges for both 'always included' (which can include hierarchies) as well as 'usually included' (for edge things) and something like 'POSSIBLY included, would have to check'." +- **Content direction (2026-09-21).** "In findingmodels, there's a recent taxonomy branch with new files in lists that's the immediate direction for all of the content, probably replacing a lot of the Gamuts-based models." The taxonomies are to be called the "MGB exam-oriented sub-taxonomies." + +## Content decisions, September 21 + +| Topic | Decision | +|---|---| +| Manuscripts in the bucket | Paraphrase facts, structures, and numbers; cite as "manuscript under review at JDIM, 2026" or "prepared for submission"; never quote; reviewer correspondence never reproduced or paraphrased. | +| Excalidraw boards | Publish the public-facing boards (object model, structured report sections, outcome tracking, exhibit figures, lineage, use-case notes). From internal boards, publish only schema and data-model ideas with organizations named; people, meeting histories, and vendor demo logistics omitted; the rest recorded as reviewed and held. | +| Diagrams | No Mermaid. Excalidraw files rendered to SVG beside each document. Per diagram, a builder is the source until the file is hand-edited, after which the builder is retired (tools/diagrams/README.md). | +| Vendor name in the deck extract | Generalized to "a reporting vendor". | +| Upstream defects | Drafted for the project lead's review before any issue is filed. | +| No specification exists | The project is in a coalescing phase; nothing is formally defined. The knowledgebase presents each source's current version of a structure side by side (repo branch and commit, manuscript under review, deck or webinar, board), dated and attributed, and states disagreements (for example the three Imaging Problem List status vocabularies) without resolving them. Words like "specification" or "canonical model" are not used for any of them. | +| Data structures versus transport | Per the project lead's 2026-09-19 manuscript notes: OIDM defines a system of data structures for imaging exam result information used inside applications; FHIR and DICOM are transport expressions designed separately and guided by the structures. The data-structure documents are to be reframed accordingly. | + +## Decisions of September 22 + +These supersede the four-pillar page list that stood here earlier today. The owner's words are quoted; everything else is the two sessions' shared understanding, confirmed in conversation. + +1. **Five named pillars. Use the names, never numbers.** "1. Foundation Context / Semantic layer: includes both the schema/meta-definitions as well as the efforts to build out content. 2. Data Structures, most ESPECIALLY in the Observation layer, and how there are two graphs and they work together. 3. SDKs, the tools that wrap the data structures and enable developers working in the application layer to manipulate instances in real-world applications (and some around helping with definition of the foundation context, obviously). 4. Theoretical Use Cases: Imaging all the different kinds of applications/tools/plugins/output products we could create using an ecosystem built around this foundation. 5. ACTUAL sample applications we've created to try to illustrate some of these ideas." The fourth was then shortened to **Use Cases**. So: **Foundation Context**, **Data Structures**, **SDKs**, **Use Cases**, **Sample Applications**. +2. **A CDE and a finding model are the same content.** "A 'CDE' and a 'Finding Model' are the same thing, but they're in different repos. Open Imaging Finding Models are in the OIDM-maintained repo, and are intended to be very INCLUSIVE, can almost be drafts. Common Data Elements live in the ACR/RSNA repository, and are intended to be WELL-REVIEWED. They're more like something published. They BOTH should soon be using the schema and classes being defined in the CDE schema re-write, with FindingClass, DataElement, Measurement, etc." Consequence: explain the common model once, then the inclusive OIDM collection and the well-reviewed ACR/RSNA collection and their relationship. Collection expectations are not per-item facts. Migration is intended, not complete. The "Observation Type" umbrella name the sessions had floated is withdrawn as unnecessary and was never approved. +3. **The OIFM reorganization around the graph is urgent.** "OIFMs VERY soon need to get re-organized around the graph-based approach we're developing in the next-gen-schema effort in CDEs. They're currently document-oriented and we need to fix that SOON." A change of organization, not added fields. Stated prominently under Foundation Context; current implementation kept distinct from direction; no storage, schema, or migration design invented beyond the source work. +4. **Source eligibility rule.** Repository documents, prompts, plans, skills, ADRs, and code are team artifacts eligible for substantive content. They are not demoted for being agent-assisted, and no statement needs separate human approval to be cited. Committing a document does not make a draft proposal or one implementation project-wide consensus. Each claim carries its source's actual status, date, and any explicit provenance label the source supplies (the CDE decision record's OWNER and CLAUDE DEFAULT, for example). Differences between sources and the owner's later corrections are preserved, not reconciled. No speculative authorship audit. +5. **Terminology.** Foundation Context (shared definitions, relationships, references) and Patient Context (this patient's imaging results plus relevant clinical context), from the SIIM 2026 deck. The CDE report graph, the Imaging Problem List representations, and the broader Patient Context are *related representations* of the Observation idea, never "the same thing serialized differently". Foundation Context and Data Structures correspond to the two graphs at the explanatory level; the report graph is the imaging-assertion part of Patient Context, not a clinical-context schema. The deck's "connective tissue" means relationships and citations between foundation concepts and is used only in that sense. Retained artifact names are used where a source's current files are cited: OIFM finding model, CDE set and element, the next-generation FindingClass, Diagnosis, Grouping, DataElement, Measurement. +6. **Organizing method** (agreed with Codex). The introduction explains the two-graph connection once within the five pillars. Idea anchors carry the recovered substance. Patient-side ideas say which shared definitions they reference; foundation-side ideas say how observations use them; related anchors link both ways. Existing worked examples are used for what each actually shows (the CDE pyelonephritis report and the deck's calculus figure show both graphs; the IPL example2 study is patient-side only; the deck's aneurysm and kidney tables are foundation-side only). Sources attach to ideas; repositories do not become chapters. Redundant generated exposition is deleted after its unique ideas are recovered; unique team-authored material is kept and counted in the budget. A glossary or question register is not a mandate to duplicate content. + +## Presentation guidance, September 22–30 + +Project lead, 2026-09-27 to 2026-09-29, on the Finding Models and Common Data Elements hub page: + +> I think what we want to say is that the finding/diagnosis definition is an unified anchor for the CONCEPT of a finding/diagnosis in a form that can be used to attach associated context: name, synonyms, definition, attributes, etc. OIFM are AN inclusive collection, intended for rapid prototyping... ACR/RSNA Common Data Elements (always include the organizations) are the well-reviewed... published through radelement.org. Forget about talking about the current schema's sets and elements--waste of bits. + +> "Finding Models and Common Data Elements" / "Definitions of findings and diagnoses anchor these concepts in a form that associated content can..." + +> "...maintained by the OIDM project..." / "The OIFM repository is intended for rapid, community-based prototyping and iteration based on an open-source model." / "...with changes proposed by anyone." / Take out "uneven" / "in the project lead's words" is RIGHT OUT. I think the thing to emphasize is that they will have the same schema and describe the same ideas. Major difference is that OIFMs is much more "beta"/rapid additions and updates and CDEs is much more "release"/reviewed and stable. + +> I think we should also emphasize that we will maintain cross-links: when something migrates to CDEs, it will continue to be maintained in the OIFM repo but have a reference to its CDE identity. "Both CDEs and OIFMs are document-oriented today, and moving..." + +The agreed hub text (2026-09-29) is the one the Opus agent writes verbatim into `site/content/foundation-context/finding-models-and-cdes.md` (moved 2026-10-05; originally `knowledge/drafts/finding-models-and-cdes-hub.md`). + +Project lead, 2026-09-30, on figures: "Let's make the AssessmentSchemes ovals rather than diamonds--those have a flowchart implication that doesn't obtain here." Recorded as a convention in tools/diagrams/README.md. + +Project lead, 2026-09-30, four prose decisions on the relationships page: (1) change the associated-findings example to a pair the graph records as OCCURS_WITH; (2) the renal abscess clause becomes "...may have sequela beyond the course of the acute pyelonephritis episode."; (3) "whether or not one causes the other"; (4) use the schema's specificity words: pathognomonic, highly suggestive, suggestive ("they're cooler"). + +Project lead, 2026-09-30, on the pulmonary nodule neighborhood figure (illustrative; reflects updates the project lead, as the CDE project's clinical expert, intends for the CDE graph): rename "attenuation" to "composition" and "pulmonary margin" to "margin"; drop "lesion count"; "ground glass nodule" for "non-solid pulmonary nodule"; add the Grouping "pulmonary parenchymal abnormality"; add the diagnoses "pulmonary neoplasm" and "pulmonary granuloma" (subtype of the grouping); add "Fleischner criteria" as an assessment scheme; add subspecialty; show SEEN_ON CT, XR, and MR; no values; no time course or etiology for now: "I'm starting to think that may need to be a property of the edge connecting it to a diagnosis. Actually, that's the same for etiology." + +Project lead, 2026-09-30, on the relationships page: + +> Emphasizing that they're "strictly apart" makes it seem like THAT'S the issue. / Let's just call this subsection "Findings <-> Diagnoses: Manifestation and Causation" / "Pyelonephritis APPEARS AS striated nephrogram..." "when the pyelonephritis resolves, the striation goes with it..." / Next subsection called "Deriving the Differential" / "The schema distinguishes manifestation and causation to enable inference:..." / We should ALSO include the idea of associated findings here, and the properties of the edges themselves that get at how commonly findings/diagnoses are associated in the specified way / Don't include the "HPO and Orphanet" reference / "a pulmonary nodule definition specifies how a pulmonary nodule WILL BE DESCRIBED. The assessment is a separate assertion, defined by the AssessmentScheme definition, which allows for evaluation of the nodule according to the defined criteria." / "...by an ASSESSED_BY edge" + +Project lead, 2026-09-30, on the next-generation schema page: + +> "Both Open Imaging FInding Models and RSNA/ACR Common Data Elements are moving..." / Show a link to the ACR/RSNA CDE schema repository, on the next-gen-2026 branch. / "...as a graph of interconnected concepts rather than a set of documents." / "Applications will be able to use either or both collections at once via a common SDK based on the new schema." / "FindingClass" defines the properties of the items described in imaging reports. A "Diagnosis" is a separate, closely-related node type, distinguished by a different set of possible relationships. Groupings are broad classes of findings/diagnoses such as "renal abnormality", usually intended to be used in the negative. / For most of these node class definitions, we want to use "defines" rather than "is a" (this is a very tricky but important semantic distinction) / "AssessmentScheme" definitions layout the systems such as Lung-RADS via which a constellation of other findings is evaluated. / "The anatomic scope property of each other node indicates the locations and/or structures in which they may be localized (e.g., "lung parenchyma" for a pulmonary nodule). / "Edges integrate the notes into a rich fabric of interrelated concepts, enabling reliable, reproducible inference." / "Edges bind FindingClass and Diagnosis definitions to their anatomic scope and to the DataElements and Measurements used to specify their attributes. The edges themselves may contain properties that provide additional detail about the relationships between nodes." / Concept nodes as a note below the list, introducing the paragraph that also talks about anatomic scope. / Use the actual table of relationship types as examples. / "Evolution of CDEs and Finding Models: Both CDEs and existing Open Imaging Finding Models will have to migrate from their current JSON document-based organization to a new graph-oriented format, likely using RDF/OWL as an underlying storage format. More ergonomic data structures will also be developed for use in SDKs and via an API." + +Project lead, 2026-09-30, on the SDKs (for the pillars figure and the SDKs index): "I think we should also say that the SDKs help AUTHOR and MAINTAIN the underlying foundation context." + +Project lead, 2026-09-29, on Measurements (for the three-axes figure and the next-generation schema page): + +> Measurements don't have EXPLICIT units, they indicate what KIND of thing they are: length (with appropriate length units, which could be mm, cm, whatever), CT density (HU), or count (unitless), ratio (unitless), velocity (m/s, cm/s), volume (cc, mL, mm^3..., L) + +Project lead, 2026-09-24, on the Overview page: + +> OIDM is NOT just for modeling imaging results, it is for modeling the ENTIRE imaging workflow context throughout its lifecycle. It's meant to enable a common platform across the medical imaging ecosystem that enables tools that will assist radiologists, technologists, ordering providers, and back-office staff. RIGHT NOW, we happen to be focused on data structures and semantics for representing imaging results, whether from current exams or prior, and from AIs, radiologists, technologists, or modalities themselves, and for integrating those results across time. We will ALSO model the broader patient context and enable linking between imaging results and patients' OTHER diagnoses, procedures, and issues. + +> The imaging persona doesn't place the rest of the patient context "alongside"--it integrates them into a single, larger graph, underpinned by the Foundation Context. Basically, the imaging results can be integrated into the larger fabric of the patient's history. + +> Let's call it "finding/diagnosis definitions" since they're used so interchangably. It really might be that "ObservationType" is the better catch-all term. + +> "Pointers" is a... well, pointless inclusion. The idea is that the patient graph is deeply interconnected with the Foundation Context graph, attaching baseline clinical knowledge to the contingent patient facts. [...] every finding, diagnosis, location, and exam type. + +Source added by the project lead on 2026-09-24: `~/exam-types/sources/LoincRsnaRadiologyPlaybook.xlsx` (44,178 Playbook part rows with LOINC numbers, part types, RadLex IDs and preferred names; a second sheet `core-playbook-dev`, 995 rows) for the exam-types page and diagrams. The ACR-RSNA-CDEs next-gen graph (`docs/next-gen-schema/graph/*.jsonl`, `alpha/graph/definition-graph.json`, `diagrams/*.svg`) supplies real nodes and typed edges for the Foundation Context figures. + +Wording rules from these: "finding/diagnosis definitions" is the term for the WHAT axis for now ("Observation Type" is under consideration as the catch-all, not adopted); the connection between the graphs is described as interconnection that attaches knowledge to facts, never as pointers; the Imaging Persona integrates imaging results into one larger graph of the patient's history. + +Project lead, 2026-09-23, correcting the first version of this plan: + +> I think in the foundation context page, we need the document on finding models to be something that encompasses both OIFMs (and that repo) but also the RSNA/ACR Common Data Elements and the relationshpi between the two. We should ALSO think about how to present the work on the next-generation schema (which will be for BOTH CDEs and OIFMs--I think applications will be able to use both at once). + +Project lead, 2026-09-22: + +> There should be a big overview page, layout out a HIGH-LEVEL justification of the project, talking about what the CORE PIECES of it ARE (not the DETAILS, just basics about what they ARE). These are the first three pillars: foundation context, data structures, SDK. +> Then the next couple of things: start with some basics of USE CASES. We can have a LIST oriented view of cases, only SOME of which have a more fleshed out page. +> We can also have a list of our sample/demo applications, some of which are RELATED to the use cases. +> There should be a directory on the foundation context, with some documents on the key ideas related to FindingModels/CDEs and the definitions that go into them. There should be a page on the OIFM repository (with a node to its relationship to the CDE project). Tehre should be a page on AnatomicLocations, referencing existing work but also pointing to the fact that we're migrating to incorporate with RadLex. We can also describe the ExamType content to come. + +And, on the shape: no page budget; the number of pages follows from the ideas. "Repository pages" are not a pattern: the one page on the OIFM repository exists because that repository *is* the inclusive collection of finding models. + +## Source pipeline, October 1 + +> I'd like to have a literal knowledge bundle of OKF files which are mostly for internal team use. They're in a public repo, and anyone can look at them and see what they see, but they're not what we'd present. We'd ALSO like to have a series of interconnected pages that ARE intended as the outward presentation of these ideas, where the knowledge for them is a distillation of the broader knowledgebase to make it more accessible to outsiders. + +> I would ALSO like to maintain something like source material. I will create a non-public bucket on tigris for raw source material (presentations, manuscripts, emails, etc.). I (or people I give appropriate credentials to) can access that bucket to fill out a raw-sources directory, but this is NOT in the repo. We will generate DETAILED distilled summaries with key quotes and ESPECIALLY extracted graphics into a references directory. We can cite these in our knowledge bundle as needed, and if required go back and dig into the raw source. + +> I would promote "paraphrase and describe figures" to "generate new versions of the figures capturing key points". + +## Repository-only knowledge and one plan, October 2 + +The project lead described the intended pipeline as: + +> a Tigris bucket down to processed summaries to a repo-only OKF knowledge graph to the actual pages based on them + +The project lead asked to replace conflicting planning documents with one coherent plan, discard superseded instructions, and integrate useful material into a compact set. Temporary files may live in an ignored directory exposed through `.peruseshow` where review is useful. + +The old glossary was dropped by the project lead on September 29. New terms grow from the new pages as needed; incoming links must be repaired before removal. The reporting vendor and the organizations behind the 47 and 31 finding models remain generalized. The project lead permitted reuse of the SIIM deck and CDE-repository figures on September 29. + +## Source-review snapshot, September 20–22 + +The following is retained from the original build plan because existing pages cite its inventory and work-edge observations. It is a dated research snapshot, not a fresh source audit or a current work queue. Broad absence claims below describe that review and must be checked against the later idea inventory and corrected drafts before reuse. + +Inventory reports for every source live in the session scratchpad and are summarized here. "Read from" names the branch used for current state. + +| Source | Role | Read from | Key migration candidates | +|---|---|---|---| +| findingmodel | OIFM library, `anatomic-locations` package, CLIs, MCP server | `main` (2026-03) for released state; `dev` and `feature/metadata-cleanup` (2026-06-29) for the work edge | `docs/anatomic-locations.md`, `docs/manifest_schema.md`, `.claude/skills/manage-anatomic-locations/reference/*`, `tasks/done/anatomic-locations-normalized-schema.md`, enrichment PRD and plans, schema-versioning notes, `notebooks/data/anatomic_locations_noembed.json` (2,926 records, current anatomic data); on the work edge: `docs/canonical-structured-metadata-and-enrichment-rewrite.md`, ADRs 0001 to 0003, `tasks/canonical-structured-metadata-implementation-plan.md`, `docs/plans/metadata-enrichment-current-plan.md` | +| findingmodels | Content: 2,382 finding model definitions (1,933 Gamuts-derived, 256 OIDM, 115 CDE, 47 MGB, 31 MSFT), `ids.json`, MGB exam-oriented sub-taxonomies | `taxonomy-export-2026-08-15` (one commit ahead of `main`) for content direction; `main` for the corpus; content branches for in-flight batches | `schema/finding_model_schema.md`, `lists/README.md` and the six taxonomy CSVs (3,789 rows, 1,028 matched), `docs/plans/definition_cleanup.md`, `docs/plans/rebuild-finding-skills.md`, `docs/plans/findingmodel-upstream-proposals.md`; on branches: `content/chestcts` PLAN and batch progress, `findingmodels-metadata` plan history and schema changes, `content/headcts` | +| med-ontology-lookup | `molu` terminology lookup across RadLex, SNOMED CT, FMA, LOINC, UMLS | `main` (2026-09-20); no unmerged branches; offline-CI work uncommitted in a worktree | `docs/product-roadmap.md` (contains the only written exam-type design), `docs/project-review-and-proposal.md` (six-phase design), `docs/plans/2026-09-05-github-issue-backlog.md` | +| FindingModelForge | Authoring web app at fmf.oidm.org | `dev` (2026-01) is ahead of stale `main`; `feature/model-editing` (2025-11) | `docs/finding-model-creation-workflow.md`, `docs/DRAFT_WORKFLOW.md`, `docs/feature_finding_model_draft_saving.md`, `zOld/plans/*` (history), issue #13 model editing | +| CDEStaging | 259 informally authored CDE definitions upstream of RadElement | `main` (2025-11); all recent work lands on `main`; 11 topic branches from 2024 never merged | `docs/report_extraction_process.md`, `report_representation/{negative_statements,uncovered_findings,missing_attributes}.md`, `definitions/upmedic/roadmap.md`; the unmerged `report_representation` and `gamuts_work` branches noted in history | +| finding-models-site | Static catalog reader for findingmodels | `main` | Nothing beyond a profile entry | +| imaging-problem-list | Exam Finding List and Imaging Problem List spec, viewer, sample data; on `dev`, a full extraction, coding, persistence, and review platform | `dev` (2026-07-21, 258 commits ahead of `main`) for current state; `main` for the stable spec; the one feature branch is empty | `README.md`, `CLAUDE.md` (domain model), `docs/anatomic-location-assignment-rules.md`, `docs/technical-imaging-findings.md`, `docs/plans/anatomic-location-efl-ipl.md`, `docs/archive/finding-and-location-code-assignment-plan.md`, `initial-extraction-plan.md`, `docs/plans/viewer-v2-anatomy-dashboard.md`, `docs/plans/extractor-evals-redesign.md`, the extraction-reviewer plans, `prompts/validator_prompt_example.md`, issue #1 "Create System of Data Models", both PNG diagrams, sample data | +| IPL-MVP-ExtractionAndLabeling | Early 4-stage extraction and mapping pipeline | `main` (2025-10); three single-commit experiment branches | `README.md`; `FHIR_Example_Structure` on `Persistent_FHIR_Resources` (a design note without a `.md` extension) | +| ACR-RSNA-CDEs | Allied external project. `next-gen-2026` holds the project lead's next-generation vocabulary work | `next-gen-2026` (2026-09-15) | `CONTEXT.md` glossary terms; `notes/oifm-overview.md`, `notes/oifm-metadata-fields.md`, `notes/oifm-schema-v2-draft.md`, `notes/ihe-idr-extract.md`, `notes/hood-taxonomies-profile-2026-09-01.md`; the ideas in `docs/next-gen-schema/00, 01, 03, 07, 11` summarized as goals; `docs/check_bundle.py` conventions. Committee minutes and email extracts stay in the CDE repo. | +| RadLex (RSNA) | Allied external project: ontology (`RadLex.owl`, 46,899 RIDs) and a search pipeline repo | `main`; GitHub issue #3 "Add Anatomic Locations" | `docs/plans/hybrid-fts-search.md` (referenced, not migrated) | +| anatomiclocations.org, BodyPartIndex.py, BodyPartIndex.ts | Original curated anatomic location set (2,890 nodes) and wrapper libraries | `main` of each (2022 to 2024) | `docs/index.markdown` (rationale, exam-type roadmap line), `docs/code.markdown`, `data/body_parts_schema.json`, `BodyPartIndex.py/TODO.md` | +| OpenImagingDataModel.py and .ts | Lineage reference implementations | `main` | `observation.py` and `finding_model.py` shapes as FHIR precedent; Zod schemas as early types | +| FHIRSamples | Lineage: lung screening DiagnosticReport plus chained CDE-coded Observations | `main` | All JSON examples as FHIR mapping precedent | +| UseCases | Lineage: ~25 use case ideas and the six RSNA value categories | `main` | `Index.md`, `README.md` | +| openimagingdata.org (repo) | Lineage: old site scaffold and schema fixtures | `main` | `schemas/schema_differences.md`, sample JSON | +| openimagingdata.org (Ghost site) | 15 posts (2023-06 to 2025-03) and an About page | live | "Findings, CDEs, and Observations", "Data Model: Structure and Function", "Benchmarking a Vision", "OIDM-Based Next-gen Reporting Assistance Framework", the SIIM 2024 posts | +| CDETemplateDemo, SARTemplatesToCDEs, template_extraction, ReportFindingRefiner, get_ontology_findings, ontology_tools, common_data_elements | Lineage | `main` | `SARTemplatesToCDEs/RectalCAStaging/Changes_vs_RDES11.md`, `ReportFindingRefiner/README.md`, `common_data_elements/README.md` and schema versions; the rest as one-paragraph history entries | +| Gamma deck, January 2026 | Vision and roadmap | live | Whole deck as a Presentation Extract | +| SIIM 2026 deck, June 2026 ("Reports of the Future") | The framing: Patient Context and Foundation Context; the three axes (Observation Type, Anatomic Location, Exam Type) as OIDM layers over CDE/OIFM, RadLex, and the Playbook; relationships and external citations; SDKs that resolve codes; the ask | `sources/bucket/siim2026/` (public talk) | Whole deck; the slide 7 table and the foundation-context graph as figures, at the project lead's discretion | +| oidm-knowledge-sources bucket folder | Manuscripts (JACR IPL; two JDIM under review), the AI-evolution essay, the reporting-schema deck, the July 2026 SIIM webinar deck | `sources/bucket/REPORT.md` | Paraphrase and cite per the manuscripts rule; reviewer correspondence never used | + +### The work edge + +The project lead asked that the knowledgebase reflect the entire work edge, not only default branches. Cite the branch and pin the commit they read for each account of current state. + +- **findingmodels.** The MGB exam-oriented sub-taxonomies on `taxonomy-export-2026-08-15` are the immediate direction for all content and are expected to replace many of the Gamuts-derived models, which make up 1,933 of the 2,382 current definitions. Rows without a matched OIFM ID are the next triage task. Three content pipelines (`content/chestcts` as the live CT-chest front, `content/headcts`, `km`) and a metadata-enrichment branch touching 78 models are in flight, none merged. The finding model schema is being extended on three uncoordinated branches with nothing landed on `main`. +- **findingmodel.** The live thread is the canonical structured-metadata rewrite on `feature/metadata-cleanup` and `dev`: eight new optional fields (body regions, subspecialties, etiologies, entity type, applicable modalities, expected time course, age profile, sex specificity), a multi-agent enrichment architecture, and a dual-database release plan. The older eight-facet specification is superseded. The branch's own readiness assessment is not yet passing. Twenty-four open issues map one-to-one onto task plans. +- **imaging-problem-list.** `dev` is current. Eight active plans center on human-review tooling, an honesty-first evaluation redesign, an anatomy-aware viewer, and local-model strategy. Anatomic-location coding of findings has landed. Issue #1, a formal Pydantic model layer for Observation, Exam Finding List, and Imaging Problem List with JSON Schema export, is broader than any current plan and unclaimed. +- **med-ontology-lookup.** No unmerged branches. A twelve-issue backlog follows the six-phase design document. Offline CI is implemented but uncommitted. +- **CDEStaging, IPL-MVP-ExtractionAndLabeling, FindingModelForge.** Work lands on `main` in CDEStaging with stale 2024 topic branches never merged. IPL-MVP has three single-commit experiments, including a longitudinal FHIR-persistence design note. Forge's `dev` and `feature/model-editing` branches are ahead of `main`. + +Facts worth stating up front because they shape the writing: + +- The taxonomies-over-Gamuts direction and the metadata rewrite are stated as current direction in the finding model documents and the roadmap, sourced to their branches. +- Two anatomic location lineages coexist: the 2022 `anatomiclocations.org` set with wrapper libraries, and the newer `anatomic-locations` package inside `findingmodel` with laterality and dual hierarchies. The knowledgebase documents both and states which is current. +- The FHIR mapping for Exam Finding Lists and Imaging Problem Lists is documented but implemented nowhere. The knowledgebase says so. +- No exam-type artifact exists. The area is documented as goals plus existing building blocks. +- The "Open Imaging Reporting SDK" named in the deck has no artifact. It appears only in the roadmap. +- The Hood finding taxonomies are named after their author in the source repo. The knowledgebase refers to them as "the MGB exam-oriented sub-taxonomies" and credits the contributing institution, per the organizations-only rule. The project lead may override. diff --git a/docs/sources-manifest.json b/docs/sources-manifest.json new file mode 100644 index 0000000..6685e97 --- /dev/null +++ b/docs/sources-manifest.json @@ -0,0 +1,1801 @@ +{ + "schema_version": 1, + "bucket": "oidm-knowledge-sources", + "classification_status": "Prepared from existing evidence; no new human review or publication approval implied", + "verified_at": "2026-10-02T23:21:16.988815+00:00", + "verification_method": "All 307 bucket objects downloaded and compared by size and SHA-256; this manifest selects citable source groups and their artifacts.", + "sources": [ + { + "id": "ai-evolution-essay", + "title": "Clinical context in radiology AI", + "organization": "Mass General Brigham", + "source_date": null, + "source_status": "unpublished working draft", + "sensitivity": "internal", + "handling": "Paraphrase only; do not quote. Draft statements may be incomplete.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "jacr-ipl-manuscript", + "title": "Follow-up chest CT reporting and re-documentation", + "organization": "Mass General Brigham", + "source_date": null, + "source_status": "prepared for submission to JACR", + "sensitivity": "restricted-until-published", + "handling": "Paraphrase only; redraw figures for public references. Do not infer acceptance.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "jacr-ipl-supplement", + "title": "Supplement to the follow-up chest CT manuscript", + "organization": "Mass General Brigham", + "source_date": null, + "source_status": "supplement to a manuscript prepared for submission", + "sensitivity": "restricted-until-published", + "handling": "Paraphrase only; no quotations before publication.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "jdim-ipl-r1", + "title": "Imaging Problem List manuscript, revision R1", + "organization": "Mass General Brigham", + "source_date": null, + "source_status": "under review at JDIM", + "sensitivity": "restricted-until-published", + "handling": "Paraphrase clean manuscript pages 3–32 only. Never read, quote, or summarize reviewer correspondence on pages 93–102. Whole-packet text extractions are storage artifacts, not approved reading inputs; use page-scoped extraction for synthesis.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "jdim-anatomic-locations-r1", + "title": "Anatomic Locations Index manuscript, revision R1", + "organization": "Brigham and Women's Hospital", + "source_date": null, + "source_status": "under review at JDIM", + "sensitivity": "restricted-until-published", + "handling": "Paraphrase manuscript pages 3–17 only. Never read, quote, or summarize reviewer correspondence on pages 18–35. Whole-packet text extraction is a storage artifact; use page-scoped extraction for synthesis.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "reporting-schema-deck", + "title": "Next-generation object-oriented reporting schema", + "organization": null, + "source_date": null, + "source_status": "internal worked example", + "sensitivity": "internal", + "handling": "Use design ideas as proposals; redraw figures and retain source-specific qualifications.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "siim-ipl-webinar-2026", + "title": "Imaging Problem List webinar", + "organization": "SIIM", + "source_date": "2026-07-15", + "source_status": "public webinar", + "sensitivity": "public", + "handling": "Attribute claims to the public webinar, preserving proposed versus demonstrated work.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "siim-reports-of-the-future-2026", + "title": "Structured results and context for next-generation imaging tools", + "organization": "SIIM", + "source_date": "2026-06", + "source_status": "public SIIM presentation", + "sensitivity": "public", + "handling": "The project lead permits reuse of figures. Keep slide/notes locators and qualifications.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "oidm-status-2026-01", + "title": "OIDM status update, January 2026", + "organization": "Open Imaging Data Model", + "source_date": "2026-01", + "source_status": "public presentation; local text capture only", + "sensitivity": "public", + "handling": "Cite as a captured presentation account; the original presentation file is not in this collection.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "ipl-owner-notes-2026-09-19", + "title": "Project-lead notes on imaging-result structures", + "organization": null, + "source_date": "2026-09-19", + "source_status": "project-lead notes", + "sensitivity": "internal", + "handling": "Paraphrase relevant structure decisions; do not reproduce personal correspondence details.", + "classification_basis": "Existing source inventory and project-lead handling rules; publication status has not been freshly rechecked." + }, + { + "id": "board-anatomy-and-exam-coverage", + "title": "Anatomy and exam coverage", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-definition-observation-example", + "title": "CDE and Observation worked example", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-finding-model-authoring", + "title": "OIFM authoring and content development", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-finding-model-rules", + "title": "Finding model rules and scope", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-imaging-context-object-model", + "title": "Imaging context object model", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-imaging-problem-list", + "title": "Imaging Problem List", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-longitudinal-integration-discussions", + "title": "Longitudinal integration discussions", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-oidm-big-picture", + "title": "OIDM big picture and application framework", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-outcome-and-follow-up-structures", + "title": "Outcome and follow-up structures", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-participation-and-demonstrations", + "title": "Participation, reference work, and demonstrations", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-pulmonary-nodule-demo-workflow", + "title": "Pulmonary-nodule demo workflow and role tagging", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-radiologist-outcome-feedback", + "title": "Radiologist outcome feedback", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-rsna-exhibit-examples", + "title": "RSNA exhibit examples", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-structured-report-representation", + "title": "Structured report representation", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + }, + { + "id": "board-use-case-development", + "title": "Use-case development and interaction ideas", + "organization": null, + "source_date": null, + "source_status": "historical board; publication status unconfirmed", + "sensitivity": "internal", + "handling": "Use permitted schema ideas only; redraw figures for public references. Omit live share URLs, people, meeting histories, and vendor logistics.", + "classification_basis": "Conservative internal classification under existing board rules; not a claim that the original was formally confidential. Source date and publication status remain unconfirmed." + } + ], + "files": [ + { + "id": "ai-evolution-essay-d3c2de44fb", + "source_id": "ai-evolution-essay", + "path": "bucket/AI Evolution Radiology tracked 2026-08-14 CMH Final MG.docx", + "bucket_key": "bucket/AI Evolution Radiology tracked 2026-08-14 CMH Final MG.docx", + "size": 501243, + "sha256": "4d04461040d7cb00e50016235b066d8671e4600fd1431ed3b6d6de056da7fe43", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "siim-ipl-webinar-2026-a6e8de5408", + "source_id": "siim-ipl-webinar-2026", + "path": "bucket/IPL Webinar Deck.html", + "bucket_key": "bucket/IPL Webinar Deck.html", + "size": 8820597, + "sha256": "89ceec8d2ca3b73b3ec07805d6602642244855b0b1ce7389ee1b44bad68e6e40", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "jacr-ipl-manuscript-04ba174b4d", + "source_id": "jacr-ipl-manuscript", + "path": "bucket/JACR_IPL_figures_png.zip", + "bucket_key": "bucket/JACR_IPL_figures_png.zip", + "size": 1036210, + "sha256": "a05afb235429eef5172489fa64a5178371a1744cb7ea016d8135aab54801f32c", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "original", + "description": "Supplied manuscript figure archive", + "derived_from": [] + }, + { + "id": "jacr-ipl-manuscript-8ea78bed00", + "source_id": "jacr-ipl-manuscript", + "path": "bucket/JACR_IPL_manuscript.docx", + "bucket_key": "bucket/JACR_IPL_manuscript.docx", + "size": 1110245, + "sha256": "68cfb50f07623e986068bc2f89c62975a137e01310afc6e0fe8cf7ec5ef179db", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "jacr-ipl-supplement-9e50450cf4", + "source_id": "jacr-ipl-supplement", + "path": "bucket/JACR_IPL_supplement.docx", + "bucket_key": "bucket/JACR_IPL_supplement.docx", + "size": 20779, + "sha256": "4c722eae863efbc0f70e185cc6593e9bac4fb1ee1c50ceb5547192e0d9af85c8", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "jdim-anatomic-locations-r1-2209a47e82", + "source_id": "jdim-anatomic-locations-r1", + "path": "bucket/JDIM-D-26-02183_R1.pdf", + "bucket_key": "bucket/JDIM-D-26-02183_R1.pdf", + "size": 1444465, + "sha256": "c6691b297bc02d0c42b0d8a783f23da20a20f97cf38460919c5fb66f905076ab", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "jdim-ipl-r1-fe22d4c9b5", + "source_id": "jdim-ipl-r1", + "path": "bucket/JDIM-D-26-02980_R1.pdf", + "bucket_key": "bucket/JDIM-D-26-02980_R1.pdf", + "size": 12957978, + "sha256": "cf9a2b158947e98463e1a20bdfa761c15ddbb55373092af568373d032e1977a0", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "reporting-schema-deck-cb4d6a7c76", + "source_id": "reporting-schema-deck", + "path": "bucket/Next Gen Object Oriented Reporting Schema.pptx", + "bucket_key": "bucket/Next Gen Object Oriented Reporting Schema.pptx", + "size": 2322678, + "sha256": "78bb34bb093845943f5bccbeaa7b4475c3d57866854090d991c1d2abb53f7a5e", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "jacr-ipl-manuscript-65e80827c6", + "source_id": "jacr-ipl-manuscript", + "path": "bucket/figures/JACR_IPL_Figure1.png", + "bucket_key": "bucket/figures/JACR_IPL_Figure1.png", + "size": 337935, + "sha256": "ec4f4ea1ec77ccde8f82291b06282d74e1de33ff2f0b194e0d90881d02ba1167", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "jacr-ipl-manuscript-04ba174b4d" + ] + }, + { + "id": "jacr-ipl-manuscript-1395fd413c", + "source_id": "jacr-ipl-manuscript", + "path": "bucket/figures/JACR_IPL_Figure2.png", + "bucket_key": "bucket/figures/JACR_IPL_Figure2.png", + "size": 285072, + "sha256": "9873265aaaa48c6e37f4e1ed5cb4b3a8a934ca99238cde75a066fc481d1af7af", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "jacr-ipl-manuscript-04ba174b4d" + ] + }, + { + "id": "jacr-ipl-manuscript-e9ff41b9b3", + "source_id": "jacr-ipl-manuscript", + "path": "bucket/figures/JACR_IPL_Figure3.png", + "bucket_key": "bucket/figures/JACR_IPL_Figure3.png", + "size": 310695, + "sha256": "e13cea9b16019855a41796391dc9ca8ee7c862db551a849c789afa2be165b0c9", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "jacr-ipl-manuscript-04ba174b4d" + ] + }, + { + "id": "jacr-ipl-manuscript-cdbdb9abc6", + "source_id": "jacr-ipl-manuscript", + "path": "bucket/figures/JACR_IPL_Figure4.png", + "bucket_key": "bucket/figures/JACR_IPL_Figure4.png", + "size": 218167, + "sha256": "3cc59b3457a32d79c7d42f185fca368f13f5b35af82df47af8b35641a61c0ec0", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "jacr-ipl-manuscript-04ba174b4d" + ] + }, + { + "id": "siim-ipl-webinar-2026-b6c2a1971c", + "source_id": "siim-ipl-webinar-2026", + "path": "bucket/figures/webinar/anatomy-dashboard.png", + "bucket_key": "bucket/figures/webinar/anatomy-dashboard.png", + "size": 232703, + "sha256": "f707b051c983bd83159ed5e7f43039dfbd0e52d8906bb12f71f66b0795f4f041", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-ipl-webinar-2026-a6e8de5408" + ] + }, + { + "id": "siim-ipl-webinar-2026-39ccc148be", + "source_id": "siim-ipl-webinar-2026", + "path": "bucket/figures/webinar/cde-blueprint.png", + "bucket_key": "bucket/figures/webinar/cde-blueprint.png", + "size": 297123, + "sha256": "c3546a80c20494f0031f7be5679cef30b7bf7fccd1bdc8a86cfbcf35b68f2bcb", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-ipl-webinar-2026-a6e8de5408" + ] + }, + { + "id": "siim-ipl-webinar-2026-395344951d", + "source_id": "siim-ipl-webinar-2026", + "path": "bucket/figures/webinar/doc-to-entity.png", + "bucket_key": "bucket/figures/webinar/doc-to-entity.png", + "size": 724377, + "sha256": "48d6edf400308739170d05ea4bcf456a3319c388d3b8436d7bc2a1fb38d3d877", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-ipl-webinar-2026-a6e8de5408" + ] + }, + { + "id": "siim-ipl-webinar-2026-6ddacd0197", + "source_id": "siim-ipl-webinar-2026", + "path": "bucket/figures/webinar/pipeline.png", + "bucket_key": "bucket/figures/webinar/pipeline.png", + "size": 234634, + "sha256": "8877591a922ddf2d2b53df8c567448cde1df83265e403a0cf4416e6487492fce", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-ipl-webinar-2026-a6e8de5408" + ] + }, + { + "id": "siim-ipl-webinar-2026-887a268ab8", + "source_id": "siim-ipl-webinar-2026", + "path": "bucket/figures/webinar/prototype-app.png", + "bucket_key": "bucket/figures/webinar/prototype-app.png", + "size": 1139197, + "sha256": "6cbcf679306311d4beb565f72c6911b7838511991e296effb24cca145de644c3", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-ipl-webinar-2026-a6e8de5408" + ] + }, + { + "id": "siim-ipl-webinar-2026-f67a3529a1", + "source_id": "siim-ipl-webinar-2026", + "path": "bucket/figures/webinar/timeline.png", + "bucket_key": "bucket/figures/webinar/timeline.png", + "size": 428537, + "sha256": "f108e34b97349d8ae57e359814729d2779fdcd43d71fcf8475aebfcb851397bc", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-ipl-webinar-2026-a6e8de5408" + ] + }, + { + "id": "siim-reports-of-the-future-2026-14bc22da22", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image1.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image1.png", + "size": 27399, + "sha256": "0d0b8d1a3b573e2485b62dc76bde4659950ed08552c228b2d0c59b1c3167f20a", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-5958b95e77", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image2.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image2.png", + "size": 111537, + "sha256": "b3dde92052b147e5be858c16a5256c9929ccecc3cd45c27cee1cb47b3c830a19", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-89058e1b2b", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image3.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image3.png", + "size": 111537, + "sha256": "b3dde92052b147e5be858c16a5256c9929ccecc3cd45c27cee1cb47b3c830a19", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-1cdc19e92c", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image4.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image4.png", + "size": 135973, + "sha256": "a28294d06e8b0fe098188e88864140c2cd12986b90db3f0b750b5f2ac7883040", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-7ebf5051be", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image5.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image5.png", + "size": 55750, + "sha256": "ff987b18770353c63818a41a485fc752eb4f32ed18b49de9d81efbc7b144ba32", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-3dfe35f626", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image6.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image6.png", + "size": 258907, + "sha256": "cfe817be45c7e8df70c20b51b9896999c7cc0fe8f19c66b73a53dc874ab6c4b5", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-ed342dcf35", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image7.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image7.png", + "size": 603674, + "sha256": "92b7b1d5eccd2f041ffced1aadb852071eeb50ea30e6be52bac8bc5a5d51e559", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-75eff937e9", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image8.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image8.png", + "size": 507614, + "sha256": "9fe1df5f13b2275d2670d5b94bcfc56e919e076ebfff157a595754e50c043e3f", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-849e86095a", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/media/ppt/media/image9.png", + "bucket_key": "bucket/siim2026/media/ppt/media/image9.png", + "size": 90805, + "sha256": "a214ac893de87a8fffb2b2fabd4f6f1fc8fe074112ae0a0324417440b018865c", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-figure", + "description": "Figure extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-447b89d48d", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/siim2026.md", + "bucket_key": "bucket/siim2026/siim2026.md", + "size": 9374, + "sha256": "cbf80e0c5c521667578b9d72d283de559553785d0fc1b9a95a786c1ff6a69234", + "added_at": "2026-10-02T23:11:24.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "siim-reports-of-the-future-2026-6f15f842ec", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/siim2026/siim2026.pptx", + "bucket_key": "bucket/siim2026/siim2026.pptx", + "size": 1999347, + "sha256": "120727cc513ed32c67713f83dbd49eb1829cf97e9ae938595f39273d4880e27a", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "ai-evolution-essay-71bca75ce8", + "source_id": "ai-evolution-essay", + "path": "bucket/text/ai-evolution-radiology.md", + "bucket_key": "bucket/text/ai-evolution-radiology.md", + "size": 19932, + "sha256": "7df5c3ccfc2fc02e533d71de1614691e50813a38f5a8b5ab4f63a5d7eef01154", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "ai-evolution-essay-d3c2de44fb" + ] + }, + { + "id": "siim-ipl-webinar-2026-e806ed2334", + "source_id": "siim-ipl-webinar-2026", + "path": "bucket/text/ipl-webinar-deck.md", + "bucket_key": "bucket/text/ipl-webinar-deck.md", + "size": 30129, + "sha256": "73051cd356c56a0cf96f4181e88c368c8ab030e4476395b2176e9314229e355c", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "siim-ipl-webinar-2026-a6e8de5408" + ] + }, + { + "id": "jacr-ipl-manuscript-4c6222c7ce", + "source_id": "jacr-ipl-manuscript", + "path": "bucket/text/jacr-ipl-manuscript.md", + "bucket_key": "bucket/text/jacr-ipl-manuscript.md", + "size": 33907, + "sha256": "55e853dd7641ee97ae39dd37765824c49752c594bdea4ce78dd3c6e8db3b63f6", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "jacr-ipl-manuscript-8ea78bed00" + ] + }, + { + "id": "jacr-ipl-supplement-9c08095985", + "source_id": "jacr-ipl-supplement", + "path": "bucket/text/jacr-ipl-supplement.md", + "bucket_key": "bucket/text/jacr-ipl-supplement.md", + "size": 23111, + "sha256": "213dd670c826c0df10a0585395f96e66008057e7a9fece1c27df003897fcc8f4", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "jacr-ipl-supplement-9e50450cf4" + ] + }, + { + "id": "jdim-anatomic-locations-r1-50b74bc02e", + "source_id": "jdim-anatomic-locations-r1", + "path": "bucket/text/jdim-02183.md", + "bucket_key": "bucket/text/jdim-02183.md", + "size": 109824, + "sha256": "381db3bea78a2e18a50d0d6b447035e6469b45f6174461e290b74162eb236442", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "jdim-anatomic-locations-r1-2209a47e82" + ] + }, + { + "id": "jdim-ipl-r1-00c853b688", + "source_id": "jdim-ipl-r1", + "path": "bucket/text/jdim-02980-mupdf.md", + "bucket_key": "bucket/text/jdim-02980-mupdf.md", + "size": 56948, + "sha256": "7f8b45677001e527cfcf499fa41c148f0345b43562e1f4c103632b332b1ac388", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "jdim-ipl-r1-fe22d4c9b5" + ] + }, + { + "id": "jdim-ipl-r1-a73708a450", + "source_id": "jdim-ipl-r1", + "path": "bucket/text/jdim-02980.md", + "bucket_key": "bucket/text/jdim-02980.md", + "size": 56856, + "sha256": "328f4a057d0b1c9f56d447ed8ce3834030b8fc1e0acdeb3ed7e23df34bc255ed", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "jdim-ipl-r1-fe22d4c9b5" + ] + }, + { + "id": "reporting-schema-deck-541b874c0c", + "source_id": "reporting-schema-deck", + "path": "bucket/text/next-gen-reporting-schema.md", + "bucket_key": "bucket/text/next-gen-reporting-schema.md", + "size": 1016, + "sha256": "cb920afa2ea60587246001751258801c25ea115d80c18d2d5d69730d75980f69", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "reporting-schema-deck-cb4d6a7c76" + ] + }, + { + "id": "siim-reports-of-the-future-2026-ee167f2b5b", + "source_id": "siim-reports-of-the-future-2026", + "path": "bucket/text/siim2026-reports-of-the-future.md", + "bucket_key": "bucket/text/siim2026-reports-of-the-future.md", + "size": 9374, + "sha256": "cbf80e0c5c521667578b9d72d283de559553785d0fc1b9a95a786c1ff6a69234", + "added_at": "2026-10-02T23:11:25.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "siim-reports-of-the-future-2026-6f15f842ec" + ] + }, + { + "id": "ipl-owner-notes-2026-09-19-c896d943b7", + "source_id": "ipl-owner-notes-2026-09-19", + "path": "email/2026-09-19-ipl-manuscript-notes.md", + "bucket_key": "email/2026-09-19-ipl-manuscript-notes.md", + "size": 3610, + "sha256": "abdee45bf88ade74c6df34e63880bd944e2d827be1cb294d2462294f7adc1afa", + "added_at": "2026-10-02T23:11:44.000Z", + "role": "original", + "description": "Original source file", + "derived_from": [] + }, + { + "id": "oidm-status-2026-01-5f1a4b5f8d", + "source_id": "oidm-status-2026-01", + "path": "gamma-2026-status-update.md", + "bucket_key": "gamma-2026-status-update.md", + "size": 4216, + "sha256": "b765b8e332c5c5371e45dfaf1c129203f9c4fc8be58c9ded480341dc252e1239", + "added_at": "2026-10-02T23:12:32.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-rsna-exhibit-examples-4f21c34a1f", + "source_id": "board-rsna-exhibit-examples", + "path": "linked-diagrams/19Qjzb9AmAT/metadata.json", + "bucket_key": "linked-diagrams/19Qjzb9AmAT/metadata.json", + "size": 1368, + "sha256": "259d0cebe49da903e54cae7e4aeeabb648e3272d9eb0b9b0c206ded2e140879b", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-rsna-exhibit-examples-2bc3c89fb7" + ] + }, + { + "id": "board-rsna-exhibit-examples-52abbeff93", + "source_id": "board-rsna-exhibit-examples", + "path": "linked-diagrams/19Qjzb9AmAT/original.png", + "bucket_key": "linked-diagrams/19Qjzb9AmAT/original.png", + "size": 2653524, + "sha256": "a182f86c10be0306e37bfd52621cb3fcdcb11856ff4c15d6df8922696c1703a2", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-rsna-exhibit-examples-2bc3c89fb7", + "source_id": "board-rsna-exhibit-examples", + "path": "linked-diagrams/19Qjzb9AmAT/original.svg", + "bucket_key": "linked-diagrams/19Qjzb9AmAT/original.svg", + "size": 2106543, + "sha256": "da51b965b564b3d85890d0da9789963f0760d2f25a3ce20f881c4a1e1f9c21a3", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-rsna-exhibit-examples-345a61e486", + "source_id": "board-rsna-exhibit-examples", + "path": "linked-diagrams/19Qjzb9AmAT/scene-contents.json", + "bucket_key": "linked-diagrams/19Qjzb9AmAT/scene-contents.json", + "size": 330045, + "sha256": "8399d17242f5e7dabf022c4205f1ef94f844850925d2f252bfdc59ec66285456", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-rsna-exhibit-examples-d34828b0b8", + "source_id": "board-rsna-exhibit-examples", + "path": "linked-diagrams/19Qjzb9AmAT/text-elements.json", + "bucket_key": "linked-diagrams/19Qjzb9AmAT/text-elements.json", + "size": 11309, + "sha256": "923fd141d9f51710e670c3b658fffc82e5994998466809f2c45b9a09dbd414bd", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-rsna-exhibit-examples-345a61e486" + ] + }, + { + "id": "board-longitudinal-integration-discussions-fdfee0ff3f", + "source_id": "board-longitudinal-integration-discussions", + "path": "linked-diagrams/1PrQV4pohsD/metadata.json", + "bucket_key": "linked-diagrams/1PrQV4pohsD/metadata.json", + "size": 1751, + "sha256": "091be730c61892da7164e5aac4a35cc41a9ffb1e07f388c77218781214091b70", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-longitudinal-integration-discussions-939e05c349" + ] + }, + { + "id": "board-longitudinal-integration-discussions-dd7281ceb1", + "source_id": "board-longitudinal-integration-discussions", + "path": "linked-diagrams/1PrQV4pohsD/original.png", + "bucket_key": "linked-diagrams/1PrQV4pohsD/original.png", + "size": 2603357, + "sha256": "207c2a494292adc945b2a47d1e761135c2859331c031e51db731efdb9ac25d08", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-longitudinal-integration-discussions-939e05c349", + "source_id": "board-longitudinal-integration-discussions", + "path": "linked-diagrams/1PrQV4pohsD/original.svg", + "bucket_key": "linked-diagrams/1PrQV4pohsD/original.svg", + "size": 1779768, + "sha256": "cf994587f371076cbab438817e63158392f92d81759cf8097f71ce28bbdb59c3", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-longitudinal-integration-discussions-a05029fea6", + "source_id": "board-longitudinal-integration-discussions", + "path": "linked-diagrams/1PrQV4pohsD/scene-contents.json", + "bucket_key": "linked-diagrams/1PrQV4pohsD/scene-contents.json", + "size": 70752, + "sha256": "4048c2e18a0194f3781c1dd41a15867d197fab74fe6aae0ecdc9511e9b969f91", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-longitudinal-integration-discussions-697ea4ba20", + "source_id": "board-longitudinal-integration-discussions", + "path": "linked-diagrams/1PrQV4pohsD/text-elements.json", + "bucket_key": "linked-diagrams/1PrQV4pohsD/text-elements.json", + "size": 7519, + "sha256": "db32ed06d3a506e526b1acea97f9f702f53a95040aa27d83c5f6e18c213d06d0", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-longitudinal-integration-discussions-a05029fea6" + ] + }, + { + "id": "board-longitudinal-integration-discussions-621c21078b", + "source_id": "board-longitudinal-integration-discussions", + "path": "linked-diagrams/1PrQV4pohsD/usable.svg", + "bucket_key": "linked-diagrams/1PrQV4pohsD/usable.svg", + "size": 1779768, + "sha256": "18dda0eae4c1276dcbdb9263cf49aa505fd7f21b11e244287fcba7b10e35ec7f", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "normalized-figure", + "description": "Normalized copy of the original SVG", + "derived_from": [ + "board-longitudinal-integration-discussions-939e05c349" + ] + }, + { + "id": "board-use-case-development-1a4ff7a93c", + "source_id": "board-use-case-development", + "path": "linked-diagrams/1lt7q7M1bEz/metadata.json", + "bucket_key": "linked-diagrams/1lt7q7M1bEz/metadata.json", + "size": 1742, + "sha256": "6fec43c6b699a69aa4827bd190cb2ba34134688688b0fcf273d29709fe8cbe62", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-use-case-development-cff30a92e5" + ] + }, + { + "id": "board-use-case-development-367ce22477", + "source_id": "board-use-case-development", + "path": "linked-diagrams/1lt7q7M1bEz/original.png", + "bucket_key": "linked-diagrams/1lt7q7M1bEz/original.png", + "size": 2333482, + "sha256": "9d5de160816f0e237593eb8075b0bd47b0b599cd82c1c961d9101e949813a593", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-use-case-development-cff30a92e5", + "source_id": "board-use-case-development", + "path": "linked-diagrams/1lt7q7M1bEz/original.svg", + "bucket_key": "linked-diagrams/1lt7q7M1bEz/original.svg", + "size": 1151407, + "sha256": "060586ead1d09b7738760bd383a2f46c5d28d73d5b7120932b4de42c634eafa4", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-use-case-development-a80fe21725", + "source_id": "board-use-case-development", + "path": "linked-diagrams/1lt7q7M1bEz/scene-contents.json", + "bucket_key": "linked-diagrams/1lt7q7M1bEz/scene-contents.json", + "size": 34894, + "sha256": "4778b9bf6ff943d760350f3be9dbbc4b97d348178d0fa3e6fcb8cb94087ad820", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-use-case-development-b961b72121", + "source_id": "board-use-case-development", + "path": "linked-diagrams/1lt7q7M1bEz/text-elements.json", + "bucket_key": "linked-diagrams/1lt7q7M1bEz/text-elements.json", + "size": 2831, + "sha256": "586104391b1820792302af32a17fad86f56835576e7e58b776dd2a781991e2d5", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-use-case-development-a80fe21725" + ] + }, + { + "id": "board-use-case-development-e1362d1252", + "source_id": "board-use-case-development", + "path": "linked-diagrams/1lt7q7M1bEz/usable.svg", + "bucket_key": "linked-diagrams/1lt7q7M1bEz/usable.svg", + "size": 1151407, + "sha256": "52935a7cbdbb714b1f40464ae6811d04d80479cf4f7b62c396ad3347134b3546", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "normalized-figure", + "description": "Normalized copy of the original SVG", + "derived_from": [ + "board-use-case-development-cff30a92e5" + ] + }, + { + "id": "board-imaging-context-object-model-b0c0c839fc", + "source_id": "board-imaging-context-object-model", + "path": "linked-diagrams/1sEx11UZ3gq/metadata.json", + "bucket_key": "linked-diagrams/1sEx11UZ3gq/metadata.json", + "size": 1355, + "sha256": "495301bbdc3051490843ba5a147da3330d0d33ecc13d364904cf358d97c43020", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-imaging-context-object-model-e247d4cf68" + ] + }, + { + "id": "board-imaging-context-object-model-65b577480b", + "source_id": "board-imaging-context-object-model", + "path": "linked-diagrams/1sEx11UZ3gq/original.png", + "bucket_key": "linked-diagrams/1sEx11UZ3gq/original.png", + "size": 1601246, + "sha256": "c970c6761717c575525c83e72bdd02881167eaf7e5eed8c813099651e192a085", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-imaging-context-object-model-e247d4cf68", + "source_id": "board-imaging-context-object-model", + "path": "linked-diagrams/1sEx11UZ3gq/original.svg", + "bucket_key": "linked-diagrams/1sEx11UZ3gq/original.svg", + "size": 537564, + "sha256": "19fd7142c1cc3d40e63feed32ac06e0078cddf4e5ecdc04a3ed6f141437ecbcb", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-imaging-context-object-model-3344fb1a60", + "source_id": "board-imaging-context-object-model", + "path": "linked-diagrams/1sEx11UZ3gq/scene-contents.json", + "bucket_key": "linked-diagrams/1sEx11UZ3gq/scene-contents.json", + "size": 176291, + "sha256": "23e180c37878efc7b90baab4d8d4b30d3c2091f5f7b355f42d3dd213745b93c8", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-imaging-context-object-model-d574e32872", + "source_id": "board-imaging-context-object-model", + "path": "linked-diagrams/1sEx11UZ3gq/text-elements.json", + "bucket_key": "linked-diagrams/1sEx11UZ3gq/text-elements.json", + "size": 7100, + "sha256": "8feb8fc489544be4b2eb8f602c6d83eafdbecae2da60fa8072638cb36be65646", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-imaging-context-object-model-3344fb1a60" + ] + }, + { + "id": "board-pulmonary-nodule-demo-workflow-0e7d17ad19", + "source_id": "board-pulmonary-nodule-demo-workflow", + "path": "linked-diagrams/2ZpQ4nXotjV/metadata.json", + "bucket_key": "linked-diagrams/2ZpQ4nXotjV/metadata.json", + "size": 1362, + "sha256": "daa29b9859693e1a4582058cf5194c166ffc841889e957ca2ecc313cae27c0a0", + "added_at": "2026-10-02T23:12:50.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-pulmonary-nodule-demo-workflow-6a5c437d6a" + ] + }, + { + "id": "board-pulmonary-nodule-demo-workflow-249e3e791e", + "source_id": "board-pulmonary-nodule-demo-workflow", + "path": "linked-diagrams/2ZpQ4nXotjV/original.png", + "bucket_key": "linked-diagrams/2ZpQ4nXotjV/original.png", + "size": 1678905, + "sha256": "239bc734ea894155fbc30a638b4cfa0b07e37dc02da22b84f8c2d2841eb1d1e2", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-pulmonary-nodule-demo-workflow-6a5c437d6a", + "source_id": "board-pulmonary-nodule-demo-workflow", + "path": "linked-diagrams/2ZpQ4nXotjV/original.svg", + "bucket_key": "linked-diagrams/2ZpQ4nXotjV/original.svg", + "size": 1084938, + "sha256": "f705960389885b33cbde33a019b91a850fbe9476065da3fe3498185287704aae", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-pulmonary-nodule-demo-workflow-3c954fc2d6", + "source_id": "board-pulmonary-nodule-demo-workflow", + "path": "linked-diagrams/2ZpQ4nXotjV/scene-contents.json", + "bucket_key": "linked-diagrams/2ZpQ4nXotjV/scene-contents.json", + "size": 82425, + "sha256": "b7bcec29b5458a888eb836ff5f301ea005fa0db26b564a44d868de814c1b608f", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-pulmonary-nodule-demo-workflow-544fe4745d", + "source_id": "board-pulmonary-nodule-demo-workflow", + "path": "linked-diagrams/2ZpQ4nXotjV/text-elements.json", + "bucket_key": "linked-diagrams/2ZpQ4nXotjV/text-elements.json", + "size": 9241, + "sha256": "b70e630819d4e7a551b6960f55e4bff7ff6b6b1109babb4f2c189e3a6b3a1bf4", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-pulmonary-nodule-demo-workflow-3c954fc2d6" + ] + }, + { + "id": "board-finding-model-rules-9cd866b4a2", + "source_id": "board-finding-model-rules", + "path": "linked-diagrams/2a3Jq3oVVty/metadata.json", + "bucket_key": "linked-diagrams/2a3Jq3oVVty/metadata.json", + "size": 1360, + "sha256": "9f05f79c27b005afc03da2687e8e358bb93a239328202486f14bb9028b294eab", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-finding-model-rules-db9f23b7fd" + ] + }, + { + "id": "board-finding-model-rules-efb1a622df", + "source_id": "board-finding-model-rules", + "path": "linked-diagrams/2a3Jq3oVVty/original.png", + "bucket_key": "linked-diagrams/2a3Jq3oVVty/original.png", + "size": 219295, + "sha256": "5161ed4fd1de13fffe817477db018aa801b7980499d4a4a19bb09f7fcadda9bb", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-finding-model-rules-db9f23b7fd", + "source_id": "board-finding-model-rules", + "path": "linked-diagrams/2a3Jq3oVVty/original.svg", + "bucket_key": "linked-diagrams/2a3Jq3oVVty/original.svg", + "size": 23961, + "sha256": "f6f3c8deeb7a5575a9e799b795ba7057b739e0833593eb496d57f04d2673d53e", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-finding-model-rules-a674d84f4f", + "source_id": "board-finding-model-rules", + "path": "linked-diagrams/2a3Jq3oVVty/scene-contents.json", + "bucket_key": "linked-diagrams/2a3Jq3oVVty/scene-contents.json", + "size": 22176, + "sha256": "bb32622ef6324626285c2d2bb994e863c7cfa255d9e0a050eef1a97ad6b217a1", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-finding-model-rules-9c62f5454d", + "source_id": "board-finding-model-rules", + "path": "linked-diagrams/2a3Jq3oVVty/text-elements.json", + "bucket_key": "linked-diagrams/2a3Jq3oVVty/text-elements.json", + "size": 1992, + "sha256": "017b3fa5f044cd329a3b43be7dd5fcf70e5fa6641551a09c0a20bdcb2814ecdd", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-finding-model-rules-a674d84f4f" + ] + }, + { + "id": "board-imaging-problem-list-670308aa7a", + "source_id": "board-imaging-problem-list", + "path": "linked-diagrams/2jUZLu229km/metadata.json", + "bucket_key": "linked-diagrams/2jUZLu229km/metadata.json", + "size": 1360, + "sha256": "7e2f3eb1efac114a2b769d9b0b2be1ebeaed8bcbcd5ebf27a707697e62e362df", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-imaging-problem-list-5e6bfa59b2" + ] + }, + { + "id": "board-imaging-problem-list-d8a77dd6b7", + "source_id": "board-imaging-problem-list", + "path": "linked-diagrams/2jUZLu229km/original.png", + "bucket_key": "linked-diagrams/2jUZLu229km/original.png", + "size": 5856420, + "sha256": "4311b82fc43b8a2049ea1c411fde70ffc7bb5701876167f52bffe28d14061b00", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-imaging-problem-list-5e6bfa59b2", + "source_id": "board-imaging-problem-list", + "path": "linked-diagrams/2jUZLu229km/original.svg", + "bucket_key": "linked-diagrams/2jUZLu229km/original.svg", + "size": 1884185, + "sha256": "2f7bf7c9cfacbb499af3072995287df88c7d9ccc56543fa652a4cc0cd52ea0af", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-imaging-problem-list-58bbec214a", + "source_id": "board-imaging-problem-list", + "path": "linked-diagrams/2jUZLu229km/scene-contents.json", + "bucket_key": "linked-diagrams/2jUZLu229km/scene-contents.json", + "size": 146172, + "sha256": "cac194a274e2e1c127bd9dcc5155a48ecb071e0059b3e7ad837ae926e53a8926", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-imaging-problem-list-9961fee7b2", + "source_id": "board-imaging-problem-list", + "path": "linked-diagrams/2jUZLu229km/text-elements.json", + "bucket_key": "linked-diagrams/2jUZLu229km/text-elements.json", + "size": 16718, + "sha256": "af4227c8f19bc48580be08ab8364acee2a32cf6216f6beb146bfa55653ed6e65", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-imaging-problem-list-58bbec214a" + ] + }, + { + "id": "board-anatomy-and-exam-coverage-905b4e13ee", + "source_id": "board-anatomy-and-exam-coverage", + "path": "linked-diagrams/4jmRPObEdLq/metadata.json", + "bucket_key": "linked-diagrams/4jmRPObEdLq/metadata.json", + "size": 1360, + "sha256": "b704abf06e3aa27387f0c8092edf47076043d9dbcc01f032247402250855a5b5", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-anatomy-and-exam-coverage-9e6efa9adc" + ] + }, + { + "id": "board-anatomy-and-exam-coverage-487f8d3f76", + "source_id": "board-anatomy-and-exam-coverage", + "path": "linked-diagrams/4jmRPObEdLq/original.png", + "bucket_key": "linked-diagrams/4jmRPObEdLq/original.png", + "size": 508478, + "sha256": "fcea13e86f1962c2aa0a116c7168777345cfebd8c304065dd3cd0b6a73e4fae2", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-anatomy-and-exam-coverage-9e6efa9adc", + "source_id": "board-anatomy-and-exam-coverage", + "path": "linked-diagrams/4jmRPObEdLq/original.svg", + "bucket_key": "linked-diagrams/4jmRPObEdLq/original.svg", + "size": 280030, + "sha256": "9fb5fcf0654c0369707cc994f86d0007a55b30ee3ff8d479d5250908029bdc64", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-anatomy-and-exam-coverage-d73121e4cf", + "source_id": "board-anatomy-and-exam-coverage", + "path": "linked-diagrams/4jmRPObEdLq/scene-contents.json", + "bucket_key": "linked-diagrams/4jmRPObEdLq/scene-contents.json", + "size": 17767, + "sha256": "f9d69224c5139c32dd99bc98a1e06055337b1c7c9831a7f53384e5bdc66e306f", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-anatomy-and-exam-coverage-93b68b6639", + "source_id": "board-anatomy-and-exam-coverage", + "path": "linked-diagrams/4jmRPObEdLq/text-elements.json", + "bucket_key": "linked-diagrams/4jmRPObEdLq/text-elements.json", + "size": 3293, + "sha256": "f070a3370b120fae41221002eec486237ec9f55e320651606f54aa4770059f91", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-anatomy-and-exam-coverage-d73121e4cf" + ] + }, + { + "id": "board-radiologist-outcome-feedback-472e183c9b", + "source_id": "board-radiologist-outcome-feedback", + "path": "linked-diagrams/5QMQ1J2FYT3/metadata.json", + "bucket_key": "linked-diagrams/5QMQ1J2FYT3/metadata.json", + "size": 1367, + "sha256": "61f73e301ad18b803645cbc146cb6bd1fda49a9fb95395586394bf19dac7dd73", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-radiologist-outcome-feedback-291cdfd36f" + ] + }, + { + "id": "board-radiologist-outcome-feedback-8cef55ebb2", + "source_id": "board-radiologist-outcome-feedback", + "path": "linked-diagrams/5QMQ1J2FYT3/original.png", + "bucket_key": "linked-diagrams/5QMQ1J2FYT3/original.png", + "size": 2246977, + "sha256": "bcdd236b069f4dd8c502fd0ff52c28924d655973c2aa381e5d9b068970352b2b", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-radiologist-outcome-feedback-291cdfd36f", + "source_id": "board-radiologist-outcome-feedback", + "path": "linked-diagrams/5QMQ1J2FYT3/original.svg", + "bucket_key": "linked-diagrams/5QMQ1J2FYT3/original.svg", + "size": 982083, + "sha256": "dcde5ca0041afce76ae377d68e253440fca45ecc21c9efc1e33b5ac6eb7c86d7", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-radiologist-outcome-feedback-d91394c91c", + "source_id": "board-radiologist-outcome-feedback", + "path": "linked-diagrams/5QMQ1J2FYT3/scene-contents.json", + "bucket_key": "linked-diagrams/5QMQ1J2FYT3/scene-contents.json", + "size": 194239, + "sha256": "fe46892e58171dea30aae6aee989620b7983086e774f4f6f24a63be4fcfa2a9d", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-radiologist-outcome-feedback-ef99544b3c", + "source_id": "board-radiologist-outcome-feedback", + "path": "linked-diagrams/5QMQ1J2FYT3/text-elements.json", + "bucket_key": "linked-diagrams/5QMQ1J2FYT3/text-elements.json", + "size": 12071, + "sha256": "a7fd92f520828c1930777c0b8d2a23314e04e14b45a7b42443120de03862b461", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-radiologist-outcome-feedback-d91394c91c" + ] + }, + { + "id": "board-outcome-and-follow-up-structures-3f0bc38dfa", + "source_id": "board-outcome-and-follow-up-structures", + "path": "linked-diagrams/68X9qfSP5qA/metadata.json", + "bucket_key": "linked-diagrams/68X9qfSP5qA/metadata.json", + "size": 1360, + "sha256": "5f42952e99bbf754b853d2db8b0bf8391214a79bffa650090ee47136ff9adb86", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-outcome-and-follow-up-structures-fe275138f9" + ] + }, + { + "id": "board-outcome-and-follow-up-structures-e3e023863e", + "source_id": "board-outcome-and-follow-up-structures", + "path": "linked-diagrams/68X9qfSP5qA/original.png", + "bucket_key": "linked-diagrams/68X9qfSP5qA/original.png", + "size": 1514915, + "sha256": "71e88ff77c950dc0d3e91dab9be81507f51e75773f3adc9be2041a6f7ccd2f99", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-outcome-and-follow-up-structures-fe275138f9", + "source_id": "board-outcome-and-follow-up-structures", + "path": "linked-diagrams/68X9qfSP5qA/original.svg", + "bucket_key": "linked-diagrams/68X9qfSP5qA/original.svg", + "size": 1852743, + "sha256": "73bf546150732c4b4d72dce86a10184e3d9e502271754397a4c6687382c4a9b6", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-outcome-and-follow-up-structures-a947f78f05", + "source_id": "board-outcome-and-follow-up-structures", + "path": "linked-diagrams/68X9qfSP5qA/scene-contents.json", + "bucket_key": "linked-diagrams/68X9qfSP5qA/scene-contents.json", + "size": 43297, + "sha256": "0dfef7f853a537a3ee33b6f258ac7d9a6d4fb8d06dc7d2bef07cd3a03f3cbd64", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-outcome-and-follow-up-structures-3cb3782685", + "source_id": "board-outcome-and-follow-up-structures", + "path": "linked-diagrams/68X9qfSP5qA/text-elements.json", + "bucket_key": "linked-diagrams/68X9qfSP5qA/text-elements.json", + "size": 5628, + "sha256": "ef4484238b0d29f89cf0614d641889df967614e5539beebbfb6b3b041e6a9374", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-outcome-and-follow-up-structures-a947f78f05" + ] + }, + { + "id": "board-participation-and-demonstrations-bc57cd3075", + "source_id": "board-participation-and-demonstrations", + "path": "linked-diagrams/6dKAnPexyN8/metadata.json", + "bucket_key": "linked-diagrams/6dKAnPexyN8/metadata.json", + "size": 1344, + "sha256": "420d975a225d1b1130821dff7c8880951827834c6343b504a02c08a94f9aa12b", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-participation-and-demonstrations-24a0abddc2" + ] + }, + { + "id": "board-participation-and-demonstrations-51f40867d6", + "source_id": "board-participation-and-demonstrations", + "path": "linked-diagrams/6dKAnPexyN8/original.png", + "bucket_key": "linked-diagrams/6dKAnPexyN8/original.png", + "size": 1226211, + "sha256": "0a5c99fdf19d94541f9cc05eccc8867e3d30b0d0e87b9b0db65711b5e250d2c1", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-participation-and-demonstrations-24a0abddc2", + "source_id": "board-participation-and-demonstrations", + "path": "linked-diagrams/6dKAnPexyN8/original.svg", + "bucket_key": "linked-diagrams/6dKAnPexyN8/original.svg", + "size": 161865, + "sha256": "3a6cef543bf857ef6683d168b88553e3a75604f654e02972b771966a945bad3c", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-participation-and-demonstrations-568e4fe0e6", + "source_id": "board-participation-and-demonstrations", + "path": "linked-diagrams/6dKAnPexyN8/scene-contents.json", + "bucket_key": "linked-diagrams/6dKAnPexyN8/scene-contents.json", + "size": 23100, + "sha256": "8761b15f46d89a2a30476ad29c2d64e57cc51999df27fd55299ef827798f286a", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-participation-and-demonstrations-a3a82bdda4", + "source_id": "board-participation-and-demonstrations", + "path": "linked-diagrams/6dKAnPexyN8/text-elements.json", + "bucket_key": "linked-diagrams/6dKAnPexyN8/text-elements.json", + "size": 2146, + "sha256": "ff6f35d9e1f6791e029f95a721fd7cfe574f92d55c822bf550b514e51fd1290f", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-participation-and-demonstrations-568e4fe0e6" + ] + }, + { + "id": "board-oidm-big-picture-daab65d0c7", + "source_id": "board-oidm-big-picture", + "path": "linked-diagrams/8v07Nni5p8F/metadata.json", + "bucket_key": "linked-diagrams/8v07Nni5p8F/metadata.json", + "size": 1356, + "sha256": "313bcfad36f55c86cc4db6e95044d8e7fba7e7878222100969e258a9f1e25dbd", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-oidm-big-picture-1c8e689e18" + ] + }, + { + "id": "board-oidm-big-picture-6856d791d9", + "source_id": "board-oidm-big-picture", + "path": "linked-diagrams/8v07Nni5p8F/original.png", + "bucket_key": "linked-diagrams/8v07Nni5p8F/original.png", + "size": 3243290, + "sha256": "a181d81e41d82c74f6555fa6ec541ca87480441e1427d9522b72fc6d540aa0b2", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-oidm-big-picture-1c8e689e18", + "source_id": "board-oidm-big-picture", + "path": "linked-diagrams/8v07Nni5p8F/original.svg", + "bucket_key": "linked-diagrams/8v07Nni5p8F/original.svg", + "size": 2177216, + "sha256": "d86567bd751faea4b98474a2938916490996cb062857fb9983bc7fb8df6875b7", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-oidm-big-picture-436db71c83", + "source_id": "board-oidm-big-picture", + "path": "linked-diagrams/8v07Nni5p8F/scene-contents.json", + "bucket_key": "linked-diagrams/8v07Nni5p8F/scene-contents.json", + "size": 164882, + "sha256": "0c96851ee2b2cc3df738a52a13d470835ee14c897e6ff5d0111c0b0ab05c6709", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-oidm-big-picture-f293fd0e4c", + "source_id": "board-oidm-big-picture", + "path": "linked-diagrams/8v07Nni5p8F/text-elements.json", + "bucket_key": "linked-diagrams/8v07Nni5p8F/text-elements.json", + "size": 13515, + "sha256": "957e694d0ec1153a67b2eedd369fa774f3a3c5402a31c288a925fb4a801778b5", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-oidm-big-picture-436db71c83" + ] + }, + { + "id": "board-structured-report-representation-7b4fa7ccb9", + "source_id": "board-structured-report-representation", + "path": "linked-diagrams/9Jgo8oKswiE/metadata.json", + "bucket_key": "linked-diagrams/9Jgo8oKswiE/metadata.json", + "size": 1368, + "sha256": "7687613e5c2a7823f6d5c87ea86b7a04168f82dc2ef75e6308656e1cdc39aac5", + "added_at": "2026-10-02T23:12:51.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-structured-report-representation-fcc03db438" + ] + }, + { + "id": "board-structured-report-representation-561328c9fd", + "source_id": "board-structured-report-representation", + "path": "linked-diagrams/9Jgo8oKswiE/original.png", + "bucket_key": "linked-diagrams/9Jgo8oKswiE/original.png", + "size": 1003021, + "sha256": "3d4f39592da740d288db38f88962a6f8fd7ddef52fcf0abf4a7753a3114a9cc6", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-structured-report-representation-fcc03db438", + "source_id": "board-structured-report-representation", + "path": "linked-diagrams/9Jgo8oKswiE/original.svg", + "bucket_key": "linked-diagrams/9Jgo8oKswiE/original.svg", + "size": 322099, + "sha256": "c2208b8b1e5c05d95d17d9db8ef4edf14976dd22130e728846215a6ab0c91895", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-structured-report-representation-6f840453b5", + "source_id": "board-structured-report-representation", + "path": "linked-diagrams/9Jgo8oKswiE/scene-contents.json", + "bucket_key": "linked-diagrams/9Jgo8oKswiE/scene-contents.json", + "size": 54781, + "sha256": "9b44919dacec9300e90f3394055d4a4b77067433fc4e67fc4626575b9df62c23", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-structured-report-representation-3d0887b947", + "source_id": "board-structured-report-representation", + "path": "linked-diagrams/9Jgo8oKswiE/text-elements.json", + "bucket_key": "linked-diagrams/9Jgo8oKswiE/text-elements.json", + "size": 6979, + "sha256": "bcd54a6d0dad2fbade9c5ec49ecfefdb8c840bba2e433c2238b2882754566828", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-structured-report-representation-6f840453b5" + ] + }, + { + "id": "board-finding-model-authoring-4f0ce189fa", + "source_id": "board-finding-model-authoring", + "path": "linked-diagrams/A55Dpt6hley/metadata.json", + "bucket_key": "linked-diagrams/A55Dpt6hley/metadata.json", + "size": 1728, + "sha256": "f3b5d855a6cf160b978ad63e769375c8fa37fcebdf05ec0f5cd93866271b2cca", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-finding-model-authoring-dac6bed1e0" + ] + }, + { + "id": "board-finding-model-authoring-7462528e6b", + "source_id": "board-finding-model-authoring", + "path": "linked-diagrams/A55Dpt6hley/original.png", + "bucket_key": "linked-diagrams/A55Dpt6hley/original.png", + "size": 6354962, + "sha256": "259ec2f1425cf2dd87eebb11e41cbc02ee2eeee53b221b721a9d90dcea3201d9", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-finding-model-authoring-dac6bed1e0", + "source_id": "board-finding-model-authoring", + "path": "linked-diagrams/A55Dpt6hley/original.svg", + "bucket_key": "linked-diagrams/A55Dpt6hley/original.svg", + "size": 4792962, + "sha256": "08be397b374197deecbc48ddfb7491476d56505380a1e5db3580bc014f9c94b7", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-finding-model-authoring-c09db4553e", + "source_id": "board-finding-model-authoring", + "path": "linked-diagrams/A55Dpt6hley/scene-contents.json", + "bucket_key": "linked-diagrams/A55Dpt6hley/scene-contents.json", + "size": 145151, + "sha256": "bc81fce3e8f5a1ba7a047e39af4553eff8c3838d84aedd1c4e61961d9325ce15", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-finding-model-authoring-a8708db7a0", + "source_id": "board-finding-model-authoring", + "path": "linked-diagrams/A55Dpt6hley/text-elements.json", + "bucket_key": "linked-diagrams/A55Dpt6hley/text-elements.json", + "size": 18225, + "sha256": "f6f966d903e6d19e39c5b03d17c444bb216a99835e859e3b0da5e84c7e9a42b1", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-finding-model-authoring-c09db4553e" + ] + }, + { + "id": "board-finding-model-authoring-0987ef5f24", + "source_id": "board-finding-model-authoring", + "path": "linked-diagrams/A55Dpt6hley/usable.svg", + "bucket_key": "linked-diagrams/A55Dpt6hley/usable.svg", + "size": 4792962, + "sha256": "6a71d790b3717dc000365a1ab5a6a1e61b0c5c0b47109a8a5447bf1c9b6d9601", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "normalized-figure", + "description": "Normalized copy of the original SVG", + "derived_from": [ + "board-finding-model-authoring-dac6bed1e0" + ] + }, + { + "id": "board-definition-observation-example-3aa72adf5b", + "source_id": "board-definition-observation-example", + "path": "linked-diagrams/ArAkPl5S3kD/metadata.json", + "bucket_key": "linked-diagrams/ArAkPl5S3kD/metadata.json", + "size": 1364, + "sha256": "ec45624771559dc8290788817d4483eaf916656019bd11ba6ca3dcc6732222ad", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture-metadata", + "description": "Private source capture metadata", + "derived_from": [ + "board-definition-observation-example-8037dcefba" + ] + }, + { + "id": "board-definition-observation-example-ce8844ad67", + "source_id": "board-definition-observation-example", + "path": "linked-diagrams/ArAkPl5S3kD/original.png", + "bucket_key": "linked-diagrams/ArAkPl5S3kD/original.png", + "size": 1061487, + "sha256": "3a4de5ff3e59d29597b757adb55ff07e07ee96f5dea0a020ba3318d9e4dc13ef", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-definition-observation-example-8037dcefba", + "source_id": "board-definition-observation-example", + "path": "linked-diagrams/ArAkPl5S3kD/original.svg", + "bucket_key": "linked-diagrams/ArAkPl5S3kD/original.svg", + "size": 163911, + "sha256": "fdf862877289d2528dadd54623f07bd42a09f12aee6812c27d07a3c695bb67f8", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-definition-observation-example-6437a466cb", + "source_id": "board-definition-observation-example", + "path": "linked-diagrams/ArAkPl5S3kD/scene-contents.json", + "bucket_key": "linked-diagrams/ArAkPl5S3kD/scene-contents.json", + "size": 149329, + "sha256": "1a0e585b68a236e8fc8a5573d5dc9244c1845ad2cc6a352870b1e6080f0d0890", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "capture", + "description": "Preserved source capture", + "derived_from": [] + }, + { + "id": "board-definition-observation-example-149e9e08a7", + "source_id": "board-definition-observation-example", + "path": "linked-diagrams/ArAkPl5S3kD/text-elements.json", + "bucket_key": "linked-diagrams/ArAkPl5S3kD/text-elements.json", + "size": 9111, + "sha256": "84eeabf0fb90a0386f06070fbe890d4453105329ffce7035acc9ea3cff4cc760", + "added_at": "2026-10-02T23:12:52.000Z", + "role": "extracted-text", + "description": "Text extracted from the source", + "derived_from": [ + "board-definition-observation-example-6437a466cb" + ] + } + ] +} diff --git a/docs/sources.md b/docs/sources.md new file mode 100644 index 0000000..17772f9 --- /dev/null +++ b/docs/sources.md @@ -0,0 +1,86 @@ +# Recover and verify source material + +The [source manifest](sources-manifest.json) selects 118 artifacts in 25 source groups from the private `oidm-knowledge-sources` Tigris bucket. It covers original documents, captured boards, and their extraction artifacts. The [implementation plan](plans/implementation.md) tracks subsequent reference and knowledge work. + +## Check the manifest and local files + +Use Python 3.11 or later and the repository's Task runner: + +```bash +task sources:manifest +task sources:check +``` + +`manifest` validates the tracked metadata without private files or credentials. `check` compares the selected local files' sizes and SHA-256 hashes. It does not contact Tigris or inspect document text. Missing or changed artifacts cause a nonzero exit. + +The default local collection is `~/data/oidm-knowledge-sources`. Override it with `OIDM_SOURCES_DIR` or `--root`: + +```bash +task sources:check -- --root /path/to/local/collection +task sources:check -- --source siim-reports-of-the-future-2026 +``` + +Repeat `--source` to select several source IDs. IDs come from the manifest's `sources` list. Without Task, run `python3 tools/sources.py manifest`, `check`, or `pull` with the same options. + +## Recover into a new checkout + +Install the Tigris CLI and authenticate with an account or access key authorized to read the bucket. Use `t3 login` for interactive login, or your existing credential setup. Do not put credentials into this repository or the manifest. + +```bash +t3 credentials test --bucket oidm-knowledge-sources +task sources:pull +task sources:check +``` + +The pull command downloads only missing manifest entries and verifies each temporary download before adding it to the collection. Matching files are skipped. A differing local file causes failure and is never overwritten; inspect or move your local copy deliberately before retrying. Successfully verified downloads remain available if a later transfer fails. Temporary partial files are removed. There are no remote writes or deletes. + +The download uses the installed CLI's [`t3 cp`](https://www.npmjs.com/package/@tigrisdata/cli) command with an explicit remote key for each artifact. Transfers run four at a time by default; use `--jobs 1` for sequential transfers, or any value up to 16. A download failure reports the artifact ID; check authentication and connectivity with the CLI before retrying. + +Existing reference links use `sources/` relative to the repository. After recovering into the default location, create the ignored compatibility symlink from the repository root if `sources` does not already exist: + +```bash +ln -s "$HOME/data/oidm-knowledge-sources" sources +``` + +If `sources` already exists, inspect it before changing it. For a custom collection location, link to that location instead. The command does not alter this symlink automatically. + +## Read the manifest + +Each source has a stable ID, title, organization where known, source date where known, source status, sensitivity, and handling instructions. Unknown organizations/dates remain `null`. Month-only dates reflect the evidence's precision. `added_at` on artifacts is the observed Tigris object timestamp, not the source's authorship date. + +Each artifact records its own ID, source ID, local path, bucket key, byte size, SHA-256, role, description, and parent artifact IDs. Paths are relative to the collection. IDs identify the retained record; preserve an existing ID when deliberately relocating its file. The checksum identifies the current expected bytes. `verified_at` records storage-integrity verification, not human acceptance of the source's claims. + +Sensitivity controls use of the source, not the bucket's network access. All artifacts remain in private storage, including artifacts of public talks: + +- `public`: a public presentation; cite its actual statements and preserve qualifications. +- `restricted-until-published`: unpublished manuscript material; paraphrase only and redraw figures for public references. +- `internal`: source ideas may inform references under their recorded rules; do not reproduce private correspondence, live board URLs, or internal logistics. + +Read `handling` before opening an artifact. The JDIM PDF packets and whole-packet text extractions include restricted sections. Use only the specified clean manuscript pages for synthesis. Never read or summarize reviewer correspondence. Hash verification treats files as opaque bytes and grants no permission to read their contents. + +Board sensitivity is conservatively `internal` because publication status is not established by capture. Public-facing subject matter does not establish permission to reproduce an original board. The source date and publication status can be corrected when evidence is supplied; retrieval timestamps do not fill those fields. Other source statuses are retained from the existing source inventory and have not been rechecked for publication since that review. + +## Retained material outside the source manifest + +The bucket contains 307 objects. The manifest selects 118. The other 189 remain untouched: + +| Classification | Files | Treatment | +|---|---:|---| +| Older board screenshots | 152 | Retained; use the native captures for new references. | +| Private review findings and responses | 31 | Retained separately from independent source evidence. | +| Working research, combined transcription, and maintenance notes | 5 | Retained for local investigation; not default source inputs. | +| Private capture index | 1 | Retained with live source URLs; not copied into the public manifest. | + +Private metadata beside the local collection records the entire collection: `.local-inventory.json`, `.remote-inventory.json`, `.remote-verification.json`, and `.classified-inventory.json`. These are not committed or uploaded by this task. The default pull/check commands intentionally cover only the tracked source manifest; they do not recover the excluded review/research files or promise a complete working-directory mirror. + +## Change a source record + +Add a source or artifact only after identifying its status, sensitivity, and relationship to retained material. Compare local and remote bytes before replacing an expected checksum. Keep a changed upstream document's prior version when it supplies evidence for existing references. Do not change a manifest hash merely to silence a failed check. + +Run `task sources:manifest`, `task sources:check`, and the source-tool tests after editing the manifest or recovery code: + +```bash +python3 -m unittest discover -s tests -p test_sources.py +``` + +Update reference citations when their source version changes. Record meaningful source changes and verification in the existing implementation plan and development log. Uploads remain explicit operations, separate from the pull/check tool. diff --git a/knowledge/applications/finding-and-location-coding.md b/knowledge/applications/finding-and-location-coding.md deleted file mode 100644 index 58064bc..0000000 --- a/knowledge/applications/finding-and-location-coding.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -type: Concept -title: Finding and location coding -description: How extracted findings are assigned OIFM finding codes and RadLex anatomic location codes, as a deterministic index lookup followed by language model term generation and selection. -tags: [applications, coding, anatomic-locations, llm, extraction] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -stale_after: 2027-09-21 -sources: - - id: coding-design - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/coding-agent-design.md - title: Coding agent design, imaging-problem-list dev branch - last_modified: 2026-03-16 - - id: coding-prompts - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/coding-agent-prompts.md - title: Coding agent prompt catalog, imaging-problem-list dev branch - - id: coding-archive - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/archive/finding-and-location-code-assignment-plan.md - title: Finding and location code assignment plan, version 3, archived, imaging-problem-list dev branch - last_modified: 2026-02-19 - - id: anat-rules - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/anatomic-location-assignment-rules.md - title: Anatomic location assignment rules, imaging-problem-list dev branch - - id: anat-plan - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/anatomic-location-efl-ipl.md - title: Anatomic locations for Exam Finding Lists and Imaging Problem Lists plan, imaging-problem-list dev branch - last_modified: 2026-06-10 ---- - -# Why coding is its own step - -[Extraction](/glossary/extraction.md) asks what the report says. Coding asks which existing definition that corresponds to. Keeping them apart is the central design decision of the current pipeline: extraction output persists without codes, and coding is triggered separately, can be re-run with different models, and never re-reads the report.[^coding-design] Two earlier designs did it inside extraction and were retired. - -The two axes are coded independently. A finding gets an [OIFM identifier](/glossary/oifm.md) naming a [finding model](/glossary/finding-model.md) definition. Its location, when the report gives one, gets a [RadLex identifier](/glossary/radlex-id.md) from the [anatomic locations](/glossary/anatomic-location.md) data set. Either can succeed while the other fails. - -# The pipeline - -Coding operates on the flat merged finding list of a completed extraction rather than on report chunks. Each finding already carries its name, [presence](/glossary/presence.md), location fields, verbatim quote, and source section, and the design states that this is sufficient context; chunk-level ambiguity was already resolved during extraction.[^coding-design] - -Five phases, of which two involve language model calls. - -| Phase | What happens | -|---|---| -| 1. Fast path | Exact or synonym lookup in the finding index by name, and in the anatomic location index by specific anatomy. Findings that resolve skip the rest for that axis | -| 2. Term generation | Two agents, one for findings and one for locations, generate two or three diverse search terms for each finding that missed the fast path | -| 3. Index search | Batch embedding search over all terms at once; results are distributed back per finding, deduplicated, and capped | -| 4. Code selection | Two agents, one for findings and one for locations, choose from the candidate set, running concurrently under a semaphore | -| 5. Assembly | Fast-path and selected results merge into one coding bundle per finding | - -Four agents in total. Term generation and selection use different models on the stated ground that proposing search synonyms needs no reasoning while judging candidate fit does; traces showed reasoning tokens spent on term generation with no quality benefit. - -Every phase catches its own failures and degrades rather than aborting. A term generation failure falls back to using the finding name as the search term. A search failure or a selection failure marks that finding unresolved. One finding's failure does not block the others. - -The prompts carry the exam information, modality, body part, and study description, because it disambiguates both terms and candidates, and deliberately omit the full report text; testing found identical results with 22 percent fewer input tokens without it.[^coding-prompts] - -## What the fast path is worth - -In prototype testing on chest radiograph extractions, exact and synonym lookup resolved 13 of 16 unique finding names and 12 of 15 unique locations with no model call at all.[^coding-design] The same testing found that a purely deterministic top-candidate choice was not adequate for locations: location assignment needs contextual reasoning even when the finding code resolves deterministically. - -# The coding record - -Each finding carries a coding bundle holding one finding code and a list of location codes. The list is plural because a finding can span sides or structures, so "lungs" resolves to both the left and the right lung.[^coding-design] - -```python -class FindingCodingBundle(StrictBaseModel): - finding_code: FindingCode = Field(default_factory=FindingCode) - location_codes: list[LocationCode] = Field(default_factory=list) -``` - -Both code objects record not only the answer but how it was reached and, when it failed, why. - -| Field | Finding code | Location code | -|---|---|---| -| `status` | `coded` or `unmapped` | `coded` or `unmapped` | -| identifier | `oifm_id`, `oifm_name` | `location_id`, `location_name` | -| `method` | `fast-path`, `llm`, or `unresolved` | same three values | -| `reason` | why it is unresolved | why it is unresolved | -| `reasoning` | the model's explanation of the choice or the rejection | same | -| `candidates` | the alternates that were considered | the alternates that were considered | -| `closest_candidate_id` | the nearest miss when unresolved | not carried | - -The unresolved reasons are the part worth reading closely, because they are designed to report gaps rather than to hide them. - -| Finding reason | Meaning | -|---|---| -| `too_specific` | The candidates narrow beyond what the report states | -| `too_broad` | The candidates are too general to be useful | -| `wrong_concept` | The candidates are different clinical entities | -| `definition_mismatch` | A candidate's name matches but its definition differs, which flags a gap in the ontology | -| `no_candidates` | The search returned nothing | -| `coding_error` | A model or infrastructure failure | - -| Location reason | Meaning | -|---|---| -| `no_candidate_match` | The location is known but no candidate fits, which flags a gap in the index | -| `location_unknown` | The location cannot be determined from the available context | -| `no_candidates` | The search returned nothing | -| `coding_error` | A model or infrastructure failure | - -Two of these, `definition_mismatch` and `no_candidate_match`, exist specifically so that an unresolved finding becomes evidence about content coverage rather than noise. They are the pipeline's channel back to [the content catalog](/semantic-foundation/finding-models/content-catalog.md) and [the anatomic location data model](/semantic-foundation/anatomic-locations/data-model.md). - -The model's own explanation is persisted with the extraction but never written to logs or trace attributes, because it can quote report content. - -# Where the location comes from - -Choosing a code is the second half of the problem. The first is deciding which anatomic structure the report actually asserts, and that is governed by a written precedence ladder, applied per observation and agreed during a correction pass over the sample data.[^anat-rules] In summary: - -1. **Explicit anatomy in the report text or section context wins.** Use the most specific structure stated, sided only if a side is stated. A section heading counts as context. -2. **Otherwise use the finding's own target organ**, at the finding's anatomic granularity and never finer. -3. **Otherwise fall back to the exam-scoped coarse region**, sided only if the exam is sided. - -An organ always beats the exam region when the finding has a real target organ, so "lung bases clear" on an abdominal study codes to lung rather than abdomen. Laterality is resolved against the whole report section rather than the isolated quote, because the side is often stated only in a heading; but a side is never inferred from a different exam or from clinical priors. Bilateral findings split into left and right when the lesions are separable and stay generic and unsided when the process is one diffuse entity. A structure absent from the ontology is left unassigned rather than forced to a wrong code. The full rules, including the worked examples and the exam-to-region map, are in [the assignment rules reference](/data-structures/anatomic-location-assignment-rules.md). - -The rules name one limitation they do not solve. A generic location and a specific one for the same finding code across exams, "kidney" and "left kidney", still produce separate [Imaging Problem List](/glossary/imaging-problem-list.md) groups. Deciding whether those are one problem or two is called anatomic-compatibility reconciliation and is an open follow-on.[^anat-plan] - -# The review artifact - -Coding decisions are reviewable as a spreadsheet, not only as JSON. The enrichment script that ran the sample Exam Finding Lists through the production pipeline writes a review CSV of every location decision alongside the enriched files. In the completed pass of 2026-06-10, 260 of 275 findings received a location, and the regenerated [Imaging Problem List](/data-structures/imaging-problem-list.md) was regrouped by finding code together with location identifier.[^anat-plan] - -# Earlier designs, now archived - -Three generations exist in the repository, and only the third is current. - -| Generation | Shape | Status | -|---|---|---| -| Per-finding deterministic then adjudication | One deterministic attempt per finding, then an adjudicating model call | Retired | -| Version 3, batch per chunk | Fast path, then three model calls per report chunk: generate search terms for findings and locations together, select finding codes, select location codes | Marked completed 2026-02-19, then superseded | -| Current, flat merged finding list | Fast path, then four agents across two phases, operating on the merged finding list rather than per chunk | Current, implementation complete on `dev` | - -The move from version 3 to the current design changed both the unit of work, from chunk to merged finding list, and the agent count, from three to four, by splitting term generation into separate finding and location agents so each could use a cheaper model.[^coding-archive][^coding-design] - -# Where this sits - -Coding is a stage of [the report extraction platform](/applications/report-extraction-platform.md) and runs as its own asynchronous job. Its inputs are the finding model corpus and the anatomic location data set, and its output is what makes an [Observation](/glossary/observation.md) interoperable at all: without codes, an extracted finding is text. See [the architecture overview](/overview/architecture.md) for why the identifiers are the joints of the whole system. - -[^coding-design]: Coding agent design, imaging-problem-list dev branch -[^coding-prompts]: Coding agent prompt catalog, imaging-problem-list dev branch -[^coding-archive]: Finding and location code assignment plan, version 3, archived -[^anat-rules]: Anatomic location assignment rules, imaging-problem-list dev branch -[^anat-plan]: Anatomic locations for Exam Finding Lists and Imaging Problem Lists plan diff --git a/knowledge/applications/finding-model-forge.md b/knowledge/applications/finding-model-forge.md deleted file mode 100644 index 206e875..0000000 --- a/knowledge/applications/finding-model-forge.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -type: Project Profile -title: Finding Model Forge -description: The web application at fmf.oidm.org where contributors author finding model definitions through an AI-assisted wizard and move them through a draft review lifecycle. -tags: [applications, finding-models, authoring, llm, mongodb] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -stale_after: 2027-09-21 -sources: - - id: forge-readme - resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/README.md - title: FindingModelForge README, dev branch - - id: forge-workflow - resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/finding-model-creation-workflow.md - title: Finding model creation workflow, FindingModelForge dev branch - last_modified: 2025-08-31 - - id: forge-draft - resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/DRAFT_WORKFLOW.md - title: Finding model draft workflow, FindingModelForge dev branch - - id: forge-db - resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/database.md - title: MongoDB collections and access, FindingModelForge dev branch - - id: forge-public-review - resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/tasks/done/PRD-public-draft-review.md - title: Public draft review feature requirements, FindingModelForge dev branch - - id: forge-comments - resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/tasks/done/comments-feature-prd.md - title: Comments feature requirements, FindingModelForge dev branch - - id: forge-iteration - resource: https://github.com/openimagingdata/FindingModelForge/blob/af357f8decf029d99863ece63f03e1a7e86c18e5/tasks/model_iteration_feature_plan.md - title: Model iteration feature implementation plan, FindingModelForge feature/model-editing branch - last_modified: 2025-11-27 - - id: forge-issue-13 - resource: https://github.com/openimagingdata/FindingModelForge/issues/13 - title: FindingModelForge issue 13, "Implement model editing", opened 2025-10-16 - - id: forge-zold - resource: https://github.com/openimagingdata/FindingModelForge/tree/f55dc858e1e3c3fde64ccd0d53b46ec07374ede6/zOld/plans - title: FindingModelForge zOld/plans, the superseded application's planning notes - - id: deck - resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal - title: Open Imaging Data Model 2026 Status Update, January 2026 ---- - -# Purpose - -Finding Model Forge is the authoring front end of the Open Imaging Data Model (OIDM). It exists so that a radiologist who is not a programmer can create a [finding model](/glossary/finding-model.md) definition, have identifiers and standard codes assigned automatically, and put the result in front of reviewers. The January 2026 status deck names it as the tool behind the claim that finding models are a rapid-innovation workbench ahead of formal [common data element](/glossary/cde.md) adoption.[^deck] - -The application is a FastAPI web wrapper around the `findingmodel` Python library, which supplies the actual generation, similarity search, and identifier assignment.[^forge-readme] The library is documented under [the finding models area](/semantic-foundation/finding-models/authoring-workflow.md); this profile covers the application. - -# What a user does with it - -Sign-in is GitHub OAuth. After that the creation flow is two steps followed by draft editing, streamlined from an earlier five-step wizard.[^forge-workflow] The whole flow happens inside one page: the server returns HTTP 303 responses, HTMX intercepts them and swaps content into a single container, and the browser never navigates. The creation-workflow document is emphatic about this because it changes how the flow is tested. - -| Stage | User action | What the server does | -|---|---|---| -| 1. Basic information | Enter the finding name, press "Generate Description" | Generates a description from the name | -| 2. Description refinement | Review or edit the description, optionally add synonyms, press "Check for Similar" | Runs a semantic similarity search against existing models; shows near-matches, or creates a draft if there are none | -| 3. Draft editing | Edit description, manage synonyms, write the attributes in markdown; press "Update & Preview" | Autosaves as you type, then generates the full finding model JSON including identifiers and standard codes | -| 4. Preview and submission | Review the generated model, press "Submit Draft" | Moves the draft to `submitted` and locks editing | - -The finding name is read-only once the draft exists. A creation session tracks progress through the first two steps and expires after an hour of inactivity. Drafts appear on the user's profile page, where editing can be resumed; resuming a name that was already submitted lands on the final display view instead of the editor.[^forge-draft] - -# The draft object and its lifecycle - -A draft is the unit of work. It carries the owning user, the finding name, the human inputs (description, synonyms, attributes markdown), the server-generated finding model JSON, a status, and an action log of timestamped entries recording who did what.[^forge-draft] - -```text -draft ──submit──▶ submitted ──▶ under-review ──▶ added - └──▶ declined -``` - -Five statuses exist: `draft`, `submitted`, `under-review`, `added`, and `declined`. Submission locks further editing; everything after it is administrative review. A peer-review layer sits alongside this lifecycle on the development branch: a public draft state lets other contributors read and comment on a draft before it is submitted, and a comment feature is scoped to drafts in the submitted, under-review, added, and declined states. Both are stated as serving collaborative refinement rather than approval gating.[^forge-public-review][^forge-comments] - -# Data it reads and writes - -MongoDB holds drafts, users, and organizations, accessed asynchronously through Motor.[^forge-db] Redis provides a caching layer for user and finding model data; the implementation always calls the cache, and the calls become safe no-ops when Redis is unavailable.[^forge-draft] Two DuckDB files supplied by the `findingmodel` library must be present in the platform data directory, one for finding models and one for [anatomic locations](/glossary/anatomic-location.md), which is how similarity search and code lookup work offline of any web service.[^forge-readme] - -The output format is the finding model JSON defined by [the finding model format](/semantic-foundation/finding-models/finding-model-format.md), carrying an [OIFM identifier](/glossary/oifm.md) and per-[attribute](/glossary/attribute.md) identifiers. Drafts store the generated JSON inline; the definitive corpus lives in the separate content repository described in [the content catalog](/semantic-foundation/finding-models/content-catalog.md). - -# Language model use - -Three generation steps are documented, with the timeouts the application budgets for them.[^forge-workflow] - -| Step | Task | Budgeted time | -|---|---|---| -| Stage 1 | Generate a description from the finding name | 2 to 5 seconds, with a default description as fallback | -| Stage 2 | Semantic similarity search against existing models | 15 to 30 seconds | -| Stage 3 | Generate the complete finding model JSON, including identifier assignment and standard codes | 30 to 90 seconds | - -All three are calls into the `findingmodel` library rather than code in this repository. - -# Architecture - -One FastAPI process serves both the pages and the API. Templates are Jinja2, with Flowbite components for layout, Alpine.js for client state, and HTMX driving every transition as a server-rendered fragment swap; the house rule is to keep interactivity in Alpine and HTMX rather than custom JavaScript. MongoDB through Motor is the store, Redis the cache, and the `findingmodel` library the engine. Static assets are built with Vite and resolved through its manifest.[^forge-readme][^forge-draft] - -# Deployment - -Live at [fmf.oidm.org](https://fmf.oidm.org). The container needs MongoDB and Redis running alongside it, a GitHub OAuth application for sign-in, and the two DuckDB data files.[^forge-readme] Bringing the deployment compose file into the repository is open issue 9, and a development compose file is open issue 7. Install and run instructions stay with the code; this profile does not repeat them. - -# Repository and branch of record - -`FindingModelForge` in the `openimagingdata` organization. See [the repository map](/repositories/repository-map.md) for how it relates to everything else. - -| Branch | Tip commit | Date | State | -|---|---|---|---| -| `main` | `f55dc85` | 2025-08-20 | Stale relative to `dev`; the deployed feature set predates the refactor | -| `dev` | `15d9ebc` | 2026-01-02 | Branch of record, 96 commits ahead of `main`, unmerged | -| `feature/model-editing` | `af357f8` | 2025-11-28 | 60 commits ahead of `main`, unmerged | - -Neither long-lived branch has an open pull request as of 2026-09-21. - -# What is in flight - -**The `dev` refactor.** Ninety-six commits consolidate a service-layer extraction, a router cleanup, standardization of the HTMX and Alpine patterns, and a rebuilt test infrastructure. The branch's own complexity assessment names what remains in the service and router layers after that work. The peer-review workflow described above, public drafts plus comments, is finished on `dev` and absent from `main`.[^forge-public-review][^forge-comments] - -**Model editing, issue 13.** Opened 2025-10-16 and still open. The implementation on `feature/model-editing` answers it with AI-assisted *iterations* rather than manual editing: a user submits a natural-language description of the change they want, and the library applies it.[^forge-iteration] The constraints are explicit. The model name cannot change, one iteration draft exists per user per published model, the existing action log carries the audit trail, and manual edits to an iteration draft are not permitted. It builds on the `model_editor` module added in `findingmodel` 0.6.0, whose guardrails preserve the [OIFM identifiers](/glossary/oifm.md) and reject unsafe changes. Sprint 1, natural-language iteration, is complete as of 2025-11-27. Sprint 2, markdown-edit iteration, and Sprint 3, enhanced history, are not started. - -# Open issues - -| Issue | Title | Opened | State | -|---|---|---|---| -| 13 | Implement model editing | 2025-10-16 | Open; partly implemented on `feature/model-editing` | -| 9 | Bring the VPS compose/deploy into repository | 2025-05-30 | Open | -| 7 | Make a dev Docker Compose file | 2025-05-27 | Open | - -# History - -A superseded version of the application survives in the repository under `zOld/`, together with its planning notes.[^forge-zold] Those notes record decisions the current application still reflects: a deliberate commitment to MongoDB with the Beanie object-document mapper after considering embedded vector stores, a front end on the same FastAPI instance as the API, and GitHub OAuth holding authorization in the session. The three mini-sprint task lists from November 2024 through April 2025 show the feature set arriving in order, beginning with generating a finding model from nothing but a finding name, then adding identifiers, a user table, and searchable [common data element](/glossary/cde.md) content. Most of their boxes are still unchecked, including batch creation from a CSV file and links from a finding model to its eventual CDE codes. - -# What it realizes - -Finding Model Forge is where the [contributor](/glossary/contributor.md) meets the [finding model](/glossary/finding-model.md) format. It assigns [OIFM identifiers](/glossary/oifm.md) with their [organization code](/glossary/oidm-organization-code.md), writes [attributes](/glossary/attribute.md) and their values, and attaches [index codes](/glossary/index-code.md) into external terminologies. Its place in the stack is drawn in [the architecture overview](/overview/architecture.md), and the authoring path it implements is described end to end in [the authoring workflow](/semantic-foundation/finding-models/authoring-workflow.md). - -[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 -[^forge-readme]: FindingModelForge README, dev branch -[^forge-workflow]: Finding model creation workflow, FindingModelForge dev branch -[^forge-draft]: Finding model draft workflow, FindingModelForge dev branch -[^forge-db]: MongoDB collections and access, FindingModelForge dev branch -[^forge-public-review]: Public draft review feature requirements, FindingModelForge dev branch -[^forge-comments]: Comments feature requirements, FindingModelForge dev branch -[^forge-iteration]: Model iteration feature implementation plan, FindingModelForge feature/model-editing branch -[^forge-zold]: FindingModelForge zOld/plans, the superseded application's planning notes diff --git a/knowledge/applications/finding-models-site.md b/knowledge/applications/finding-models-site.md deleted file mode 100644 index c0e728c..0000000 --- a/knowledge/applications/finding-models-site.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -type: Project Profile -title: Finding model catalog site -description: The static Astro site that renders the finding model corpus as browsable pages, building it from the content repository pulled in as a git submodule. -tags: [applications, finding-models, catalog, astro] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -stale_after: 2027-09-21 -sources: - - id: site-config - resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/astro.config.mjs - title: finding-models-site Astro configuration, main branch - - id: site-loader - resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/src/data/loadFindingModels.ts - title: finding-models-site build-time model loader, main branch - - id: site-index - resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/src/pages/index.astro - title: finding-models-site home page, main branch - - id: site-submodule - resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/.gitmodules - title: finding-models-site submodule declaration, main branch - - id: site-deploy - resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/.github/workflows/deploy.yml - title: finding-models-site GitHub Pages deployment workflow, main branch ---- - -# Purpose - -The catalog site is the public reading surface for the [finding model](/glossary/finding-model.md) corpus. It answers one question, "what definitions exist and what is in them," without requiring a reader to install anything, clone a repository, or read JSON. [Finding Model Forge](/applications/finding-model-forge.md) writes definitions; this site displays them. - -# What a user does with it - -Three things, from the home page.[^site-index] - -- **Browse the catalog.** A "Browse Finding Models" link leads to a list page, and each entry leads to a detail page for one definition. -- **Start authoring.** A "Launch Finding Model Forge" link goes to the sign-in page at `fmf.oidm.org`. -- **Report a problem.** A "Submit Issue" link opens a new issue against the `findingmodels` content repository, so corrections go to the content rather than to the site. - -A search box is present on the home page but is not wired to anything; it is markup only. - -# Data it reads - -The site holds no content of its own. The `findingmodels` content repository is attached as a git submodule at `external/findingmodels`.[^site-submodule] At build time, a loader module reads every `.json` file in that submodule's `defs/` directory, parses each one, and derives the page slug from the file name.[^site-loader] There is no database, no API call, and no runtime fetch. A definition appears on the site only after the submodule pointer is advanced and the site is rebuilt. - -```typescript -const modelDir = path.resolve('./external/findingmodels/defs'); -``` - -That single line is the whole coupling between the site and the corpus, and it is why the content repository stays the source of truth for finding model identity. The corpus itself is described in [the content catalog](/semantic-foundation/finding-models/content-catalog.md) and the format in [the finding model format](/semantic-foundation/finding-models/finding-model-format.md). - -# Language model use - -None. The site is a deterministic renderer. - -# Architecture - -Astro 5 in static output mode, with Tailwind CSS 4 applied through the Astro Vite plugin and pnpm as the package manager.[^site-config] Four source files carry the whole application: a layout, a home page, a model list page, and a model detail page parameterized by slug. The configuration sets a base path of `/finding-models-site/`, which is what makes the published URLs resolve under the project's GitHub Pages subpath. - -# Deployment - -Live at [openimagingdata.github.io/finding-models-site](https://openimagingdata.github.io/finding-models-site/), verified responding on 2026-09-21. A GitHub Actions workflow triggers on every push to `main`, checks the repository out with submodules, installs with pnpm, runs the Astro build, and publishes `dist/` to GitHub Pages.[^site-deploy] Because the workflow fires on pushes to this repository rather than on changes in the content repository, new definitions reach the site only when someone updates the submodule pointer here. - -# Repository and branch of record - -`finding-models-site` in the `openimagingdata` organization; see [the repository map](/repositories/repository-map.md). - -| Property | Value | -|---|---| -| Branch of record | `main`, the only branch | -| Tip commit | `6298fa3`, 2025-06-19 | -| Open issues | None | -| Open pull requests | None | -| Status | Maintenance: it is the current artifact for its purpose, with no commits since June 2025 | - -The repository's README is the unmodified Astro starter boilerplate and describes nothing project-specific. - -# What it realizes - -The catalog site is the simplest realization of the claim that a [finding model](/glossary/finding-model.md) definition is a portable file rather than a database row. It needs no service, no credentials, and no synchronization, and it proves that a consumer can read the corpus by reading files. The same property is what lets the extraction platform resolve [OIFM identifiers](/glossary/oifm.md) against a published registry file; see [the architecture overview](/overview/architecture.md). - -[^site-config]: finding-models-site Astro configuration, main branch -[^site-loader]: finding-models-site build-time model loader, main branch -[^site-index]: finding-models-site home page, main branch -[^site-submodule]: finding-models-site submodule declaration, main branch -[^site-deploy]: finding-models-site GitHub Pages deployment workflow, main branch diff --git a/knowledge/applications/imaging-problem-list-viewer.md b/knowledge/applications/imaging-problem-list-viewer.md deleted file mode 100644 index 16ca2b6..0000000 --- a/knowledge/applications/imaging-problem-list-viewer.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -type: Project Profile -title: Imaging Problem List viewer -description: The deployed browser application that renders Imaging Problem Lists, its three-level drill-down and status sections, and the anatomy-first second-generation viewer being built on the development branch. -tags: [applications, ipl, viewer, anatomy, frontend] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -stale_after: 2027-09-21 -sources: - - id: ipl-main-readme - resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/viewer/README.md - title: imaging-problem-list viewer README, main branch - - id: ipl-main-claude - resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/CLAUDE.md - title: imaging-problem-list domain model notes, main branch - - id: ipl-deploy - resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/DEPLOYMENT.md - title: imaging-problem-list deployment guide, main branch - - id: ipl-status-commit - resource: https://github.com/openimagingdata/imaging-problem-list/commit/06f64a7893b444b761dc069ed86140a081195eac - title: "imaging-problem-list commit 06f64a7, \"Display findings in status-based sections instead of filtering\", 2025-11-18" - - id: viewer-v2-plan - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/viewer-v2-anatomy-dashboard.md - title: viewer_v2 anatomy-aware IPL viewer plan, imaging-problem-list dev branch - - id: anat-plan - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/anatomic-location-efl-ipl.md - title: Anatomic locations for Exam Finding Lists and Imaging Problem Lists plan, imaging-problem-list dev branch - - id: ipl-dev-claude - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/CLAUDE.md - title: imaging-problem-list domain model notes, dev branch ---- - -# Purpose - -The viewer is the demonstration that an [Imaging Problem List](/glossary/imaging-problem-list.md) is worth assembling. It takes the reorganization of a patient's findings by finding rather than by date and puts it on screen, so that "has this finding ever been described, and is it present now" is a glance instead of a chart review. The January 2026 status deck names an Imaging Problem List browser among the demonstration applications of the applications pillar. - -Two viewers exist. The deployed one is on `main`. A second-generation, anatomy-first viewer called `viewer_v2` sits on `dev` and is not deployed. - -# The deployed viewer - -## What a user does with it - -Navigation is three levels deep, held entirely in client-side state.[^ipl-main-readme] - -1. **Patient level.** The [Imaging Problem List](/data-structures/imaging-problem-list.md): every finding described across the patient's exams. A dropdown switches patients, and a `?patient=` query parameter selects one directly. -2. **Exam level.** Clicking through a finding's exam entry opens that exam's [Exam Finding List](/data-structures/exam-finding-list.md). -3. **Report level.** From there, the raw report text the findings came from. - -Findings are grouped into sections by temporal status rather than filtered by it. Status filtering existed and was removed in favor of status-labeled sections on 2025-11-18.[^ipl-status-commit] Four statuses are computed in the browser from each finding's observation list sorted by date, never stored in the data. - -| Status | Meaning | -|---|---| -| Current | [Present](/glossary/presence.md) on the most recent exam that mentions it | -| Always | Present on every observation | -| Resolved | Present in the past, absent now | -| Never | Never described as present | - -A [body region](/glossary/body-region.md) filter offers chest, abdomen, pelvis and genitourinary, musculoskeletal, and head and neck. It is driven by a static lookup table of 98 finding-to-region entries, which replaced an earlier keyword-matching approach. Clicking a finding's exam group opens a popover listing every observation of that finding in that report, not just the first, grouped by report identifier. Exam type names are shortened for display through a second lookup table, so "MR Brain WO and W contrast IV" reads as "MR Brain w/wo". The design is dark-mode-first, with the preference kept in browser local storage. - -## Data it reads - -Static files only, laid out by patient and exam. - -```text -data/patients.json -data/patients//patient.json -data/patients//ipl.json -data/patients//exams//efl.json -data/patients//exams//report.txt -data/finding_region_mappings.json -data/exam_type_mappings.json -``` - -The deployed bundle carries two patients: one with 10 exams and 98 aggregated Imaging Problem List findings, and one with 2 exams.[^ipl-main-readme] Those files are generated from the sample data described in [sample data](/data-structures/sample-data.md) by scripts in the repository. No server, no API, no database. - -## Language model use - -None in the viewer. Everything it renders was produced upstream. - -## Architecture - -One page of Alpine.js state, with Tailwind and Flowbite loaded from a content delivery network and no build step at all. Status computation, region filtering, and popover assembly are plain JavaScript over the loaded JSON. - -## Deployment - -Live at [imaging-problem-list.pages.dev](https://imaging-problem-list.pages.dev), verified responding on 2026-09-21. Deployment is a Wrangler command that pushes the `viewer/` directory to a Cloudflare Pages project named `imaging-problem-list`; there is no continuous deployment.[^ipl-deploy] - -## A documentation discrepancy - -The prose in the repository's domain notes and the viewer README still describes an older three-state scheme, present, resolved, and not present or ruled out, while the shipped code computes the four states listed above.[^ipl-main-claude] The code is current. This is recorded in [open questions](/roadmap/open-questions.md). - -# viewer_v2, the anatomy-first viewer - -A separate React, Vite, TypeScript, and Tailwind static application under `viewer_v2/` on the `dev` branch. Its plan is marked "Body-map redesign implemented; ready for review" and is not archived, so it is in flight.[^viewer-v2-plan] - -**The governing rule.** The viewer trusts the Imaging Problem List. Each finding identifier in the list is the canonical clinical problem row, and anatomy grouping is presentation only: it must never merge or split findings. Clinical reconciliation of compatible anatomic locations is explicitly out of scope and belongs to the follow-on step in the anatomic location plan.[^anat-plan] - -**What it shows.** A single patient, the ten-exam example, presented anatomy first: an abstract body-map schematic rather than an anatomical illustration, with extremities lateralized so the right side renders on the viewer's left and the left on the viewer's right, and findings with no stated side kept separate from both. Diagram zones show the actual active finding names as clickable chips rather than a count badge. From there the detail pane opens progressively through region, cluster, finding, and observation, with finding timelines, finding-definition metadata, and drill-down to the exam, its Exam Finding List, and the report. The theme is a dark radiology-workstation palette, and the plan states that a light presentation is not acceptable for this version. - -**Evidence highlighting.** Quotes are highlighted in the source report by whitespace-normalized exact match only, after folding Unicode compatibility forms, dashes, and smart quotes. No fuzzy matching. A quote that cannot be located is recorded as a warning in the generated data rather than silently dropped. - -**Data contract.** The browser reads only generated files under `viewer_v2/public/data/`, never the sample data, the viewer's own data directory, or the anatomic location database. A build script assembles that bundle from the example Imaging Problem List, its Exam Finding Lists, the matching report files, finding display metadata, and the `anatomic-locations` package. Every generated top-level file carries a schema version, and the generator fails on duplicate or missing finding identifiers, observations whose report cannot be resolved, or anatomy that resolves to nothing without an explicit unlocalized fallback. A check task regenerates the bundle and fails if the committed output has drifted. - -**Stated caveats in the current pass.** The bundle covers 123 Imaging Problem List findings across 10 exams for one patient. Two findings have no specific location and use the explicit unlocalized fallback. Seven warnings are embedded in the manifest for the interface to surface: four exact-evidence misses, two missing-anatomy fallbacks, and one missing finding definition. - -**Deployment.** The plan names a separate Cloudflare Pages project, `ipl-anatomy`, publishing the built output. No site responds at `ipl-anatomy.pages.dev` as of 2026-09-21, and deployment automation beyond the documented build and deploy commands is listed as out of scope for this slice. - -# Repository and branch of record - -`imaging-problem-list` in the `openimagingdata` organization; see [the repository map](/repositories/repository-map.md). - -| Branch | Tip commit | Date | Role | -|---|---|---|---| -| `main` | `06f64a7` | 2025-11-18 | The deployed viewer and the stable data-structure specification | -| `dev` | `36fa30c` | 2026-07-22 | Branch of record for current state, 258 commits ahead; carries `viewer_v2` and the extraction platform | - -The same repository holds [the report extraction platform](/applications/report-extraction-platform.md). Its one open issue, number 1, asks for a formal model layer for Observation, Exam Finding List, and Imaging Problem List and is tracked in [the data model roadmap](/roadmap/ipl-data-model-system.md). - -# What it realizes - -The viewer is the reading end of the [Observation](/glossary/observation.md) to [Exam Finding List](/glossary/exam-finding-list.md) to [Imaging Problem List](/glossary/imaging-problem-list.md) hierarchy described in [the data structures area](/data-structures/hierarchy.md). `viewer_v2` additionally realizes [anatomic location](/glossary/anatomic-location.md) coding as a navigational axis, which is only possible because location codes were added to the findings themselves under [the assignment rules](/data-structures/anatomic-location-assignment-rules.md), and because the Imaging Problem List grouping key on `dev` became the finding code together with the location identifier.[^ipl-dev-claude] - -[^ipl-main-readme]: imaging-problem-list viewer README, main branch -[^ipl-main-claude]: imaging-problem-list domain model notes, main branch -[^ipl-deploy]: imaging-problem-list deployment guide, main branch -[^ipl-status-commit]: imaging-problem-list commit 06f64a7, 2025-11-18 -[^viewer-v2-plan]: viewer_v2 anatomy-aware IPL viewer plan, imaging-problem-list dev branch -[^anat-plan]: Anatomic locations for Exam Finding Lists and Imaging Problem Lists plan, imaging-problem-list dev branch -[^ipl-dev-claude]: imaging-problem-list domain model notes, dev branch diff --git a/knowledge/applications/index.md b/knowledge/applications/index.md deleted file mode 100644 index b851af9..0000000 --- a/knowledge/applications/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Applications - -Tools built on the OIDM semantic foundation and data structures: what each one is for, who uses it, what it reads and writes, where it runs, and what is in flight on its branches. Every profile names its repository and branch of record; the full repository listing is in [the repository map](/repositories/repository-map.md). - -* [Finding Model Forge](./finding-model-forge.md) - The web application at fmf.oidm.org where contributors author finding model definitions through an AI-assisted wizard and move them through a draft review lifecycle. -* [Finding model catalog site](./finding-models-site.md) - The static Astro site that renders the finding model corpus as browsable pages, building it from the content repository pulled in as a git submodule. -* [Imaging Problem List viewer](./imaging-problem-list-viewer.md) - The deployed browser application that renders Imaging Problem Lists, its three-level drill-down and status sections, and the anatomy-first second-generation viewer being built on the development branch. -* [Report extraction platform](./report-extraction-platform.md) - The extraction, coding, persistence, evaluation, and human-review platform on the imaging-problem-list development branch that turns narrative radiology reports into coded findings. -* [Finding and location coding](./finding-and-location-coding.md) - How extracted findings are assigned OIFM finding codes and RadLex anatomic location codes, as a deterministic index lookup followed by language model term generation and selection. -* [IPL MVP extraction and labeling](./ipl-mvp-extraction.md) - The 2025 four-stage prototype that extracted findings from reports and matched them to finding models by embedding similarity, its measured results, and why it was superseded. -* [Open Imaging Reporting SDK](./reporting-sdk.md) - The vendor-facing reporting toolkit named as a strategic pillar in the 2026 status deck, its 2023 plugin-container precursor, and the fact that no artifact exists. diff --git a/knowledge/applications/report-extraction-platform.md b/knowledge/applications/report-extraction-platform.md deleted file mode 100644 index d4ff408..0000000 --- a/knowledge/applications/report-extraction-platform.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -type: Project Profile -title: Report extraction platform -description: The extraction, coding, persistence, evaluation, and human-review platform on the imaging-problem-list development branch that turns narrative radiology reports into coded findings. -tags: [applications, extraction, llm, ipl, evaluation, phi] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -stale_after: 2027-09-21 -sources: - - id: dev-readme - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/README.md - title: imaging-problem-list README, dev branch - - id: dev-claude - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/CLAUDE.md - title: imaging-problem-list architecture notes, dev branch - - id: initial-plan - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/initial-extraction-plan.md - title: Initial extraction plan and schema, imaging-problem-list dev branch - - id: validator - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/prompts/validator_prompt_example.md - title: Chunk-level validator prompt, imaging-problem-list dev branch - - id: eval-usage - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/eval-usage.md - title: Evaluation harness user guide, imaging-problem-list dev branch - - id: evals-redesign - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extractor-evals-redesign.md - title: Extractor evals redesign plan, imaging-problem-list dev branch - last_modified: 2026-07-07 - - id: review-workflow - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/human-review-workflow.md - title: Human review workflow for gold extractions, imaging-problem-list dev branch - - id: reviewer-plan - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extraction-reviewer.md - title: Standalone extraction reviewer plan, imaging-problem-list dev branch - - id: reviewer-workflows - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extraction-reviewer-workflows.md - title: Extraction reviewer integration and workflow plan, imaging-problem-list dev branch - last_modified: 2026-07-07 - - id: reviewer-ux - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extraction-reviewer-ux.md - title: Extraction reviewer UX evaluation plan, imaging-problem-list dev branch - last_modified: 2026-07-20 - - id: ui-review-ux - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/extractor-ui-review-ux.md - title: Extractor UI review UX evaluation and redesign plan, imaging-problem-list dev branch - last_modified: 2026-07-19 - - id: local-hardening - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/archive/local-only-mode-hardening.md - title: Local-only mode hardening plan, imaging-problem-list dev branch - last_modified: 2026-04-12 - - id: local-tightening - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/local-only-future-tightening.md - title: Future local-only tightening plan, imaging-problem-list dev branch - - id: mlx - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/local-model-mlx-reassessment.md - title: Local model reassessment plan, imaging-problem-list dev branch - last_modified: 2026-07-01 - - id: thinking - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/docs/plans/pydantic-ai-thinking-capability-simplification.md - title: Reasoning plumbing simplification plan, imaging-problem-list dev branch ---- - -# Purpose - -The extraction platform turns narrative radiology report text into structured, coded findings that can be assembled into an [Exam Finding List](/glossary/exam-finding-list.md) and then an [Imaging Problem List](/glossary/imaging-problem-list.md). It is the seventh and current attempt at that problem in this project; the six before it, and what each established, are in [extraction approaches](/history/extraction-approaches.md). - -It lives on the `dev` branch of `imaging-problem-list`, 258 commits ahead of `main` as of tip commit `36fa30c`, 2026-07-22. `main` remains the stable data-structure specification and the deployed [viewer](/applications/imaging-problem-list-viewer.md). Nothing described here has merged. - -# What a user does with it - -Four command-line entry points and one web interface.[^dev-readme] - -| Entry point | What it does | -|---|---| -| `finding-extractor` | Extract findings from one report, choosing model and reasoning level | -| `finding-extractor-batch` | Run a directory of reports, interactively or detached, with resume | -| `finding-extractor-code` | Run the post-extraction coding pipeline over a saved extraction | -| `finding-extractor-eval` | Score extraction quality against a dataset | -| Extractor web interface | Submit a report, browse reports, open an extraction result, and review it | - -The documented human path for producing trustworthy output is a four-step loop: batch-generate one candidate extraction file per report, have a reviewer overread and correct each one, copy the corrected file to a gold file name, and validate every gold file against the extraction model.[^review-workflow] Installation and configuration stay with the code. - -# The data - -**Input.** Plain report text. A helper script splits a spreadsheet of reports into one text file each, stripping attestation and boilerplate lines. - -**Extraction output.** The schema, set out in the original extraction plan, is a finding name, a [presence](/glossary/presence.md) value, an optional location, a list of attributes, and the verbatim report text the finding came from.[^initial-plan] - -| Field | Type | Note | -|---|---|---| -| `finding_name` | string | Standard terminology, not raw report phrasing | -| `presence` | `present`, `absent`, `indeterminate`, `possible` | `possible` exists for hedged language such as "raising the possibility of" or "cannot exclude" | -| `location` | body region, specific anatomy, [laterality](/glossary/laterality.md) | Optional | -| `attributes` | key and value pairs | Standard keys: size, acuity, change from prior, severity, count, morphology | -| `report_text` | string | The verbatim quote supporting the finding | - -Text that is not a finding is captured separately rather than discarded. - -**Persistence.** SQLite through SQLModel, with Alembic migrations. Reports are deduplicated by the SHA-256 hash of their text. Each extraction run is its own row carrying the report link, the timestamp, the model, the reasoning setting, and the full JSON payload; findings and non-finding text live nested inside that payload. Reviewer edits are stored as separate correction rows rather than by mutating the extraction, in three shapes: propose a new finding, suggest an update to an existing one by index or JSON path, and leave a comment for follow-up.[^dev-readme] - -**Coded output.** Coding writes back into the same extraction row, populating each finding's coding bundle and updating the coded and unresolved counts. See [finding and location coding](/applications/finding-and-location-coding.md). - -# Language model use - -Extraction is agentic and multi-provider: OpenAI, Anthropic, Google, OpenRouter, Ollama for local models, and vLLM for on-premises deployments.[^dev-readme] A single reasoning knob with five levels is normalized per provider, because each exposes thinking differently, and some local model families need JSON-schema output mode rather than tool calling.[^dev-claude] - -Three architectural decisions shape the pipeline.[^dev-claude] - -**Chunking.** Long reports are semantically chunked and the chunks extracted concurrently under a bound, then merged and deduplicated. A reviewer sub-agent can flag problems and trigger targeted re-extraction of specific chunks rather than the whole report. - -**Verbatim-quote validation.** Extraction output must carry exact quotes from the report, checked both as an output validator during generation and again afterward. - -**Coding as a separate job.** Mapping findings to standard codes happens after extraction, in its own pipeline, and can be re-run with different models without re-extracting. - -## The validator contract - -The chunk-level validator reviews one chunk's extraction against that chunk's text and returns a single decision object: the chunk identifier, whether to re-extract, a list of problems, and a rationale. The prompt names six issue patterns that justify re-extraction, and one rule that does not.[^validator] - -| Problem type | What it catches | -|---|---| -| `hallucination` | Clinically meaningful content unsupported by the chunk text | -| `missed_finding` | Report text describing a finding that no structure represents | -| `wrong_presence` | Present when it is not, or absent when it is possible | -| `over_specific_finding_name` | A finding name more specific than the text supports | -| `incorrect_blanket_negative` | A blanket negative mapped to the wrong or over-specific absent findings | -| `incorrect_location` | Wrong, too specific, or too general location | - -Formatting and style differences alone are not grounds for re-extraction. The evidence boundary is explicit: only the chunk is evidence, and the surrounding chunks are advisory. The prompt gives one worked mapping, "clear lungs" becoming an absent finding named "pulmonary parenchymal abnormality", which is the treatment of blanket negatives that `CDEStaging` first catalogued as a problem; see [extraction approaches](/history/extraction-approaches.md). - -# Architecture - -FastAPI serves the API. Long work is asynchronous: an extraction or coding request returns 202 with a job identifier, TaskIQ workers pull the job through a Redis broker, and the client polls job status. Configuration is centralized in typed settings under an `IPL_` environment namespace. Observability is Logfire spans plus structured logging, with a stated rule that raw report text and verbatim quotes never appear in span attributes or log fields.[^dev-claude] - -Two browser interfaces serve different people. - -**The extractor interface** is the connected one, built like the viewer with Alpine.js, Flowbite, and Tailwind from a content delivery network and no build step. It submits reports, lists them, and shows extraction results. - -**The standalone extraction reviewer** is a single self-contained HTML file that a non-developer can open locally with no install and no network, walk through an extraction's findings, and mark each one approved, flagged, unsure, or missing with free-text notes; the reviewer returns a zip of per-file review JSON.[^reviewer-plan] Its vanilla JavaScript and absence of any content delivery network are a deliberate exception to the project's frontend conventions, justified by the requirement that it work from a local file with nothing installed.[^reviewer-workflows] Its plan is marked complete, and it has become the primary instrument for extraction quality assurance and for adjudicating gold cases. - -# Evaluation - -A harness runs a named dataset of reports through extraction, compares the output against ground truth, and reports scores, with optional thresholds on finding F1, presence accuracy, and verbatim checks that make the command exit non-zero when they are not met.[^eval-usage] - -The design of what it scores was rewritten for honesty. On 2026-07-07, after three and a half months with no implementation, the evaluation redesign was deliberately descoped rather than discarded; the full plan's thresholds, continuous-integration gating, and thirty-case benchmark were named as what made it unstartable.[^evals-redesign] The motivation is a specific failure: a model-selection round on 2026-05-14 made a local model the default on average latency and a thirty percent increase in findings produced, and neither number can tell a real recall gain from a fabricated finding. - -Version one keeps four things: a quote-first primary matcher, scoring of attribute *values* rather than attribute presence, frozen run configurations with per-case artifacts for reproducibility, a ten-case gold set adjudicated through the standalone reviewer, and a non-blocking scorecard task. The reviewer plan and the evaluation plan are sequenced rather than parallel; the reviewer exists to produce the gold set the evaluation needs.[^reviewer-workflows] - -# Local models and protected health information - -Protected health information is named as the next use case, and the platform's answer is a local-only mode. A hardening pass completed 2026-04-12 moved enforcement out of the command-line boundary into a shared preflight helper that every entry point calls, closing gaps where the batch command, the API, and the worker could still reach a cloud provider with local-only set; it also detects cloud-suffixed model names in the local runtime.[^local-hardening] The evaluation command is explicitly outside the guarantee, on the stated ground that evaluation datasets are curated fixtures rather than patient data. - -Remaining hardening items were shelved with explicit reopen triggers rather than left as an open-ended list. The example given is detecting a local model alias whose underlying definition points at a cloud source, deferred because the runtime's inspection surface is unstable and parsing model definition files is not authoritative.[^local-tightening] - -Local model choice itself is under reassessment rather than settled. The plan opened 2026-07-01 proposes reassessing wholesale rather than patching documentation, on the grounds that both the runtime and the model generations have moved since the May 2026 round, and it flags one new candidate family as unverified for radiology extraction because it is tuned for agentic coding.[^mlx] - -# Plans in flight - -Eight plans are active on `dev`. Facts and dates as stated in each document. - -| Plan | Date | Status | -|---|---|---| -| Extraction reviewer integration and workflows | created 2026-07-07 | Phase A in progress; the reviewer becomes the primary human-review instrument and feeds gold adjudication | -| Extractor evals redesign | updated 2026-07-07 | Active, descoped to version one | -| Extractor interface review UX | created 2026-07-19 | Proposed; its core finding is that the review page does not show the source report text at all | -| Extraction reviewer UX | created 2026-07-20 | Active; Phase 0 complete 2026-07-20, Phases 1 through 3 remain | -| Local model reassessment | created 2026-07-01 | Planned, not started | -| Future local-only tightening | not dated | Not started, active backlog with per-item reopen triggers | -| Reasoning plumbing simplification | not dated | Fully specified, not started; blocked on a library version bump that would retire roughly 728 lines of per-provider translation code[^thinking] | -| Anatomy-first viewer | not dated | Implemented, ready for review; see [the viewer profile](/applications/imaging-problem-list-viewer.md) | - -The anatomic location plan that added location codes to the sample Exam Finding Lists is marked complete as of 2026-06-10, with one follow-on open: reconciling generic against specific locations for the same finding across exams, which today produces separate Imaging Problem List groups. Two plans completed in July 2026 are excluded from the table above. - -# Open issues - -One, number 1, "Create System of Data Models", asking for a formal model layer for [Observation](/glossary/observation.md), Exam Finding List, and Imaging Problem List with schema export. It is broader than any plan above and unclaimed; see [the data model roadmap](/roadmap/ipl-data-model-system.md). The single open pull request is empty, its design document having been moved to a separate repository. - -# What it realizes - -This platform is where [extraction](/glossary/extraction.md) and [coding](/glossary/coding.md) become operations rather than ideas. It produces the [Observations](/glossary/observation.md) that fill an [Exam Finding List](/data-structures/exam-finding-list.md), attaches [anatomic locations](/glossary/anatomic-location.md) under [the assignment rules](/data-structures/anatomic-location-assignment-rules.md), and resolves [OIFM identifiers](/glossary/oifm.md) against the published registry described in [the content catalog](/semantic-foundation/finding-models/content-catalog.md). The [provenance](/glossary/provenance.md) requirement that each Observation carry a marker of how it was produced is met here by storing the model, the reasoning setting, and the verbatim quote with every extraction. Its place in the layered system is drawn in [the architecture overview](/overview/architecture.md). - -[^dev-readme]: imaging-problem-list README, dev branch -[^dev-claude]: imaging-problem-list architecture notes, dev branch -[^initial-plan]: Initial extraction plan and schema, imaging-problem-list dev branch -[^validator]: Chunk-level validator prompt, imaging-problem-list dev branch -[^eval-usage]: Evaluation harness user guide, imaging-problem-list dev branch -[^evals-redesign]: Extractor evals redesign plan, imaging-problem-list dev branch -[^review-workflow]: Human review workflow for gold extractions, imaging-problem-list dev branch -[^reviewer-plan]: Standalone extraction reviewer plan, imaging-problem-list dev branch -[^reviewer-workflows]: Extraction reviewer integration and workflow plan, imaging-problem-list dev branch -[^local-hardening]: Local-only mode hardening plan, imaging-problem-list dev branch -[^local-tightening]: Future local-only tightening plan, imaging-problem-list dev branch -[^mlx]: Local model reassessment plan, imaging-problem-list dev branch -[^thinking]: Reasoning plumbing simplification plan, imaging-problem-list dev branch diff --git a/knowledge/applications/reporting-sdk.md b/knowledge/applications/reporting-sdk.md deleted file mode 100644 index 0cbe934..0000000 --- a/knowledge/applications/reporting-sdk.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -type: Concept -title: Open Imaging Reporting SDK -description: The vendor-facing reporting toolkit named as a strategic pillar in the 2026 status deck, its 2023 plugin-container precursor, and the fact that no artifact exists. -tags: [applications, reporting, roadmap, vendors] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -stale_after: 2027-09-21 -sources: - - id: deck - resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal - title: Open Imaging Data Model 2026 Status Update, January 2026, applications pillar and call to action - - id: site-framework - resource: https://www.openimagingdata.org/oidm-based-next-gen-reporting-assistance/ - title: "Site post: OIDM-Based Next-gen Reporting Assistance Framework, 2023-07-16" - - id: site-about - resource: https://www.openimagingdata.org/about/ - title: openimagingdata.org About page, last modified 2023-06-21 - - id: cde-demo - resource: https://github.com/openimagingdata/CDETemplateDemo/blob/e09f2553de58b2c88ea26e9673750a62803d4dbc/mapper-logic/src/mappers/obsToMustache.ts - title: CDETemplateDemo Observation to template-data mapper, the rendering precedent ---- - -# Status first - -No artifact exists. There is no repository, package, specification, issue, or branch for an Open Imaging Reporting SDK in any repository read for this knowledgebase, and the project lead confirms it as concept only. It appears in this bundle as stated direction, and every document that mentions it says so. The glossary entry is [Open Imaging Reporting SDK](/glossary/open-imaging-reporting-sdk.md); the goal itself is recorded in [the 2026 roadmap](/roadmap/roadmap-2026.md). - -What follows is what has actually been said about it, and the two pieces of working code that show what parts of it would look like. - -# What the deck says - -The January 2026 status update organizes the project into three pillars. The third is applications, and the deck states it as "an Open Imaging Reporting SDK enabling vendor-driven innovation", alongside demonstration applications including an Imaging Problem List browser and early draft-generation tools.[^deck] - -The call to action returns to it in the same terms. The deck asks for an ACR-OIDM structured imaging results working group, for structure first with FHIR and other standards following, and for the work to be vendor-driven, giving reporting software development kits from a reporting vendor as the example of what that means. The SDK is therefore named as something vendors would build on or build, not as something the project commits to shipping. - -# The 2023 precursor - -The idea is not new, and the earliest statement of it is more specific than the deck's. A site post of 2023-07-16 proposes a reporting assistance framework built as a plugin container inside the reporting tool.[^site-framework] - -Its shape, as stated: - -- Developers author plugins in an ordinary programming language, the post naming JavaScript. -- The container gives every plugin a standard data structure, defined by OIDM, holding the relevant context: the findings in the current report, prior reports, and the exams being reported on. -- Each plugin inspects that context and issues standard commands back to the reporting system. The four named are inserting generated text into the report, requesting more information from the radiologist, alerting the radiologist to a problem, and sending data to an external system. -- The container re-runs every plugin whenever the context changes, whether because new machine-generated data arrived or because the radiologist edited the report. - -The post describes the result as a suite of assistant scripts continually reviewing the data around a radiologist and issuing commands to assist. That is a plugin architecture rather than a library, which is the main way it differs from what the deck's shorthand suggests. The post is summarized among [the site articles](/history/site-articles.md). - -# The About page promise - -The About page, last modified 2023-06-21, commits the project to maintaining programming interfaces in at least three languages, TypeScript and JavaScript, Python, and C#.[^site-about] Two of the three were begun and both are now lineage; no C# library was ever written. See [lineage repositories](/history/lineage-repositories.md). - -The libraries that do exist, `findingmodel` with its anatomic locations package and `med-ontology-lookup`, are semantic-foundation tools. They resolve definitions and codes. They are not a reporting toolkit and are not presented as one; see [the terminology lookup profile](/semantic-foundation/terminologies/med-ontology-lookup.md). - -# The one working precedent - -`CDETemplateDemo`, written in 2023 and untouched since, is the only code in the project that does anything a reporting SDK would have to do. It takes a [CDE-labeled FHIR Observation](/glossary/cde-labeled-fhir-observation.md), flattens its components into a dictionary keyed by display name, derives boolean flags including explicit present and absent flags, and renders the result through a text template so that one template produces a full sentence for a present finding and a short clause for an absent one.[^cde-demo] It was demonstrated at SIIM in June 2023 as a web service. - -That is structured data to report prose, without a language model. It is the half of the 2023 framework that the plugins would call when they insert generated text. The full account is in [CDE template rendering](/history/cde-template-rendering.md). - -# What would have to exist - -Stated only as what the sources describe, not as a design. The 2023 post names a standard report-context data structure defined by OIDM; today the closest thing to it is the [Exam Finding List](/data-structures/exam-finding-list.md), which covers the findings of one exam, and the [Imaging Problem List](/data-structures/imaging-problem-list.md), which covers a patient's history. Neither carries the current report text, the order, or the prior reports that the post's context structure and the About page's wider scope both call for, and the [Imaging Persona](/glossary/imaging-persona.md), which is the deck's name for that surrounding clinical context, is itself concept only. The rendering half has a working precedent and no maintained implementation. The plugin container half has no implementation at all. - -# Where this is recorded - -| Claim | Source | Date | -|---|---|---| -| Open Imaging Reporting SDK as the applications pillar | Status deck | January 2026 | -| Vendor-driven reporting SDKs as the call to action | Status deck | January 2026 | -| Plugin container, standard context structure, four commands, continuous re-run | Site post | 2023-07-16 | -| Programming interfaces in TypeScript and JavaScript, Python, and C# | About page | 2023-06-21 | -| Structured Observation rendered to prose through a template | `CDETemplateDemo` | 2023-06 | - -[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 -[^site-framework]: "OIDM-Based Next-gen Reporting Assistance Framework", 2023-07-16 -[^site-about]: openimagingdata.org About page, last modified 2023-06-21 -[^cde-demo]: CDETemplateDemo Observation to template-data mapper diff --git a/knowledge/data-structures/exam-finding-list.md b/knowledge/data-structures/exam-finding-list.md deleted file mode 100644 index ac7ef05..0000000 --- a/knowledge/data-structures/exam-finding-list.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -type: Data Structure -title: Exam Finding List -description: The per-exam structure that holds every finding declared present or absent on one imaging exam, its full field list on both branches, where its contents come from, and the FHIR and IHE encodings it is meant to reach. -tags: [data-structures, exam-finding-list, loinc, fhir, provenance] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -sources: - - id: ipl-main - resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/README.md - title: imaging-problem-list README, main branch, the stable specification - - id: ipl-main-claude - resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/CLAUDE.md - title: imaging-problem-list domain model notes, main branch - - id: ipl-claude-dev - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/CLAUDE.md - title: imaging-problem-list domain model notes, dev branch - - id: efl-sample - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/sample_data/example2/xr_shoulder_20210522_efl.json - title: An Exam Finding List with anatomic locations, imaging-problem-list dev branch - - id: efl-diagram - resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/exam-finding-list.png - title: exam-finding-list.png, the diagram in the imaging-problem-list README - - id: excel-script - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/scripts/generate_efl_from_excel.py - title: generate_efl_from_excel.py, the Exam Finding List generator - - id: deck - resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal - title: "Open Imaging Data Model 2026 Status Update, January 2026" ---- - -# Definition - -"In the context of an imaging exam, a list of the findings declared as present/absent on that exam."[^ipl-main] An [Exam Finding List](/glossary/exam-finding-list.md) is the second level of [the hierarchy](/data-structures/hierarchy.md): every [observation](/data-structures/observation.md) from one exam, wrapped in that exam's identity. - -The specification states two requirements. The list "must also have basic information (keyed by a curated list of [LOINC](/glossary/loinc.md) codes) about what exam this is." And "the same finding type may be declared as present multiple times; each time is a separate entry in the EFL."[^ipl-main] - -# Fields - -The structure is JSON. The table below is the stable specification on `main`, with the one development-branch addition marked.[^ipl-main-claude][^efl-sample] - -| Field | Type | Required | Notes | -|---|---|---|---| -| `$schema` | URL string | present in samples | Points at `github.com/openimagingdata/imaging-problem-list/schema/exam-problem-list-schema.json`. No such file exists; see below. | -| `diagnosticReportId` | UUID string | yes | identity of the report this list came from. The [Imaging Problem List](/data-structures/imaging-problem-list.md) points back at it. | -| `patientInfo.patientIdentifier` | string | yes | medical record number | -| `patientInfo.patientDOB` | date string | yes | birth date | -| `examInfo.studyIdentifier` | string | yes | the exam's own identifier | -| `examInfo.studyDateTime` | ISO 8601 timestamp | yes | when the exam happened; this is what orders observations over time | -| `examInfo.studyLoincCode` | LOINC code | yes | the [exam type](/glossary/exam-type.md), for example `26158-6` for a left shoulder radiograph | -| `examInfo.studyDescription` | string | yes | display name of the exam | -| `findings[]` | array | yes | one entry per observation instance | -| `findings[].observationId` | string | yes | unique within this list | -| `findings[].findingCode` | `OIFM_[A-Z]{3,4}_[0-9]{6}` | yes | the [OIFM](/glossary/oifm.md) identifier | -| `findings[].findingDescription` | string | yes | the finding model's display name | -| `findings[].attributes[]` | array | yes | each with `attributeCode`, `attributeDescription`, `attributeValueCode`, `attributeValueDescription` | -| `findings[].anatomicLocation` | `{locationId, locationDisplay}` | optional, **development branch only** | a [RadLex identifier](/glossary/radlex-id.md) from the `anatomic-locations` package, "omitted when the finding is not anatomically localizable"[^ipl-claude-dev] | -| `findings[].reportText` | string | optional | verbatim quote from the report | - -`anatomicLocation` is the only field the development branch adds. Data produced against the stable specification does not carry it, and the stable branch's own sample files do not have it. - -A trimmed real record, with a field-by-field walkthrough, is in [the Exam Finding List example](/references/exam-finding-list-example.md). - -## The exam header - -The LOINC code is not decoration. It is the key that lets the problem list say which kind of exam an observation came from, and it is what makes "was this looked for on a chest study?" answerable. The ten sample exams use seven distinct LOINC codes covering radiography, computed tomography, ultrasound, and magnetic resonance. The requirement is stated as a *curated* list of LOINC codes, which is the same need the [exam types](/semantic-foundation/exam-types/) area exists to serve; no such curated list has been published. - -## Repeatable findings - -One entry per instance is a deliberate rule, not an artifact. "The same finding type may appear multiple times in one exam (e.g., multiple kidney stones). Each instance gets a separate entry with its own `observationId`."[^ipl-main-claude] The count of a finding on an exam is therefore the number of entries. The [Imaging Problem List](/data-structures/imaging-problem-list.md) re-collapses them by source report, so a patient timeline shows one row per exam rather than three rows for three stones. - -# Where the contents come from - -The specification is explicit that an Exam Finding List has two possible provenances: it "can be generated by LLM from an existing report or generated live during exam time; each Observation should include some kind of [provenance](/glossary/provenance.md) marker."[^ipl-main] No provenance field exists in the current format. The marker is a stated requirement without an implementation. - -The deck widens the sources further, listing dictation, artificial intelligence tools, and interpretation-time interfaces as the three origins of the findings in one list.[^deck] That is the case the provenance marker is for: an observation produced by an AI tool and an observation dictated by a radiologist would sit side by side in the same list and need to be told apart. The [FHIR lineage](/data-structures/fhir-mapping.md) solved the same problem with `status`, `preliminary` for the machine and `final` for the radiologist. - -In practice today the sample lists were generated from a working spreadsheet. `generate_efl_from_excel.py` reads a worksheet with columns Exam Date, Exam Type, Exam Code, Finding, OIDM Finding Model Name, OIDM FMID, Presence OIFMA_ID, Present/Absent, and Text, groups rows by exam, and writes one file per exam with a fresh UUID as the `diagnosticReportId`.[^excel-script] The live path, from report text through extraction and coding, is the [report extraction platform](/applications/report-extraction-platform.md). - -# The diagram - -The README carries a hand-drawn figure, `exam-finding-list.png`, that makes the case in one picture.[^efl-diagram] On the left is the FINDINGS section of a chest computed tomography report with two kinds of highlight: green over the positive statements the list should capture, including biapical scarring, subsegmental atelectasis, nine separately numbered pulmonary nodules, a calcified granuloma, ectatic pulmonary arteries, coronary artery calcifications, a pacing device, colonic diverticular disease, thoracic dextroscoliosis, a T10 compression fracture, and degenerative changes; and pink over the explicit negatives, "No pleural effusion", "No enlarged supraclavicular, axillary, mediastinal or hilar lymph nodes", and "No pericardial effusion". - -On the right is a table headed "Exam Finding List" with three columns, Finding, CDE Set ID, and Count. Pulmonary nodule carries a count of 8, subsegmental atelectasis 2, and the four negatives carry a count of 0, which is the point: a zero count is a recorded observation of absence, not a missing row. Starred rows mark findings that had no entry in the finding ontology at the time the figure was drawn. The identifiers in the figure are RadElement `RDES####` [CDE set](/glossary/cde-set.md) codes rather than OIFM identifiers, which dates it to before the finding model corpus took that role. - -# FHIR and IHE - -The documented FHIR encoding is "**DiagnosticReport** containing a list of **Observation** objects, with a finding code on each Observation and a list of components with attribute codes and values (especially present/absent and change from prior)."[^ipl-main] One [FHIR DiagnosticReport](/glossary/fhir-diagnostic-report.md), one Observation per finding, attributes as components. - -That mapping is documented and implemented nowhere. No FHIR resource classes exist in the extraction platform's source, and a search across it for `DiagnosticReport` or `fhir` returns nothing. The only real FHIR documents in the repository are two *input* samples, and those do not use the component pattern the specification prescribes, because the pattern describes the output. Full detail is in [FHIR mapping](/data-structures/fhir-mapping.md). - -The deck adds a second target: an Exam Finding List "connects to IHE Imaging Diagnostic Report (IDR) FHIR representation."[^deck] The [IDR profile](/glossary/imaging-diagnostic-report.md) is not mentioned anywhere in the `imaging-problem-list` repository, on any branch. The alignment is a stated goal, and the places where the two models would have to be reconciled are set out in [IHE IDR alignment](/data-structures/ihe-idr-alignment.md). - -# Schema status - -There is no published JSON Schema. Every sample file opens with a `$schema` URL pointing into the repository at `schema/exam-problem-list-schema.json`, and the repository's own notes say plainly that it "doesn't exist yet."[^ipl-main-claude] The structure is defined by the prose above, by the sample data, and by the two scripts that write and read it. A formal model layer with JSON Schema export is [issue 1](https://github.com/openimagingdata/imaging-problem-list/issues/1); see [the IPL data model system roadmap](/roadmap/ipl-data-model-system.md). - -[^ipl-main]: imaging-problem-list README, main branch -[^ipl-main-claude]: imaging-problem-list domain model notes, main branch -[^ipl-claude-dev]: imaging-problem-list domain model notes, dev branch -[^efl-sample]: An Exam Finding List with anatomic locations, dev branch -[^efl-diagram]: exam-finding-list.png, the diagram in the imaging-problem-list README -[^excel-script]: generate_efl_from_excel.py, the Exam Finding List generator -[^deck]: "Open Imaging Data Model 2026 Status Update", January 2026 diff --git a/knowledge/data-structures/imaging-persona.md b/knowledge/data-structures/imaging-persona.md deleted file mode 100644 index ac893c2..0000000 --- a/knowledge/data-structures/imaging-persona.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -type: Concept -title: Imaging Persona -description: "The fourth level of the hierarchy, a stated goal with no artifact: the clinical context around a patient's Imaging Problem List, and the life-cycle uses the 2026 deck gives for it." -tags: [data-structures, imaging-persona, roadmap, concept-only] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -sources: - - id: deck - resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal - title: "Open Imaging Data Model 2026 Status Update: Realizing Object-Oriented Imaging Results, January 2026" - - id: ipl-mvp-fhir - resource: https://github.com/openimagingdata/IPL-MVP-ExtractionAndLabeling/blob/5ea6720e114d3e2cdded5c39ecc5a2eb108e97fb/FHIR_Example_Structure - title: FHIR_Example_Structure, a longitudinal FHIR persistence design note on the IPL-MVP-ExtractionAndLabeling Persistent_FHIR_Resources branch - - id: site-structure - resource: https://www.openimagingdata.org/data-model-structure-and-function/ - title: '"Data Model: Structure and Function", openimagingdata.org, 2024-01-25' ---- - -# Status first - -The [Imaging Persona](/glossary/imaging-persona.md) is a goal. It is named in the January 2026 status deck as the fourth level of [the data structure hierarchy](/data-structures/hierarchy.md) and appears nowhere else in the project. No schema, no sample, no code. Everything below is what the deck states.[^deck] - -# What the deck describes - -The deck's slide is headed "Imaging Persona (big picture)" and gives four categories of context that would surround a patient's [Imaging Problem List](/data-structures/imaging-problem-list.md).[^deck] - -| Category | Contents named | -|---|---| -| Clinical context | orders, indications | -| Medical baseline | problem list, allergies, laboratory results | -| Specialized history | oncology, treatments | -| Surgical history | operative records, pathology, implants | - -None of that is imaging results. The Imaging Problem List is the structure the project builds; the persona is the argument that a problem list on its own does not answer the questions radiology actually gets asked. Three of the four categories come from the electronic medical record rather than from radiology, which makes the persona as much an integration claim as a data structure. - -# Why: the imaging life cycle - -The deck argues for it through a separate slide on what the Imaging Problem List enables at each stage of the imaging life cycle. The uses named there are the ones that need context the problem list does not carry.[^deck] - -| Stage | Uses the deck names | -|---|---| -| Planning | magnetic resonance safety, mobility, prior authorization | -| Intra-exam | rules-based protocoling | -| Interpretation | findings-oriented views in the picture archiving system, real-time quality control | -| Post-interpretation | passive screening, research, outcomes | - -Read against the four categories, the dependency is concrete. Magnetic resonance safety screening is a question about implants, which is surgical history. Prior authorization is a question about the order and the indication. Rules-based protocoling needs both the prior imaging findings and the clinical question. Passive screening needs the medical baseline to know whether a finding matters for this patient. A problem list alone answers none of these; a problem list plus that context answers all four. - -The 2024 site post on the data model made a related claim earlier and in wider terms, listing observations, current report text, imaging studies, patient, order, prior studies, tracked observations, and electronic health record data as the elements the model organizes.[^site-structure] The persona is that list narrowed to the patient-context half and given a name. - -# A related unimplemented idea - -One adjacent design note exists in the repositories, on an unmerged branch of the early extraction prototype. `FHIR_Example_Structure` sketches longitudinal persistence of findings: each finding becomes "a **tracked entity** with a FHIR ID that persists," so a second report describing the same liver lesion links to the existing resource and adds a new dated value rather than creating a second finding.[^ipl-mvp-fhir] The note's worked timeline shows one lesion at 1.6 cm, 2.1 cm, and 2.8 cm across three reports, and its query examples are "show me the timeline for liver lesion" and "how has the renal calculus changed?". - -It is a different idea from the persona. It addresses identity of a finding over time, which the current [Imaging Problem List](/data-structures/imaging-problem-list.md) approximates by grouping on finding code and location rather than by tracking a lesion. It is recorded here because it is the only written treatment anywhere in the repositories of persisting a patient's imaging story as durable resources, which is the substrate a persona would need. It sits on a single-commit branch of a superseded prototype and was never merged; see [IPL-MVP extraction](/applications/ipl-mvp-extraction.md). - -# What would have to be decided - -Nothing about the persona has been specified, so this section states only what the deck leaves open rather than proposing answers. The deck does not say whether the persona is a stored structure or a query-time assembly, which systems own each of the four context categories, how it would be encoded, or who would maintain it. Those questions are carried in [open questions](/roadmap/open-questions.md) and the direction in [the 2026 roadmap](/roadmap/roadmap-2026.md). - -[^deck]: "Open Imaging Data Model 2026 Status Update", January 2026 -[^ipl-mvp-fhir]: FHIR_Example_Structure, IPL-MVP-ExtractionAndLabeling, Persistent_FHIR_Resources branch -[^site-structure]: "Data Model: Structure and Function", openimagingdata.org, 2024-01-25 diff --git a/knowledge/data-structures/index.md b/knowledge/data-structures/index.md deleted file mode 100644 index 0071c8a..0000000 --- a/knowledge/data-structures/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Data structures - -The second OIDM layer: the objects that carry what was seen on an exam, keyed to the semantic foundation by code. The ladder runs Observation, Exam Finding List, Imaging Problem List, Imaging Persona. Start with the hierarchy. - -* [The data structure hierarchy](./hierarchy.md) - The four-level ladder from Observation to Exam Finding List to Imaging Problem List to Imaging Persona, what each level adds, how identifiers thread through it, and how far each level is built. -* [Observation](./observation.md) - The atomic unit of OIDM: one finding, seen or explicitly excluded, on one exam, with its location and attribute values, as it exists today inside an Exam Finding List and in the extraction pipeline. -* [Exam Finding List](./exam-finding-list.md) - The per-exam structure that holds every finding declared present or absent on one imaging exam, its full field list on both branches, where its contents come from, and the FHIR and IHE encodings it is meant to reach. -* [Imaging Problem List](./imaging-problem-list.md) - The per-patient structure that reorganizes a patient's imaging findings by finding rather than by date, its fields, its grouping key, the temporal status computed over it, and what it is meant to become in FHIR. -* [Imaging Persona](./imaging-persona.md) - The fourth level of the hierarchy, a stated goal with no artifact: the clinical context around a patient's Imaging Problem List, and the life-cycle uses the 2026 deck gives for it. -* [Anatomic location assignment rules](./anatomic-location-assignment-rules.md) - The precedence, laterality, bilateral, and specificity rules that decide which anatomic location is attached to a finding in an Exam Finding List, migrated from the imaging-problem-list development branch. -* [Technical imaging findings](./technical-imaging-findings.md) - A draft catalog of modality-specific technical finding language, CT attenuation, MR signal, enhancement, ultrasound echogenicity, and nuclear medicine, with the search-term guidance for coding each, migrated from the imaging-problem-list development branch. -* [FHIR mapping](./fhir-mapping.md) - Everything OIDM has documented about representing its data structures in FHIR, from the CDE-labeled Observation pattern of the lineage repositories to the current Exam Finding List and Imaging Problem List mappings, and the plain fact that no current code emits FHIR. -* [IHE IDR alignment](./ihe-idr-alignment.md) - What the IHE Imaging Diagnostic Report profile specifies for encoding findings as FHIR Observations, the deck's claim that the Exam Finding List connects to it, and the fact that no OIDM repository mentions it. -* [Sample data](./sample-data.md) - The synthetic patients, exams, and findings that make up the Exam Finding List and Imaging Problem List sample data, what changes between branches, where the viewer bundles live, and how to regenerate everything. diff --git a/knowledge/history/index.md b/knowledge/history/index.md deleted file mode 100644 index 439ce6a..0000000 --- a/knowledge/history/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# History - -What was tried before OIDM reached its current shape, what survives, and what superseded the rest. - -* [Timeline](./timeline.md) - A dated record of the Open Imaging Data Model from its anatomic-location prologue through the January 2026 status update and the 2026 work edge. -* [Lineage repositories](./lineage-repositories.md) - The twelve early OIDM repositories, what each one was for, what survives into current work, and what superseded the rest. -* [Extraction approaches](./extraction-approaches.md) - Seven successive attempts at turning radiology report text into structured findings, from a template corpus in 2024 to the current extraction platform, and what each one established. -* [CDE template rendering](./cde-template-rendering.md) - The 2023 CDETemplateDemo pattern that turned a CDE-labeled Observation into report prose through a Mustache template, and how it relates to the reporting SDK direction. -* [Use case catalog](./use-cases.md) - The roughly 25 use case ideas collected in the UseCases repository, organized by category, with the six value categories used to tag them. -* [Site articles](./site-articles.md) - The fifteen posts published on openimagingdata.org between June 2023 and March 2025, plus the About page, each dated, linked, and summarized. diff --git a/knowledge/index.md b/knowledge/index.md index 6f38561..6554620 100644 --- a/knowledge/index.md +++ b/knowledge/index.md @@ -4,19 +4,8 @@ okf_version: "0.2" # Open Imaging Data Model knowledge -Canonical high-level documentation for the Open Imaging Data Model (OIDM). +This directory is the new repository-only OKF knowledge graph. Concept documents will be written from current sources and the owner's conceptual outline. No concept documents have been reconstructed yet. -**Start here:** [What is OIDM](./overview/what-is-oidm.md), then [Architecture](./overview/architecture.md). Look terms up in the [glossary](./glossary/), find where code and content live in the [repository map](./repositories/repository-map.md), and see what is settled versus open in the [roadmap](./roadmap/) and [open questions](./roadmap/open-questions.md). Every document's frontmatter states its type, status, sources, and who generated and verified it; agents should read that before relying on a page. +The outline and reconstruction method are in `docs/plans/implementation.md` at the repository root. The old generated bundle is temporarily held in `archive/knowledge` for checking omissions after fresh drafts are written. It will be deleted when useful material has been recovered. - -* [overview](./overview/) - What OIDM is, the vision, and how the pieces fit -* [glossary](./glossary/) - One term per file, the vocabulary of record -* [semantic-foundation](./semantic-foundation/) - Finding models, common data elements, anatomic locations, exam types, terminologies -* [data-structures](./data-structures/) - Observation, Exam Finding List, Imaging Problem List, Imaging Persona, FHIR mapping -* [applications](./applications/) - Tools built on the foundation: Forge, viewers, extraction platforms -* [roadmap](./roadmap/) - Stated goals and open questions, by area -* [history](./history/) - Lineage repositories, prior approaches, site articles, use cases -* [repositories](./repositories/) - Map of every source repository and its branch of record -* [references](./references/) - Verbatim and near-verbatim source material -* [guides](./guides/) - Authoring conventions and the migration ledger -* [plans](./plans/) - Plans for building and maintaining this knowledgebase +Outward-facing pages are developed separately in `site/`. diff --git a/knowledge/log.md b/knowledge/log.md index ee6957b..b7af5b8 100644 --- a/knowledge/log.md +++ b/knowledge/log.md @@ -1,79 +1,5 @@ -# Update Log +# Knowledge graph change log -## 2026-09-21 -* 2026-09-21 - Added `roadmap/open-questions.md`, 90 numbered entries deduplicated from all ten collecting agents and grouped into finding models, common data elements, anatomic locations, exam types and terminologies, data structures, applications, and the project narrative, each with the sources on each side and an owner where a source names one, plus a separate table of 21 source-document defects with repository and path so they can become issues. Rewritten after the five Phase 2 and 3 agents appended their sections. -* 2026-09-21 - Corrected three roadmap documents against those late sections: the med-ontology-lookup backlog is eleven of twelve issues open rather than nine open and unstarted; findingmodels issue 15 is answered by unmerged work rather than merely overlapping it; the two framings of the RadLex relationship and the 1,016 composite sided identifiers were added to the anatomy roadmap; an identifier lifecycle section covering the absence of any deprecation mechanism and findingmodel issue 42 was added to format evolution. -* 2026-09-21 - Added `roadmap/roadmap-2026.md`, the project-level direction from the January 2026 deck: the three strategic pillars, the ACR-OIDM Structured Imaging Results Working Group proposal with its structure-first and vendor-driven conditions, the four ACR priorities it says the work supports, the three next steps including the use-case pipeline to the CDE group, a per-repository work edge table linking each area roadmap, and the three deck items with no artifact behind them. -* 2026-09-21 - Added `roadmap/finding-model-format-evolution.md`, the four uncoordinated threads for the OIFM record format: the canonical structured-metadata rewrite with its eight fields and failing readiness assessment, the 2026-04-20 source schema version 2 draft (source against hydrated models, canonical attribute files, authored relationships, lifecycle, history sidecars, registries, and its explicit removals), the typed-relationship and derived-differential ideas from the next-generation CDE vocabulary, package and database schema versioning in issues 29 and 30, the superseded eight-facet specification still open as issue 25, the four format-shaping open issues, the three divergent schema extensions on content branches, and the two recorded prose-schema discrepancies. -* 2026-09-21 - Added `roadmap/finding-model-content-direction.md`: the MGB exam-oriented sub-taxonomies as the stated immediate direction with the expected supersession of Gamuts-derived models, the 2,761-row triage task, the five unmerged content branches with counts and dates, the definition cleanup plan's seven ordered categories including the 71 "with"-clause Gamuts patterns and the gamuts reclassification step that needs design discussion, the evidence-gated metadata branch rules and review counts, and the five open content issues. -* 2026-09-21 - Added `roadmap/ipl-data-model-system.md`: the project lead's four-part statement of the Imaging Problem List goals, issue 1 quoted in full substance with each element mapped to a gap that exists today, the eight active development-branch plans with the four that state goals beyond tooling, the anatomic-compatibility reconciliation follow-on quoted from the assignment rules, the documented-and-unimplemented FHIR mapping with the persistent-identity lineage design note, the IHE IDR alignment as a goal with its four unresolved differences, and the three items with no plan behind them. -* 2026-09-21 - Added `roadmap/exam-types.md`: the project lead's three-part goal including the always, usually, and possibly-included anatomy edges, the same goal stated in 2023 and 2024, the radiology domain profile as the one written design with its four Playbook implications, the Playbook governance and licensing facts that constrain any effort, and four unanswered questions including the never-published curated LOINC list and the description inconsistencies in the sample data. -* 2026-09-21 - Added `roadmap/anatomic-locations-and-radlex.md`: RadLex issue 3 with its coverage exit criterion, the 13 September 2026 overlay agreement and its two written consequences, what is owed in each direction with the is-a and structure-type request named as top priority, the two unreconciled datasets, the 2024 BodyPartIndex TODO list with per-item current status, the four tooling items in findingmodel including issues 38 and 39, and the four incompatible body region enumerations. -* 2026-09-21 - Added `roadmap/open-questions.md`, 61 numbered entries deduplicated from the six collecting agents and grouped into finding models, common data elements, anatomic locations, exam types and terminologies, data structures, applications, and the project narrative, each with the sources on each side and an owner where a source names one, plus a separate table of 15 source-document defects with repository and path so they can become issues. -* 2026-09-21 - Listed all seven documents in `roadmap/index.md`. -* Added `glossary/` with 48 Glossary Term documents, the vocabulary of record for OIDM, each giving a sourced definition, synonyms and near-synonyms, identifier form, where the term is used, and any conflict between sources. -* Terms cover the finding model format (OIFM, finding model, attribute, attribute value, index code, tag, contributor, organization code, finding taxonomy), the common data element vocabulary current and next-generation (CDE, CDE set, CDE element, RadElement, DataElement, FindingClass, Measurement, element binding, AssessmentScheme), anatomy (anatomic location, anatomic scope, laterality, contained by and part of, body region), terminologies (RadLex, RadLex RID, SNOMED CT, FMA, UMLS, LOINC, the LOINC/RSNA Radiology Playbook, Gamuts), the data structures (observation, Exam Finding List, Imaging Problem List, Imaging Persona, presence, change from prior, technical finding, exam type), FHIR and IHE encodings (CDE-labeled FHIR Observation, FHIR DiagnosticReport, FHIR Condition, IHE Imaging Diagnostic Report), and the pipeline (extraction, coding, provenance), plus OIDM itself and the Open Imaging Reporting SDK. -* Added `glossary/index.md` listing every term alphabetically with its frontmatter description, generated from the frontmatter so descriptions match exactly. -* Twenty-two conflicts between sources were recorded under "Conflicts" headings in the affected entries and collected for `roadmap/open-questions.md`. The largest are the positional presence dot code, four incompatible body-region enumerations, the component-versus-hasMember disagreement with IHE IDR, and the two unsynchronized anatomic location lineages. -* Added `repositories/repository-map.md`: every source repository with organization, role, status, branch of record, last activity, deployed URL, and bundle coverage; plus a data-flow diagram and a per-repository work edge summary. -* Added `history/timeline.md`: dated record from the 2022 anatomic-location prologue through the 2026 work edge. -* Added `history/lineage-repositories.md`: the twelve lineage repositories, including the FHIR precedent in `OpenImagingDataModel.py` and `FHIRSamples`. -* Added `history/extraction-approaches.md`: seven successive report-to-structure extraction efforts and what each established. -* Added `history/cde-template-rendering.md`: the 2023 Mustache rendering pattern and its relation to the reporting SDK direction. -* Added `history/use-cases.md`: the use case catalog and the six RSNA Reporting Informatics Committee value categories, migrated from `UseCases`. -* Added `history/site-articles.md`: the 15 openimagingdata.org posts and the About page, each verified live on 2026-09-21. -* Filled `history/index.md` and `repositories/index.md`. -* Added `overview/what-is-oidm.md`, `overview/vision.md`, `overview/architecture.md`, and `overview/getting-involved.md`, with `overview/index.md` listing all four. -* Added `references/status-update-2026-01.md`, a slide-by-slide extract of the January 2026 status deck with the collaborators slide reduced to organizations. -* 2026-09-21 - Added `references/finding-model-schema.md`, migrating the findingmodels prose schema and reconciling it field by field against the released Pydantic definitions, with the unmerged metadata fields recorded separately as work edge. -* 2026-09-21 - Added `references/anatomic-location-json-schema.md`, documenting both anatomic location record formats and naming the anatomic-locations package format as current. -* 2026-09-21 - Added `references/exam-finding-list-example.md` and `references/imaging-problem-list-example.md`, two worked examples built from the same synthetic patient in the imaging-problem-list sample data. -* 2026-09-21 - Added `references/oifm-overview-extract.md` and `references/oifm-metadata-fields-extract.md`, near-verbatim extracts of the two OIFM notes on the ACR-RSNA-CDEs next-gen-2026 branch. -* 2026-09-21 - Added `references/cde-schema-differences.md`, migrating the 2023 schema comparison and recording what the later JSON Schema versions changed. -* 2026-09-21 - Listed all seven documents plus the status update deck extract in `references/index.md`. -* 2026-09-21 - Added `semantic-foundation/anatomic-locations/why-anatomic-locations.md`, stating the curation rationale from the project site and tabulating every place anatomic location identifiers are used across OIDM. -* 2026-09-21 - Added `semantic-foundation/anatomic-locations/data-model.md`, describing the record in both lineages with counts verified against both data files, and naming the empty classification fields and the self-referential part-of records. -* 2026-09-21 - Added `semantic-foundation/anatomic-locations/laterality-conventions.md`, migrating the curation conventions from the findingmodel curation skill and joining them to the assignment-time laterality rules from the extraction platform. -* 2026-09-21 - Added `semantic-foundation/anatomic-locations/lineage-and-current-implementation.md`, contrasting the two lineages, naming the anatomic-locations package as current per the build plan, and listing what is unreconciled. -* 2026-09-21 - Added `semantic-foundation/anatomic-locations/radlex-integration.md`, covering RadLex structure, the concept-search work, RSNA/RadLex issue 3, the overlay agreement recorded on the next-gen-2026 branch, and the node request list. -* 2026-09-21 - Added `semantic-foundation/anatomic-locations/tooling.md`, a usage guide for the anatomic-locations package and CLI, the two wrapper libraries, and the raw JSON download. -* 2026-09-21 - Listed all six documents in `semantic-foundation/anatomic-locations/index.md`. -* 2026-09-21 - Added `semantic-foundation/common-data-elements/cdes-and-radelement.md`, describing the CDE set, element, and value model, the RadElement registry and its public API counts, the two repositories that carry the canonical JSON Schema, the RelaxNG-versus-API gap, the CDE-labeled FHIR Observation pattern, and the four ways OIDM uses CDEs today. -* 2026-09-21 - Added `semantic-foundation/common-data-elements/cde-staging.md`, profiling the CDEStaging repository: its file census, its two unreconciled markdown authoring conventions, the report-representation gap analyses, the chest CT prioritization roadmap, the manual path to RadElement, the unmerged conversion pipeline in findingmodels, and its branch and issue state. -* 2026-09-21 - Added `semantic-foundation/common-data-elements/next-generation-vocabulary.md`, recording what the ACR-RSNA-CDEs next-gen-2026 branch defines at commit 44836c1, marked throughout as in-progress design work in an allied project rather than an OIDM specification. -* 2026-09-21 - Listed all three documents in `semantic-foundation/common-data-elements/index.md`. -* 2026-09-21 - Nothing in these three documents is a verbatim migration; catalogs and definition corpora are described and linked, not copied. The one partial migration is recorded in the ledger. -* Added `semantic-foundation/exam-types/overview.md`: exam types as a greenfield area, the goals stated by the project lead in the 2026-09-20 planning interview, the same goal as stated in the 2023 anatomic locations roadmap and the 2024-01-25 site post, what the LOINC/RSNA Radiology Playbook is, and the three places an exam type code does work today. -* Added `semantic-foundation/exam-types/existing-building-blocks.md`: the eight existing building blocks a future exam type layer could use, including the `radiology` profile design, LOINC detection in `molu`, 997 Playbook provenance annotations in the RadLex OWL file, the eight distinct LOINC codes in the imaging-problem-list sample data with counts, the viewer display table, the exam-to-region fallback map, the 281-template RadReport corpus, and the CDE set schema's LOINC allowance. -* Added `semantic-foundation/terminologies/ontologies-used.md`: all eleven external terminologies OIDM touches, with governance, identifier form, the field that carries each one, verified occurrence counts across the finding model corpus and both anatomic location datasets, and which tool resolves each. -* Added `semantic-foundation/terminologies/med-ontology-lookup.md`: the `molu` library and CLI, its two providers, six operations, typed provider failure model, agent skill, six-phase design and twelve-issue backlog (one tracking issue plus eleven focused ones), delegated authentication design, status at `main` commit `a1fd3ae`, and its still-undocumented relationship to `findingmodel`. -* Filled `semantic-foundation/exam-types/index.md` and `semantic-foundation/terminologies/index.md`. -* 2026-09-21 - Added `semantic-foundation/finding-models/why-finding-models.md`, the narrative case for findings as the unit of data, drawn from the OIFM overview note, the January 2026 deck, and the 2023 site post. -* 2026-09-21 - Added `semantic-foundation/finding-models/finding-model-format.md`, the released record format class by class with a real definition as example, plus a labeled section for the eight unreleased metadata fields on `feature/metadata-cleanup`. -* 2026-09-21 - Added `semantic-foundation/finding-models/identifiers.md`, the OIFM and OIFMA grammar, the organization-code registry with corpus counts, the minting and duplicate-check mechanism, and the round-trip regeneration defect filed as findingmodel issue 42. -* 2026-09-21 - Added `semantic-foundation/finding-models/content-catalog.md`, profiling the `findingmodels` repository: 2,382 definitions by source organization, the generated artifacts, the repository conventions, three ways to browse, and six unmerged content branches with counts and dates. -* 2026-09-21 - Added `semantic-foundation/finding-models/finding-taxonomies.md`, the MGB exam-oriented sub-taxonomies on `taxonomy-export-2026-08-15`: columns, hierarchy semantics, 3,789 rows with 1,028 matched, and the 2,761-row triage the README names as next. -* 2026-09-21 - Added `semantic-foundation/finding-models/authoring-workflow.md`, covering Finding Model Forge, the `findingmodel-ai` command line, the three agent skills and their prompt fragments, the pre-commit validator gate, and the two open conventions plans. -* 2026-09-21 - Added `semantic-foundation/finding-models/enrichment-pipeline.md`, separating the shipped sidecar enrichment on `main` from the canonical seven-agent rewrite on the work edge, and recording the branch's own FAIL readiness on age profile and index codes. -* 2026-09-21 - Added `semantic-foundation/finding-models/finding-models-and-cdes.md`, the workbench relationship, how CDE-sourced models entered the corpus, CDEStaging as the informal path, and the IHE IDR grammar both vocabularies must fit. -* 2026-09-21 - Listed all eight documents in `semantic-foundation/finding-models/index.md`. -* 2026-09-21 - Applied the project lead's naming decision: the `lists/` hierarchies are the "MGB exam-oriented sub-taxonomies" on first use and "the sub-taxonomies" after. Retitled `finding-taxonomies.md`, updated its description and source titles, and updated the four references in `content-catalog.md`, `identifiers.md`, and the directory index. -* 2026-09-21 - Added `data-structures/hierarchy.md`, the four-level ladder from Observation to Imaging Persona, with the identifier joins and a per-level implementation status table. -* 2026-09-21 - Added `data-structures/observation.md`, covering the Exam Finding List entry shape, the presence dot codes, the extraction-time Pydantic models on the dev branch and how they map to a coded observation, the FHIR Observation precedent, and issue 1's call for a formal model. -* 2026-09-21 - Added `data-structures/exam-finding-list.md` and `data-structures/imaging-problem-list.md`, with full field tables for both branches, the grouping key change on dev, the four-state computed temporal status, both README diagrams described in words, and the documented but unimplemented FHIR mappings. -* 2026-09-21 - Added `data-structures/imaging-persona.md`, stating it as concept-only from the deck and recording the IPL-MVP longitudinal FHIR persistence note as a related unimplemented idea. -* 2026-09-21 - Migrated `docs/anatomic-location-assignment-rules.md` and `docs/technical-imaging-findings.md` from the imaging-problem-list dev branch as References. -* 2026-09-21 - Added `data-structures/fhir-mapping.md`, collecting the CDE-labeled FHIR Observation pattern, the lineage samples and Pydantic class, both current mappings, the raw PowerScribe input samples, and the evidence that no current code emits FHIR. -* 2026-09-21 - Added `data-structures/ihe-idr-alignment.md`, describing the IDR observation grammar from the next-gen-2026 extract and the four recorded differences from OIDM's encodings. -* 2026-09-21 - Added `data-structures/sample-data.md`, with per-branch counts for the synthetic patients, the file layouts, the viewer bundles, and the four generator scripts. -* 2026-09-21 - Listed all ten documents in `data-structures/index.md`. -* 2026-09-21 - Added `applications/finding-model-forge.md`, profiling the authoring application at fmf.oidm.org: the two-step creation and draft-edit wizard, the five-status draft lifecycle, MongoDB and Redis, the three generation steps with their timeout budgets, the dev refactor and the model-iteration sprint answering issue 13, and the superseded application's planning notes as history. -* 2026-09-21 - Added `applications/finding-models-site.md`, profiling the static Astro catalog: the git submodule coupling to the content repository, the build-time loader, the GitHub Pages workflow, and the live URL verified 2026-09-21. -* 2026-09-21 - Added `applications/imaging-problem-list-viewer.md`, covering the deployed viewer on `main` (three-level drill-down, four computed status sections, region filter, popovers, exam-type display mapping, static data layout, Cloudflare Pages) and `viewer_v2` on `dev` (React and Vite anatomy-first dashboard, presentation-only anatomy grouping, conservative evidence highlighting, generated-data contract, stated caveats, undeployed). -* 2026-09-21 - Added `applications/report-extraction-platform.md`, profiling the `dev`-branch platform: chunked multi-provider extraction, the extraction schema, verbatim-quote validation with the validator's six problem types, SQLModel persistence with corrections, TaskIQ jobs behind FastAPI, the two review interfaces, the evaluation harness and its honesty-first descope, the local-only PHI stance, and the eight active plans with dates. -* 2026-09-21 - Added `applications/finding-and-location-coding.md`, the coding pipeline as documented on `dev`: fast-path index lookup, four agents across term generation and selection, the coding bundle schema and unresolved-reason vocabularies, the anatomic location precedence ladder in summary, the review CSV, and the two archived earlier designs. -* 2026-09-21 - Added `applications/ipl-mvp-extraction.md`, profiling the 2025 four-stage prototype: the thin schema, embedding similarity at a 0.70 threshold, the measured 5 of 87 match rate, the stated limitations, the three unmerged experiment branches, and what carried forward. -* 2026-09-21 - Added `applications/reporting-sdk.md`, recording the SDK as concept only: the deck's applications pillar and call to action, the 2023 plugin-container post in detail, the About page's three-language promise, and the CDETemplateDemo rendering precedent. -* 2026-09-21 - Listed all seven documents in `applications/index.md`. -* **Creation**: bundle skeleton, directory indexes, authoring guide - [authoring guide](/guides/authoring-guide.md). -* **Creation**: knowledgebase build plan - [plan](/plans/2026-09-20-knowledgebase-build-plan.md). +## 2026-10-06 + +- Established a clean knowledge graph for fresh, source-grounded reconstruction. The old generated bundle is temporarily held in `archive/knowledge` and is not the basis for the new graph's structure or prose. diff --git a/knowledge/overview/getting-involved.md b/knowledge/overview/getting-involved.md deleted file mode 100644 index ca3f7c0..0000000 --- a/knowledge/overview/getting-involved.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -type: Guide -title: Getting involved -description: Where the OIDM community gathers, which repositories to start with, how to file issues, how content is contributed, and how this knowledgebase is edited. -tags: [overview, community, contributing] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T15:00:00Z } -sources: - - id: site-involved - resource: https://www.openimagingdata.org/how-to-get-involved/ - title: "How to Get Involved, openimagingdata.org, 2023-08-14" - - id: deck - resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal - title: Open Imaging Data Model 2026 Status Update, January 2026 - - id: forge - resource: https://github.com/openimagingdata/FindingModelForge/blob/15d9ebcf64734b889081feed4d645a0afca67e61/docs/DRAFT_WORKFLOW.md - title: Draft workflow, FindingModelForge dev branch - - id: catalog-site - resource: https://github.com/openimagingdata/finding-models-site/blob/6298fa3b900bb46b488f82d0a5b9aab06f70ca69/src/pages/index.astro - title: Finding model catalog site home page, with its Submit Issue route - - id: fm-content - resource: https://github.com/openimagingdata/findingmodels/blob/4475ac1bcb591f1a0951b2082b59208def173a5d/schema/finding_model_schema.md - title: Finding model schema, prose mirror, findingmodels repository ---- - -# Where the project gathers - -Everything OIDM produces is public. The four durable entry points are the GitHub organization, the project site, the Slack channel, and the deployed tools. - -| Channel | Where | What it is for | -|---|---|---| -| GitHub organization | | Code, content, specifications, issue trackers | -| Project site | | Articles explaining the model, project news, registration for updates | -| Slack | The Radiology Informatics workspace at , channel `#openimagingdata` | Discussion between meetings | -| Finding Model Forge | | Authoring finding models through a browser | -| IPL viewer demo | | Seeing an Imaging Problem List rendered from real sample data | - -The site's participation post also invites registration for newsletter updates and article comments, describes working groups on use cases and standards work, and asks for help identifying related projects and venues where the work would have impact.[^site-involved] That post dates from August 2023 and parts of it have aged: it advertises a working group on the TypeScript reference implementation, which is now a lineage repository rather than active work, and its meeting section is a placeholder. Treat the GitHub organization and the Slack channel as the current live channels. The site also carries a contact link for general inquiries. - -The largest stated organizational goal is not yet a channel you can join. The January 2026 deck proposes an "ACR-OIDM Structured Imaging Results Working Group" co-hosted with the ACR, bringing expert and vendor participants into one venue, with a use-case pipeline feeding the ACR and RSNA common data element group.[^deck] It is a proposal, not a convened body. - -# Repositories to start with - -Pick by what you want to do. Every repository, including the lineage ones, is listed with its status and branch of record in [the repository map](/repositories/repository-map.md). - -| If you want to | Start at | -|---|---| -| Read or use the finding model content | `openimagingdata/findingmodels`, 2,382 definitions with a prose schema mirror | -| Build against the finding model format | `openimagingdata/findingmodel`, the Python library, CLI tools, and MCP server | -| Understand the data structures | `openimagingdata/imaging-problem-list`, whose `main` branch holds the stable Exam Finding List and Imaging Problem List specification and sample data | -| See extraction from narrative reports | The same repository's `dev` branch, which holds the extraction, coding, persistence, and review platform | -| Look up codes across terminologies | `openimagingdata/med-ontology-lookup`, the `molu` library and CLI | -| Work on the authoring application | `openimagingdata/FindingModelForge` | - -Several of these repositories have their current state on a branch other than `main`. Check the repository map before assuming the default branch is the live one. - -# Filing issues - -Each repository carries its own issue tracker, and issues are where the work edge is visible. File against the repository that owns the thing you are reporting. - -- **A wrong, missing, or badly scoped finding model** goes to `openimagingdata/findingmodels`. The public catalog site routes its "Submit Issue" link there.[^catalog-site] -- **A bug or gap in the library, CLI, or anatomic location data** goes to `openimagingdata/findingmodel`. -- **A question about the data structures** goes to `openimagingdata/imaging-problem-list`. Its issue #1, asking for a formal model layer with JSON Schema export for Observation, Exam Finding List, and Imaging Problem List, is open and unclaimed. -- **Terminology lookup behavior** goes to `openimagingdata/med-ontology-lookup`. - -Existing open issues are the best statement of what the project knows is missing. They are summarized by area under [roadmap](/roadmap/). - -# Contributing content - -Content means [finding models](/glossary/finding-model.md) and the [anatomic locations](/glossary/anatomic-location.md) they reference. There are three routes, and they differ in how much of the work you do by hand. - -**Through Finding Model Forge.** The browser application is the supported route for authoring a single finding model without cloning anything. You sign in with GitHub, name the finding, generate and edit a description and synonyms, generate the attributes, and the draft autosaves. Submitting locks the draft for review, and a reviewer moves it through under review to added or declined.[^forge] Editing a model after creation is an open issue on that repository, not yet available. - -**Through the content repository.** Definitions live as one JSON file per finding, validated on commit, with identifiers issued from a registry that detects duplicates. Batch authoring and review run through agent skills held in that repository. Contributing this way means working with the schema directly; the prose mirror of the schema is the reference.[^fm-content] - -**Through staged common data elements.** Candidate definitions authored informally, ahead of formal review, are staged in `openimagingdata/CDEStaging` before entering the ACR and RSNA review pipeline that publishes through RadElement. That path is a manual curation workflow rather than an automated one. - -Identifier discipline matters in all three routes. An [OIFM identifier](/glossary/oifm.md) encodes its contributing organization in its letter block, and identifiers are never reissued or regenerated on a round trip. The authoring conventions are covered in [the authoring workflow](/semantic-foundation/finding-models/authoring-workflow.md). - -# Contributing to this knowledgebase - -This repository is the canonical high-level documentation, written as an Open Knowledge Format bundle so that both people and agents can read it. It describes and links to catalogs; it does not copy them. Code documentation, install steps, and developer workflow stay with the code. - -Before editing anything here, read [the authoring guide](/guides/authoring-guide.md). It sets the document types, the required frontmatter, the trust workflow by which a draft becomes verified, the linking conventions, and the procedure for migrating a document out of a working repository. Two checkers run on every push and must pass: an Open Knowledge Format conformance validator and a house-rules checker that enforces the type vocabulary, index coverage, link resolution, and a sweep for names and email addresses. - -Two house rules catch newcomers. Facts and stated goals only, with every goal citing the deck, issue, or plan that states it, because design proposals belong to a separate workstream. And organizations, not individuals: the project lead is named as such and nobody else is named. - -[^site-involved]: "How to Get Involved", openimagingdata.org, 2023-08-14 -[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 -[^forge]: Draft workflow, FindingModelForge dev branch -[^catalog-site]: Finding model catalog site home page -[^fm-content]: Finding model schema, prose mirror, findingmodels repository diff --git a/knowledge/overview/vision.md b/knowledge/overview/vision.md deleted file mode 100644 index 7f1f41c..0000000 --- a/knowledge/overview/vision.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -type: Concept -title: Vision -description: Why OIDM pursues object-oriented imaging results, what that unlocks across the imaging life cycle, and what the project has proposed next. -tags: [overview, vision, strategy] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T15:00:00Z } -sources: - - id: deck - resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal - title: Open Imaging Data Model 2026 Status Update, January 2026 - - id: site-structure - resource: https://www.openimagingdata.org/data-model-structure-and-function/ - title: "Data Model: Structure and Function, 2024-01-25" - - id: site-findings - resource: https://www.openimagingdata.org/findings-cdes-and-observations/ - title: "Findings, CDEs, and Observations, 2023-06-24" - - id: site-benchmark - resource: https://www.openimagingdata.org/benchmarking-a-vision/ - title: "Benchmarking a Vision, 2024-07-01" - - id: site-plugin - resource: https://www.openimagingdata.org/oidm-based-next-gen-reporting-assistance/ - title: "OIDM-Based Next-gen Reporting Assistance Framework, 2023-07-16" - - id: fmf - resource: https://fmf.oidm.org - title: Finding Model Forge, the finding model authoring application ---- - -# Object-oriented imaging results - -The January 2026 status deck frames the project's goal as "standardizing the DNA of imaging IT" and titles itself "Realizing Object-Oriented Imaging Results."[^deck] The metaphor is deliberate. An imaging result today is a document. The proposition is that it should instead be a set of objects: each finding an addressable thing with a type, a location, attributes, and an identity that persists across exams. - -The earlier site posts set the technical form of that proposition. A finding is "a standard format based on FHIR Observations semantically labeled with ACR/RSNA Common Data Element identifiers."[^site-findings] The wider model is described as "a superstructure on top of these FHIR definitions to both organize them and enable more straightforward programmatic access to the data," with an explicit analogy: "the relationship between OIDM and FHIR might be that between the browser's DOM and HTML."[^site-structure] FHIR supplies the resources; OIDM supplies the object model over them that software can actually program against. - -# What object-oriented results unlock - -The deck names three consequences.[^deck] - -**A unified reading toolset.** Picture archiving and communication systems, reporting software, and artificial intelligence tools currently each hold their own partial, private representation of what an exam showed. A shared object model lets one representation serve all three, so a finding created in one tool is legible in the next. - -**Integrated data sources.** Radiologists, the electronic medical record, and artificial intelligence tools all produce statements about a patient's imaging. When each produces the same kind of object, they can be merged, compared, and attributed rather than kept in separate lanes. The deck lists correlating artificial intelligence observations against radiologist observations as an explicit validation use case. - -**Downstream longitudinal utility.** Once findings are objects with identity, a patient's imaging history becomes queryable. The core query the deck names for the [Imaging Problem List](/glossary/imaging-problem-list.md) is whether a given finding was present on the most recent study, with dynamic tracking of appearance, disappearance, and change. - -# The workbench relationship between finding models and CDEs - -The deck's most specific strategic claim concerns the relationship between [finding models](/glossary/finding-model.md) and formal [common data elements](/glossary/cde.md). It calls the Open Imaging Finding Model corpus "the CDE workbench."[^deck] - -The reasoning is about velocity and governance. Common data elements published through RadElement are formally reviewed, versioned, and governed by the ACR and RSNA, which is what makes them worth adopting and also what makes them slow to create. Finding models are authored quickly, with language model assistance, through [Finding Model Forge](https://fmf.oidm.org) and agent-driven batch workflows. The deck positions that speed as a feature: finding models are a "rapid innovation / proving ground before ACR/RSNA CDE adoption," carrying "exploratory metadata" in the form of "rich definitions w/ embedded relationships and semantic tags that can graduate to standards."[^deck] - -The finding model corpus therefore serves two purposes at once. It is programmatic knowledge, described in the deck as "ground-level info for tool access + LLM context engineering," usable today by extraction and reporting tools. And it is a staging ground whose proven definitions become candidates for formal common data elements. The relationship is treated in depth in [Finding models and CDEs](/semantic-foundation/finding-models/finding-models-and-cdes.md). - -# The Imaging Problem List across the imaging life cycle - -The deck argues that structured results pay off at every stage of an imaging encounter, not only at reporting.[^deck] - -| Stage | Stated application | -|---|---| -| Planning | MRI safety screening, mobility assessment, prior authorization | -| Intra-exam | Rules-based protocoling | -| Interpretation | Findings-oriented views in the picture archiving system, real-time quality control | -| Post-interpretation | Passive screening, research cohorts, outcomes tracking | - -The same point appears in the 2023 framework post, which proposes a plugin-container architecture in which a reporting tool hosts small programs, supplies them with "all the relevant contextual information, about the findings in the current report, about prior reports, about the exams that were being reported on," re-runs them whenever the report context changes, and lets them issue commands back to insert text, request input, raise alerts, or send data onward.[^site-plugin] That architecture only works if the context handed to the plugins is structured, which is what the data structures layer provides. - -# Why not just let the language model read the report - -Large language models can read narrative reports well, which raises the obvious objection that structure is unnecessary. The deck answers it with three arguments.[^deck] - -**Context engineering.** A model answers better when given the relevant findings directly than when given a stack of prior reports to re-read. Structured results are how the right context gets selected and delivered. - -**Deterministic logic.** Some decisions must not be probabilistic. MRI safety checks and protocol selection are rule evaluations over facts. They need facts with known types and known codes, not an inference drawn fresh from prose on each run. - -**Citable common context.** When several systems act on the same patient, they need to be acting on the same stated facts, with a shared reference that can be pointed at and audited. A model's private reading of a report is neither shared nor citable. - -The argument is not that language models are unsuited to the work. They do the extraction: the deck's own premise is that the Imaging Problem List is "automatically extractable from narrative report text via LLMs."[^deck] The argument is about where the model sits. It converts prose into objects once, and everything downstream operates on the objects. - -# The proposed working group - -The deck's call to action is a proposal, not an existing body. It proposes that the ACR co-host an "ACR-OIDM Structured Imaging Results Working Group," bringing together expert and vendor participants.[^deck] Three principles are stated for it: structure first, with mapping to FHIR and other standards following rather than leading; vendor-driven innovation, with reporting software vendors building on the result; and an academic-vendor "big tent" that the project would host and moderate. - -The deck lists four ACR priorities the working group would serve: recommendation tracking, validation of artificial intelligence tools by correlating their observations against radiologist observations, quality metrics, and support for the *-RADS reporting systems. The stated next steps are to convene that group, run a use-case pipeline feeding the CDE group, and standardize the result. - -As of this writing the working group is a proposal recorded in the deck. Nothing in the source repositories records it as convened. Stated goals for each area are collected under [roadmap](/roadmap/). - -# Evidence offered so far - -The vision has been argued in a peer-reviewed venue. A 2024 site post announces a manuscript in the Journal of the American Medical Informatics Association titled "Standardizing imaging findings representation: harnessing Common Data Elements semantics and Fast Healthcare Interoperability Resources structures," whose position the post summarizes as "CDE-labeled Observations should be the universal representation for exchanging and consuming content in radiology reports," worked through a pulmonary nodule case study together with guidance on adoption challenges.[^site-benchmark] - -[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 -[^site-structure]: "Data Model: Structure and Function", 2024-01-25 -[^site-findings]: "Findings, CDEs, and Observations", 2023-06-24 -[^site-benchmark]: "Benchmarking a Vision", 2024-07-01 -[^site-plugin]: "OIDM-Based Next-gen Reporting Assistance Framework", 2023-07-16 diff --git a/knowledge/overview/what-is-oidm.md b/knowledge/overview/what-is-oidm.md deleted file mode 100644 index 2623b21..0000000 --- a/knowledge/overview/what-is-oidm.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -type: Overview -title: What is OIDM -description: The mission of the Open Imaging Data Model, the problem it addresses, its three layers, who it serves, and where its work lives. -tags: [overview, orientation] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T15:00:00Z } -sources: - - id: deck - resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal - title: Open Imaging Data Model 2026 Status Update, January 2026 - - id: site-about - resource: https://www.openimagingdata.org/ - title: openimagingdata.org, project site and tagline - - id: site-findings - resource: https://www.openimagingdata.org/findings-cdes-and-observations/ - title: "Findings, CDEs, and Observations, 2023-06-24" - - id: ipl-readme - resource: https://github.com/openimagingdata/imaging-problem-list/blob/36fa30c7383bf687d7bc17815282a93e123a56cb/README.md - title: imaging-problem-list README on the dev branch - - id: fm-schema - resource: https://github.com/openimagingdata/findingmodels/blob/4475ac1bcb591f1a0951b2082b59208def173a5d/schema/finding_model_schema.md - title: Finding model schema, prose mirror, findingmodels repository ---- - -# What OIDM is - -The [Open Imaging Data Model (OIDM)](/glossary/oidm.md) is an open project that defines how the results of an imaging exam are represented as data rather than as prose. Its stated purpose, in the words of the project site, is "defining unified data structures to integrate new functionality into imaging informatics platforms."[^site-about] The January 2026 status deck puts the same ambition more bluntly: standardizing "the DNA of imaging IT."[^deck] - -Concretely, OIDM supplies three things. It supplies a controlled vocabulary of imaging findings and the attributes that characterize them, each carrying a stable identifier. It supplies a small hierarchy of data structures that carry those findings from a single [observation](/glossary/observation.md) up to a longitudinal view of one patient. And it supplies working tools that produce and consume those structures, so that the vocabulary and the structures are exercised against real reports rather than only specified. - -# The problem: results locked in narrative - -An imaging report is the product of the exam, and it is almost always a block of narrative text. That text is readable by a clinician and opaque to everything else. A downstream system that wants to know whether a pulmonary nodule was present on the most recent chest CT, how large it was, and whether it grew, has to re-read the prose every time, and each system re-reads it differently. - -The consequences compound across the imaging life cycle. Prior findings cannot be retrieved reliably at interpretation time, so radiologists reconstruct history by reading old reports. Recommendations made in a report cannot be tracked to completion. Quality metrics and registry submissions require manual abstraction. Findings produced by artificial intelligence tools and findings produced by radiologists cannot be compared, because they are not expressed in the same terms. Each vendor that solves part of this solves it privately, in its own schema, which means the solution does not travel. - -OIDM's response is to make the finding, not the report, the unit of data. A finding gets a coded identity, a coded location, coded attributes, and a place in a structure that other systems can query. The 2023 site post that set the direction states it as: "Radiology findings can be represented in a standard format based on FHIR Observations semantically labeled with ACR/RSNA Common Data Element identifiers."[^site-findings] - -# Three layers - -The project organizes its work into three layers, and this knowledgebase follows that organization. - -**Semantic foundation.** The vocabulary layer. It holds [finding models](/glossary/finding-model.md), each a named finding with a description, synonyms, and a list of attributes, identified by an [Open Imaging Finding Model (OIFM)](/glossary/oifm.md) identifier.[^fm-schema] It holds [anatomic locations](/glossary/anatomic-location.md) keyed by RadLex identifiers, [common data elements](/glossary/cde.md) published through RadElement, [exam types](/glossary/exam-type.md), and the external terminologies all of these draw on. See [semantic-foundation](/semantic-foundation/). - -**Data structures.** The container layer. An [Observation](/glossary/observation.md) is one finding in one exam, with its location and attribute values. An [Exam Finding List](/glossary/exam-finding-list.md) is every observation from one exam in one queryable structure.[^ipl-readme] An [Imaging Problem List](/glossary/imaging-problem-list.md) reorganizes observations across a patient's exams by finding rather than by date. An [Imaging Persona](/glossary/imaging-persona.md) is the stated goal of surrounding that with clinical context. See [data-structures](/data-structures/). - -**Applications.** The tools that make the first two layers real: an authoring application for finding models, a catalog site, a viewer for Imaging Problem Lists, a report extraction and coding platform, and a terminology lookup library. See [applications](/applications/). - -Each layer depends on the one below it, and the identifiers are what join them. How the layers and identifier systems connect is laid out in [Architecture](/overview/architecture.md), and the reasoning behind the whole design in [Vision](/overview/vision.md). - -# Who this is for - -- **Developers** building imaging informatics software who need formats to read and write, and libraries that already speak them. -- **Informaticists** who need to know what a finding model is, how it relates to a common data element, and what identifier to use for an anatomic structure. -- **Standards bodies and committees** evaluating the vocabulary and data structures, and deciding what should graduate into formal standards. -- **Agents** doing extraction, coding, or authoring work, which is why this knowledgebase is an Open Knowledge Format bundle with machine-readable frontmatter on every document. - -# Where things live - -The work is spread across repositories in the `openimagingdata` GitHub organization plus several allied external projects. Content, code, specifications, and in-flight work do not all live in the same place, and several repositories have their current state on a branch other than `main`. [The repository map](/repositories/repository-map.md) lists every source repository, its role, its status, and its branch of record. - -This knowledgebase is the high-level documentation. It describes and links to catalogs; it does not copy them. Installation instructions, API usage, and developer workflow stay with the code. - -# Getting involved - -Participation channels, the repositories to start with, how to file issues, and how content is contributed are covered in [Getting involved](/overview/getting-involved.md). Conventions for editing this knowledgebase itself are in [the authoring guide](/guides/authoring-guide.md). - -[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 -[^site-about]: openimagingdata.org, project site and tagline -[^site-findings]: "Findings, CDEs, and Observations", 2023-06-24 -[^ipl-readme]: imaging-problem-list README on the dev branch -[^fm-schema]: Finding model schema, prose mirror, findingmodels repository diff --git a/knowledge/plans/2026-09-20-knowledgebase-build-plan.md b/knowledge/plans/2026-09-20-knowledgebase-build-plan.md deleted file mode 100644 index a45c065..0000000 --- a/knowledge/plans/2026-09-20-knowledgebase-build-plan.md +++ /dev/null @@ -1,297 +0,0 @@ ---- -type: Plan -title: Knowledgebase build plan -description: How the oidm-knowledge repository is scaffolded, populated from the source repositories, cross-linked, validated, and published. -tags: [plan, meta] -status: draft -generated: { by: claude-fable-5-1/claude-code, at: 2026-09-20T22:30:00Z } -sources: - - id: charter - resource: scope:grilling-session-2026-09-20 - title: Decisions from the 2026-09-20 planning interview with the project lead - - id: inventories - resource: scope:seven-inventory-reports-2026-09-20 - title: Read-only inventories of all source repositories, the Gamma status deck, and the openimagingdata.org site ---- - -# Purpose - -Build `oidm-knowledge` as the canonical, public, high-level documentation for the Open Imaging Data Model (OIDM). Non-code documentation migrates here from the working repositories; those repositories will later reference this one. The bundle follows the Open Knowledge Format (OKF) 0.2 so that people and agents can both read it. - -This plan is itself part of the bundle. It is updated as phases complete and marked complete at the end. - -# Decisions already made - -These were settled with the project lead on 2026-09-20 and are not re-opened here. - -| Area | Decision | -|---|---| -| Role of this repo | Canonical high-level documentation. Migrate, don't mirror. Working repos keep code documentation only. | -| Format | OKF 0.2 bundle in `knowledge/`. Root `index.md` carries `okf_version: "0.2"`. | -| Tooling | scaccogatto/okf-skills (installed under `.agents/skills/`) for authoring and validation, plus a port of the stricter checker from the ACR-RSNA-CDEs `next-gen-2026` branch as a second CI gate. | -| Doc types | Inherit the `next-gen-2026` vocabulary and extend it (see Conventions). | -| Trust signals | Every agent-written doc carries `generated`. `status: draft` until the project lead verifies it as `human:talkasab`, then `status: stable`. Migrated human-written docs carry `sources` pointing at their origin. | -| Site | Rendered site from day one. Not MkDocs Material. Recommendation below: Quartz 5, runner-up Astro Starlight. | -| Repo | `openimagingdata/oidm-knowledge`, public, CC-BY-4.0. Plan, `CHANGELOG.md`, `DEV_LOG.md`, `CONTRIBUTING.md`, agent authoring guide. | -| Organization | Three layers: semantic foundation, data structures, applications. Per-repo index as secondary lookup. Balanced audience: each area gets a narrative "why" and a technical "how". | -| Content stance | Facts and stated goals only. No design proposals yet. Catalogs described and linked, never copied. Organizations named, individuals not. | -| Sources | All openimagingdata repos including lineage; the openimagingdata.org site; the January 2026 status deck; the anatomic-location repos and the newer data inside `findingmodel`; RSNA/ACR-RSNA-CDEs and RSNA/RadLex as allied external projects with OIDM-relevant parts of `next-gen-2026` migrated; IPL-MVP-ExtractionAndLabeling. Excluded: ladybug, composer-prototype, MGB clinical-guidance repos. Manuscripts to follow from the project lead. | -| Process | Source repos cloned read-only into the home directory. Sonnet for inventory and extraction, Opus for synthesis, the main session for review and cross-linking. The project lead approves this plan before any synthesis or bundle writing. | - -# The project lead's stated goals - -Recorded verbatim from the 2026-09-20 request that started this work, so that documents citing `scope:grilling-session-2026-09-20` have a written source. Wording lightly normalized for spelling only. - -- **Open Imaging Finding Models.** "Definition formats, needs overhaul for increased metadata and interesting relationship/graph work coming from CDEs." "Tooling for working with these, using them, probably needs corresponding overhaul." "Actual content efforts." -- **ACR/RSNA Common Data Elements.** "Should dovetail with the OIFM work." -- **FindingModelForge.** "An authoring tool for creating OIFMs / CDEs." -- **Imaging Problem List.** "A format for representing a patient's imaging history." "Define appropriate data structures for representing both observations in radiology reports and their relationships." "Manage them (and appropriately collate them) across the patient's imaging history." "Design interesting applications that use this tool and demonstrate how this works." "Create standard data structure formats, etc. for working with these." -- **Anatomic Locations.** "There are repos, and this will be blended in with RadLex." "Also needs tooling for people working with them." -- **Exam types.** "Needs wrapper tooling around the knowledge graph represented by RadLex LOINC playbook." "Need to define the PREFERRED high-level entries like 'CT Chest', 'MRI Brain', 'X-ray Knee'." "Need to TIGHTLY wrap up with Anatomic Locations, including edges for both 'always included' (which can include hierarchies) as well as 'usually included' (for edge things) and something like 'POSSIBLY included, would have to check'." -- **Content direction (2026-09-21).** "In findingmodels, there's a recent taxonomy branch with new files in lists that's the immediate direction for all of the content, probably replacing a lot of the Gamuts-based models." The taxonomies are to be called the "MGB exam-oriented sub-taxonomies." - -# Source map - -Inventory reports for every source live in the session scratchpad and are summarized here. "Read from" names the branch that represents current state. - -| Source | Role | Read from | Key migration candidates | -|---|---|---|---| -| findingmodel | OIFM library, `anatomic-locations` package, CLIs, MCP server | `main` (2026-03) for released state; `dev` and `feature/metadata-cleanup` (2026-06-29) for the work edge | `docs/anatomic-locations.md`, `docs/manifest_schema.md`, `.claude/skills/manage-anatomic-locations/reference/*`, `tasks/done/anatomic-locations-normalized-schema.md`, enrichment PRD and plans, schema-versioning notes, `notebooks/data/anatomic_locations_noembed.json` (2,926 records, current anatomic data); on the work edge: `docs/canonical-structured-metadata-and-enrichment-rewrite.md`, ADRs 0001 to 0003, `tasks/canonical-structured-metadata-implementation-plan.md`, `docs/plans/metadata-enrichment-current-plan.md` | -| findingmodels | Content: 2,382 finding model definitions (1,933 Gamuts-derived, 256 OIDM, 115 CDE, 47 MGB, 31 MSFT), `ids.json`, MGB exam-oriented sub-taxonomies | `taxonomy-export-2026-08-15` (one commit ahead of `main`) for content direction; `main` for the corpus; content branches for in-flight batches | `schema/finding_model_schema.md`, `lists/README.md` and the six taxonomy CSVs (3,789 rows, 1,028 matched), `docs/plans/definition_cleanup.md`, `docs/plans/rebuild-finding-skills.md`, `docs/plans/findingmodel-upstream-proposals.md`; on branches: `content/chestcts` PLAN and batch progress, `findingmodels-metadata` plan history and schema changes, `content/headcts` | -| med-ontology-lookup | `molu` terminology lookup across RadLex, SNOMED CT, FMA, LOINC, UMLS | `main` (2026-09-20); no unmerged branches; offline-CI work uncommitted in a worktree | `docs/product-roadmap.md` (contains the only written exam-type design), `docs/project-review-and-proposal.md` (six-phase design), `docs/plans/2026-09-05-github-issue-backlog.md` | -| FindingModelForge | Authoring web app at fmf.oidm.org | `dev` (2026-01) is ahead of stale `main`; `feature/model-editing` (2025-11) | `docs/finding-model-creation-workflow.md`, `docs/DRAFT_WORKFLOW.md`, `docs/feature_finding_model_draft_saving.md`, `zOld/plans/*` (history), issue #13 model editing | -| CDEStaging | 259 informally authored CDE definitions upstream of RadElement | `main` (2025-11); all recent work lands on `main`; 11 topic branches from 2024 never merged | `docs/report_extraction_process.md`, `report_representation/{negative_statements,uncovered_findings,missing_attributes}.md`, `definitions/upmedic/roadmap.md`; the unmerged `report_representation` and `gamuts_work` branches noted in history | -| finding-models-site | Static catalog reader for findingmodels | `main` | Nothing beyond a profile entry | -| imaging-problem-list | Exam Finding List and Imaging Problem List spec, viewer, sample data; on `dev`, a full extraction, coding, persistence, and review platform | `dev` (2026-07-21, 258 commits ahead of `main`) for current state; `main` for the stable spec; the one feature branch is empty | `README.md`, `CLAUDE.md` (domain model), `docs/anatomic-location-assignment-rules.md`, `docs/technical-imaging-findings.md`, `docs/plans/anatomic-location-efl-ipl.md`, `docs/archive/finding-and-location-code-assignment-plan.md`, `initial-extraction-plan.md`, `docs/plans/viewer-v2-anatomy-dashboard.md`, `docs/plans/extractor-evals-redesign.md`, the extraction-reviewer plans, `prompts/validator_prompt_example.md`, issue #1 "Create System of Data Models", both PNG diagrams, sample data | -| IPL-MVP-ExtractionAndLabeling | Early 4-stage extraction and mapping pipeline | `main` (2025-10); three single-commit experiment branches | `README.md`; `FHIR_Example_Structure` on `Persistent_FHIR_Resources` (a design note without a `.md` extension) | -| ACR-RSNA-CDEs | Allied external project. `next-gen-2026` holds the project lead's next-generation vocabulary work | `next-gen-2026` (2026-09-15) | `CONTEXT.md` glossary terms; `notes/oifm-overview.md`, `notes/oifm-metadata-fields.md`, `notes/oifm-schema-v2-draft.md`, `notes/ihe-idr-extract.md`, `notes/hood-taxonomies-profile-2026-09-01.md`; the ideas in `docs/next-gen-schema/00, 01, 03, 07, 11` summarized as goals; `docs/check_bundle.py` conventions. Committee minutes and email extracts stay in the CDE repo. | -| RadLex (RSNA) | Allied external project: ontology (`RadLex.owl`, 46,899 RIDs) and a search pipeline repo | `main`; GitHub issue #3 "Add Anatomic Locations" | `docs/plans/hybrid-fts-search.md` (referenced, not migrated) | -| anatomiclocations.org, BodyPartIndex.py, BodyPartIndex.ts | Original curated anatomic location set (2,890 nodes) and wrapper libraries | `main` of each (2022 to 2024) | `docs/index.markdown` (rationale, exam-type roadmap line), `docs/code.markdown`, `data/body_parts_schema.json`, `BodyPartIndex.py/TODO.md` | -| OpenImagingDataModel.py and .ts | Lineage reference implementations | `main` | `observation.py` and `finding_model.py` shapes as FHIR precedent; Zod schemas as early types | -| FHIRSamples | Lineage: lung screening DiagnosticReport plus chained CDE-coded Observations | `main` | All JSON examples as FHIR mapping precedent | -| UseCases | Lineage: ~25 use case ideas and the six RSNA value categories | `main` | `Index.md`, `README.md` | -| openimagingdata.org (repo) | Lineage: old site scaffold and schema fixtures | `main` | `schemas/schema_differences.md`, sample JSON | -| openimagingdata.org (Ghost site) | 15 posts (2023-06 to 2025-03) and an About page | live | "Findings, CDEs, and Observations", "Data Model: Structure and Function", "Benchmarking a Vision", "OIDM-Based Next-gen Reporting Assistance Framework", the SIIM 2024 posts | -| CDETemplateDemo, SARTemplatesToCDEs, template_extraction, ReportFindingRefiner, get_ontology_findings, ontology_tools, common_data_elements | Lineage | `main` | `SARTemplatesToCDEs/RectalCAStaging/Changes_vs_RDES11.md`, `ReportFindingRefiner/README.md`, `common_data_elements/README.md` and schema versions; the rest as one-paragraph history entries | -| Gamma deck, January 2026 | Vision and roadmap | live | Whole deck as a Presentation Extract | - -# The work edge - -The project lead asked that the knowledgebase reflect the entire work edge, not only default branches. Branch-level inventories on 2026-09-21 established the following. Documents that describe current state cite the branch and pin the commit they read. - -- **findingmodels.** The MGB exam-oriented sub-taxonomies on `taxonomy-export-2026-08-15` are the immediate direction for all content and are expected to replace many of the Gamuts-derived models, which make up 1,933 of the 2,382 current definitions. Rows without a matched OIFM ID are the next triage task. Three content pipelines (`content/chestcts` as the live CT-chest front, `content/headcts`, `km`) and a metadata-enrichment branch touching 78 models are in flight, none merged. The finding model schema is being extended on three uncoordinated branches with nothing landed on `main`. -- **findingmodel.** The live thread is the canonical structured-metadata rewrite on `feature/metadata-cleanup` and `dev`: eight new optional fields (body regions, subspecialties, etiologies, entity type, applicable modalities, expected time course, age profile, sex specificity), a multi-agent enrichment architecture, and a dual-database release plan. The older eight-facet specification is superseded. The branch's own readiness assessment is not yet passing. Twenty-four open issues map one-to-one onto task plans. -- **imaging-problem-list.** `dev` is current. Eight active plans center on human-review tooling, an honesty-first evaluation redesign, an anatomy-aware viewer, and local-model strategy. Anatomic-location coding of findings has landed. Issue #1, a formal Pydantic model layer for Observation, Exam Finding List, and Imaging Problem List with JSON Schema export, is broader than any current plan and unclaimed. -- **med-ontology-lookup.** No unmerged branches. A twelve-issue backlog follows the six-phase design document. Offline CI is implemented but uncommitted. -- **CDEStaging, IPL-MVP-ExtractionAndLabeling, FindingModelForge.** Work lands on `main` in CDEStaging with stale 2024 topic branches never merged. IPL-MVP has three single-commit experiments, including a longitudinal FHIR-persistence design note. Forge's `dev` and `feature/model-editing` branches are ahead of `main`. - -Facts worth stating up front because they shape the writing: - -- The taxonomies-over-Gamuts direction and the metadata rewrite are stated as current direction in the finding model documents and the roadmap, sourced to their branches. -- Two anatomic location lineages coexist: the 2022 `anatomiclocations.org` set with wrapper libraries, and the newer `anatomic-locations` package inside `findingmodel` with laterality and dual hierarchies. The knowledgebase documents both and states which is current. -- The FHIR mapping for Exam Finding Lists and Imaging Problem Lists is documented but implemented nowhere. The knowledgebase says so. -- No exam-type artifact exists. The area is documented as goals plus existing building blocks. -- The "Open Imaging Reporting SDK" named in the deck has no artifact. It appears only in the roadmap. -- The Hood finding taxonomies are named after their author in the source repo. The knowledgebase refers to them as "the MGB exam-oriented sub-taxonomies" and credits the contributing institution, per the organizations-only rule. The project lead may override. - -# Target bundle structure - -One concept per file. Directory names are stable identifiers; file names are kebab-case. Every directory has an `index.md`. Roughly 90 documents. - -``` -knowledge/ - index.md root listing, okf_version 0.2 - log.md - overview/ - what-is-oidm.md Overview: mission, the three layers, who it is for - vision.md Concept: object-oriented imaging results, from the deck and site - architecture.md Concept: how the three layers and the repos fit together - getting-involved.md Guide - glossary/ one term per file, alphabetical index - oidm.md oifm.md finding-model.md attribute.md index-code.md cde.md cde-set.md - data-element.md finding-class.md measurement.md anatomic-location.md laterality.md - contained-by-and-part-of.md exam-type.md observation.md presence.md - exam-finding-list.md imaging-problem-list.md imaging-persona.md technical-finding.md - finding-taxonomy.md radelement.md radlex.md loinc-playbook.md ... - semantic-foundation/ - index.md - finding-models/ - why-finding-models.md Concept (narrative) - finding-model-format.md Format Specification (fields, attribute types, index codes, contributors) - identifiers.md Reference (OIFM and OIFMA ID scheme, ids.json) - content-catalog.md Project Profile (findingmodels repo, how to browse) - finding-taxonomies.md Reference (MGB exam-oriented sub-taxonomies, 3,789 rows, matching status) - authoring-workflow.md Guide (Forge, findingmodel-ai, agent skills, review) - enrichment-pipeline.md Concept (AI enrichment design as documented) - finding-models-and-cdes.md Concept (the workbench relationship) - common-data-elements/ - cdes-and-radelement.md Concept (CDE sets, elements, schema 1.0 and 1.1, schema-vs-API gaps) - cde-staging.md Project Profile (two authoring conventions, pipeline to RadElement) - next-generation-vocabulary.md Concept (FindingClass, DataElement, Measurement, relationships, anatomy axis; status draft; sourced to next-gen-2026) - anatomic-locations/ - why-anatomic-locations.md Concept - data-model.md Format Specification (fields, hierarchies, laterality triads, code mappings, counts) - laterality-conventions.md Reference (migrated) - lineage-and-current-implementation.md Concept (two lineages, which is current) - radlex-integration.md Concept (RadLex structure, issue #3, overlay agreement) - tooling.md Guide (anatomic-locations CLI and search, wrapper libraries) - exam-types/ - overview.md Concept (goals: preferred entries, LOINC/Playbook wrapper, edges to anatomy) - existing-building-blocks.md Reference (med-ontology-lookup roadmap, LOINC in the IPL, template corpus) - terminologies/ - ontologies-used.md Reference (RadLex, SNOMED CT, FMA, LOINC, UMLS, RadElement: roles and ID forms) - med-ontology-lookup.md Project Profile - data-structures/ - index.md - hierarchy.md Concept (Observation, Exam Finding List, Imaging Problem List, Imaging Persona) - observation.md Data Structure - exam-finding-list.md Data Structure (fields, example, provenance) - imaging-problem-list.md Data Structure (grouping key, status model, example) - imaging-persona.md Concept (goals only) - anatomic-location-assignment-rules.md Reference (migrated from imaging-problem-list dev) - technical-imaging-findings.md Reference (migrated) - fhir-mapping.md Concept (documented mapping, lineage precedents, implementation status) - ihe-idr-alignment.md Concept - sample-data.md Reference (example patients, counts) - applications/ - index.md - finding-model-forge.md Project Profile - finding-models-site.md Project Profile - imaging-problem-list-viewer.md Project Profile (viewer and viewer_v2) - report-extraction-platform.md Project Profile (imaging-problem-list dev: extraction, coding, persistence, review) - finding-and-location-coding.md Concept (the coding pipeline as documented) - ipl-mvp-extraction.md Project Profile - reporting-sdk.md Concept (stated direction, no artifact) - roadmap/ - index.md - roadmap-2026.md Roadmap (from the deck: working group, pillars, next steps) - finding-model-format-evolution.md Roadmap (canonical metadata rewrite, relationships and graph ideas from the CDE work, schema versioning: stated goals only) - finding-model-content-direction.md Roadmap (taxonomies as the content spine, Gamuts supersession, in-flight batches, triage) - ipl-data-model-system.md Roadmap (issue #1 and the dev-branch direction) - exam-types.md Roadmap - anatomic-locations-and-radlex.md Roadmap - open-questions.md Reference (per area) - history/ - index.md - timeline.md History - lineage-repositories.md History (one section per lineage repo, status, what survives) - extraction-approaches.md History (template_extraction, SAR templates, ReportFindingRefiner, get_ontology_findings) - cde-template-rendering.md History (CDETemplateDemo pattern) - use-cases.md Reference (the use case catalog and value categories) - site-articles.md Reference (the 15 posts, dated, summarized, linked) - repositories/ - index.md - repository-map.md Reference (every repo: org, role, status, branch of record, links into this bundle) - references/ verbatim or near-verbatim material, OKF convention - status-update-2026-01.md Presentation Extract (the deck) - finding-model-schema.md Reference (prose schema mirror, verbatim) - anatomic-location-json-schema.md Reference (verbatim) - exam-finding-list-example.md Worked Example (real sample JSON) - imaging-problem-list-example.md Worked Example - oifm-overview-extract.md Source Extract (from next-gen-2026 notes) - guides/ - authoring-guide.md Guide (for humans and agents: types, frontmatter, trust workflow, links, migration procedure) - migration-ledger.md Reference (every migrated doc: source repo, path, commit, destination, date) - plans/ - index.md - 2026-09-20-knowledgebase-build-plan.md this document -``` - -Repository root, outside the bundle: `README.md`, `CONTRIBUTING.md`, `CHANGELOG.md`, `DEV_LOG.md`, `LICENSE`, `tools/check_bundle.py`, `site/` (generator config), `.github/workflows/`. - -# Conventions - -**Frontmatter.** `type`, `title`, `description`, `tags`, `status`, `generated` on every concept document. `sources` whenever content derives from a repo doc, a site post, or the deck, with `resource` as a permalink (commit-pinned GitHub URL where possible). `verified` only when the project lead signs off. `stale_after` on project profiles and roadmap docs, set one year out. - -**Document types.** Inherited from `next-gen-2026` and kept: Reference, Analysis, Decision Record, Proposal, Playbook, Worked Example, Presentation Extract, Meeting Notes, Draft Specification, Gap Log, Exploration. Added: Overview, Concept, Glossary Term, Format Specification, Data Structure, Project Profile, Guide, Roadmap, History, Source Extract, Plan. Proposal and Decision Record are reserved for the next workstream. The checker enforces this list. - -**Links.** Bundle-relative absolute links (`/glossary/oifm.md`). Every concept links to its glossary terms on first use and to the repository map. External links go to commit-pinned GitHub URLs for files and to the live sites for deployed tools. - -**Naming.** "Open Imaging Data Model (OIDM)" for the project, "Open Imaging Finding Model (OIFM)" for a finding model definition, "finding model" thereafter. Repos are named by their GitHub name in code font. The old name "Open Radiology Data Model" appears only in history. - -**People.** Organizations only. The project lead is named as such. - -**Migration.** A migrated document keeps its substance, gets frontmatter and links, is rewritten only for consistency of terms, and is recorded in the migration ledger with source repo, path, and commit. The follow-up round of source-repo pull requests works from the ledger. - -# Tooling and site - -**Validation, two gates.** `uv run .agents/skills/validate/scripts/okf_validate.py knowledge --strict` for OKF conformance. `tools/check_bundle.py`, ported from `next-gen-2026`, for the stricter house rules: type vocabulary, index coverage in every directory, link resolution, balanced fences, no empty headings, denylist sweep for emails and individual names. The diagram-freshness rules are dropped until diagrams exist. - -**Site.** Quartz 5 is recommended: folder-driven navigation, tag pages, search, arbitrary frontmatter, Mermaid, and a link graph with backlinks, all built in. Its release hygiene is weak (no tag since 2023), so CI pins a commit. Astro Starlight is the runner-up if a more conventional toolchain is preferred. Phase 0 includes a short spike to confirm the Quartz config keys the researcher could not verify against live docs: content path, absolute markdown link resolution, and a frontmatter badge component. Deploy with GitHub Actions to GitHub Pages. Run a link checker over the built output. - -# Phases - -Each phase ends with validation passing, `log.md` and `DEV_LOG.md` updated, and this plan's phase table updated. - -## Phase 0: Scaffold - -Done so far: `git init`, LICENSE, `.gitignore`, OKF skills installed, source repos cloned, inventories complete. - -Remaining: - -1. Initialize the bundle with `okf_init.py`, then replace the placeholder with the directory skeleton and index files above. -2. Write `guides/authoring-guide.md`, root `CONTRIBUTING.md`, and root `README.md`. -3. Port `check_bundle.py` into `tools/` with the type vocabulary and denylist; wire both validators into a GitHub Actions workflow. -4. Spike Quartz in `site/`, confirm the three unverified config points, render the skeleton, and set up Pages deploy. -5. Start `CHANGELOG.md` and `DEV_LOG.md`. - -## Phase 1: Spine - -Overview, glossary, repository map, history, references. These are written first because every later document links into them. - -- Opus synthesis agents, one per directory, each given the relevant inventory reports, the source file paths, the authoring guide, and the deck extract. -- The glossary harvests every definition-like statement the inventories quoted and flags conflicts for the project lead in `roadmap/open-questions.md`. -- The Ghost posts and the deck become extracts in `references/` and summaries in `history/`. - -## Phase 2: Semantic foundation - -Finding models, common data elements, anatomic locations, exam types, terminologies. Four Opus agents, one per subdirectory. The next-generation vocabulary document is written as facts about what the `next-gen-2026` branch defines, with `status: draft` and sources pointing at the branch. - -## Phase 3: Data structures - -Observation, Exam Finding List, Imaging Problem List, Imaging Persona, assignment rules, FHIR mapping, IHE alignment, sample data. One Opus agent with the deep imaging-problem-list inventory, both branches' domain docs, the FHIR lineage material, and the deck. - -## Phase 4: Applications - -Forge, site, viewer, extraction platform, coding pipeline, MVP extraction, reporting SDK. One Opus agent. - -## Phase 5: Roadmap - -Goals and open questions per area, drawn from the deck, GitHub issues, and plan documents in the repos. Stated as goals, never as proposals. One Opus agent, reviewed closely by the main session for the facts-and-goals-only rule. - -## Phase 6: Integration and review - -1. Main session cross-link pass: every concept links to its terms, its repositories, its roadmap entries, and its history. -2. Both validators pass under strict mode. Site builds and deploys. Link checker passes. -3. The project lead reviews document by document. Each accepted document gains `verified` and `status: stable`. Rejections go back to the phase's agent with notes. -4. Documentation review: this plan marked complete, `CHANGELOG.md` reflecting the first release, `DEV_LOG.md` current, migration ledger complete. -5. Follow-ups filed as issues: source-repo pull requests replacing migrated docs with links; manuscripts to integrate; the proposals workstream (finding model format evolution, exam types, IPL data model system). - -# Phase status - -| Phase | Status | Notes | -|---|---|---| -| 0 Scaffold | done | init, license, skills, clones, inventories, skeleton, guide, checker, CI validation and site workflows, Quartz site building from a link-normalized staging copy | -| 1 Spine | done | overview (4), deck extract, glossary (48 terms), repository map, history (6), references (7); conflicts collected for the roadmap phase | -| 2 Semantic foundation | done | finding models (8), CDEs (3), anatomic locations (6), exam types (2), terminologies (2) | -| 3 Data structures | done | 10 documents including two migrations | -| 4 Applications | done | 7 documents | -| 5 Roadmap | done | six roadmap documents plus open questions (90 entries, 21 upstream defects) | -| 6 Integration and review | in progress | validators clean (115 concepts, 0 errors, 0 warnings), site builds, ledger and log merged; awaiting the project lead's document-by-document verification, then CHANGELOG release entry and follow-up issues | - -# Questions for the project lead - -Answers can come at approval time or during Phase 1. - -1. Answered 2026-09-21: the whole work edge is in scope. The imaging-problem-list `dev` branch is current state, `main` is the stable spec, and the findingmodels taxonomy branch is the content direction. -2. FindingModelForge is documented from `dev` and `feature/model-editing` (assumed; not contradicted). -3. Answered 2026-09-21: the per-modality taxonomies are called the "MGB exam-oriented sub-taxonomies"; no author name. -4. Answered 2026-09-21: publish the next-generation vocabulary document as draft, sourced to the branch, no committee material. -5. Answered 2026-09-21: publish the use case catalog with the committee credit. - -# Risks - -- **Drift while writing.** Source repos are active. Every `sources` entry pins a commit so a reader can tell what version was read. -- **Overreach into proposals.** Phase 5 and the next-generation vocabulary document are the places this is most likely. Review checks for it explicitly. -- **Site generator immaturity.** Quartz pinned by commit; Starlight as the fallback, with content unaffected either way. -- **Individual names in sources.** The denylist sweep catches author names from the source repos before publish. diff --git a/knowledge/plans/index.md b/knowledge/plans/index.md deleted file mode 100644 index a19ce4c..0000000 --- a/knowledge/plans/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Plans - -* [Knowledgebase build plan](./2026-09-20-knowledgebase-build-plan.md) - How the oidm-knowledge repository is scaffolded, populated from the source repositories, cross-linked, validated, and published. diff --git a/knowledge/repositories/index.md b/knowledge/repositories/index.md deleted file mode 100644 index c7d810c..0000000 --- a/knowledge/repositories/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Repositories - -Where the code and content live. One document, covering every source repository behind OIDM. - -* [Repository map](./repository-map.md) - Every source repository behind OIDM, with its organization, role, status, branch of record, last activity, deployed URL, and the documents in this bundle that cover it. diff --git a/knowledge/roadmap/index.md b/knowledge/roadmap/index.md deleted file mode 100644 index a6c6465..0000000 --- a/knowledge/roadmap/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Roadmap - -Stated goals and direction, area by area, with the source that states each one. Everything here is a fact about what a deck, an issue, a plan document, or the project lead has said. Nothing here is a design proposal by this knowledgebase; proposals are a separate workstream. Where sources disagree, the disagreement is recorded rather than resolved, and collected in open questions. - -* [Roadmap 2026](./roadmap-2026.md) - The project-level direction stated in the January 2026 status deck, the working group and use-case pipeline it proposes, and a per-repository summary of what is currently in flight. -* [Finding model format evolution](./finding-model-format-evolution.md) - The stated goals for where the Open Imaging Finding Model record format is going: the canonical structured-metadata rewrite, the source schema version 2 draft, relationship and graph ideas from the next-generation CDE work, versioning, and the discrepancies a revision would have to settle. -* [Finding model content direction](./finding-model-content-direction.md) - Where the finding model corpus is going: the MGB exam-oriented sub-taxonomies as the content spine, the expected supersession of Gamuts-derived models, the triage of unmatched rows, the content batches in flight, the definition cleanup plan, and the open content issues. -* [Imaging Problem List data model system](./ipl-data-model-system.md) - The stated goals for the Observation, Exam Finding List, and Imaging Problem List layer: the formal model system asked for in issue 1, the eight active plans on the development branch, the anatomic-compatibility follow-on, and the FHIR and IHE alignments that are documented but unimplemented. -* [Exam types roadmap](./exam-types.md) - The goals stated for an OIDM exam type layer, by whom and when, the one written design that exists, the building blocks a future effort would start from, and the questions no source answers. -* [Anatomic locations and RadLex](./anatomic-locations-and-radlex.md) - The stated goals for the anatomic location set: incorporation into RadLex as an overlay, the node requests owed to the RadLex anatomy track, the two unreconciled datasets, the tooling items carried forward from the original libraries, and the body region enumerations that disagree. -* [Open questions](./open-questions.md) - Every conflict between sources and every unverified fact found while building this knowledgebase, grouped by area, with the sources on each side and the owner where one is indicated. diff --git a/knowledge/semantic-foundation/anatomic-locations/index.md b/knowledge/semantic-foundation/anatomic-locations/index.md deleted file mode 100644 index 8a1ea35..0000000 --- a/knowledge/semantic-foundation/anatomic-locations/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Anatomic locations - -* [Why anatomic locations](./why-anatomic-locations.md) - Why OIDM curates its own anatomic location set instead of pointing at an existing ontology, and every place those identifiers are used across the model. -* [Anatomic location data model](./data-model.md) - The record model for an anatomic location in both lineages, its identifier scheme, its two hierarchies, its laterality triads, its classification fields, and its external codes, with verified counts. -* [Laterality conventions](./laterality-conventions.md) - How sided anatomic locations are identified, linked, contained, and named in the curated set, and how a side is assigned to a finding at coding time. -* [Lineage and current implementation](./lineage-and-current-implementation.md) - The two anatomic location lineages, the original curated set with its wrapper libraries and the anatomic-locations package inside findingmodel, which one is current, and what remains unreconciled between them. -* [RadLex integration](./radlex-integration.md) - What RadLex is, how the curated anatomic location set relates to it as an overlay, the open RSNA tracking issue for folding the set into RadLex, and the running list of node requests. -* [Anatomic location tooling](./tooling.md) - How to look up, search, and traverse anatomic locations with the anatomic-locations package and CLI, the older wrapper libraries, and the raw JSON download. diff --git a/knowledge/semantic-foundation/common-data-elements/index.md b/knowledge/semantic-foundation/common-data-elements/index.md deleted file mode 100644 index 9dd3c6f..0000000 --- a/knowledge/semantic-foundation/common-data-elements/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Common data elements - -* [CDEs and RadElement](./cdes-and-radelement.md) - What ACR and RSNA common data elements are, where they are published and schematized, where the two published schema descriptions disagree, and how the Open Imaging Data Model uses them today. -* [CDE staging](./cde-staging.md) - The CDEStaging repository, where informally authored candidate common data element definitions collect before the formal review pipeline, and how its content feeds finding models. -* [Next-generation CDE vocabulary](./next-generation-vocabulary.md) - What the next-gen-2026 branch of the ACR-RSNA-CDEs repository defines, covering the working vocabulary, the graph shape, the anatomy axis, the display grammar, and the alpha implementation, recorded as in-progress design work in an allied project. diff --git a/knowledge/semantic-foundation/finding-models/index.md b/knowledge/semantic-foundation/finding-models/index.md deleted file mode 100644 index 697d949..0000000 --- a/knowledge/semantic-foundation/finding-models/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Finding models - -* [Why finding models](./why-finding-models.md) - Why the finding is OIDM's unit of data, what a finding model definition carries, what it deliberately is not, and how downstream structures consume it. -* [Finding model format](./finding-model-format.md) - The released Open Imaging Finding Model record format, class by class and field by field, with a real example and the unreleased metadata fields on the work edge. -* [Finding model identifiers](./identifiers.md) - The OIFM and OIFMA identifier grammar, the organization-code segment and its registered values, value codes, how identifiers are minted and checked, and what stability is guaranteed. -* [Finding model content catalog](./content-catalog.md) - The findingmodels repository: what is in the corpus, where its identifiers and generated renders live, the conventions that govern it, how to browse it, and the content batches in flight on unmerged branches. -* [MGB exam-oriented sub-taxonomies](./finding-taxonomies.md) - The six exam-oriented finding hierarchies contributed by MassGeneral Brigham, their columns and hierarchy semantics, their row and match counts, and the triage they call for. -* [Authoring workflow](./authoring-workflow.md) - How finding models are created and reviewed today, through the Forge web application, the findingmodel-ai command line, the three agent skills, and the validator that gates every commit. -* [Metadata enrichment pipeline](./enrichment-pipeline.md) - How finding model definitions get structured metadata assigned by language models, what shipped, what the canonical rewrite on the work edge changes, and why it has not been released. -* [Finding models and common data elements](./finding-models-and-cdes.md) - The workbench relationship: finding models as fast exploratory definitions that can graduate into governed ACR and RSNA common data elements, and the vocabularies both have to fit. diff --git a/knowledge/semantic-foundation/finding-models/why-finding-models.md b/knowledge/semantic-foundation/finding-models/why-finding-models.md deleted file mode 100644 index efff0f3..0000000 --- a/knowledge/semantic-foundation/finding-models/why-finding-models.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -type: Concept -title: Why finding models -description: Why the finding is OIDM's unit of data, what a finding model definition carries, what it deliberately is not, and how downstream structures consume it. -tags: [semantic-foundation, finding-models, oifm, concept] -status: draft -generated: { by: claude-opus-5/claude-code, at: 2026-09-21T17:00:00Z } -sources: - - id: oifm-overview - resource: https://github.com/openimagingdata/findingmodels/blob/2267d5d0b430ec7117a6da91efe6069da38c0056/prompts/overview.md - title: "Finding Models: Overview, findingmodels repository" - last_modified: 2026-04-18 - - id: deck - resource: https://gamma.app/docs/Open-Imaging-Data-Model-2026-Status-Update:-Realizing-Object-Oriented-Imaging-Results-yfxzx4q9zssafal - title: "Open Imaging Data Model 2026 Status Update: Realizing Object-Oriented Imaging Results, January 2026" - - id: site-post - resource: https://www.openimagingdata.org/findings-cdes-and-observations/ - title: "Findings, CDEs, and Observations, openimagingdata.org, 2023-06-24" - - id: fm-model - resource: https://github.com/openimagingdata/findingmodel/blob/75afd39a400419dcfaf7c8d4a34f065b4d804e0d/packages/findingmodel/src/findingmodel/finding_model.py - title: FindingModelFull definition, findingmodel repository, main branch - - id: efl-example - resource: https://github.com/openimagingdata/imaging-problem-list/blob/06f64a7893b444b761dc069ed86140a081195eac/README.md - title: imaging-problem-list README, main branch, the Exam Finding List specification ---- - -# The finding is the unit - -A radiology report is prose. Somewhere inside it a radiologist has asserted that a thing is there, or is not there, or is bigger than it was. The Open Imaging Data Model (OIDM) takes that assertion as its atom, and a [finding model](/glossary/finding-model.md) is the reusable definition of one such assertion type. - -The reason to define findings rather than reports is stated plainly in the overview note that governs authoring: finding models "turn that unstructured language into structured, machine-accessible observation objects, each linked to a rich knowledge base of clinical context."[^oifm-overview] An agent reads a report, matches each finding it recognizes to a definition, and emits an [Observation](/glossary/observation.md) tagged with the finding type and the values of its [attributes](/glossary/attribute.md). - -The second reason is time. "A radiology report is a snapshot, but the findings it describes persist across exams. A pleural effusion seen today may be the same one from last week, now larger."[^oifm-overview] Two reports written a year apart by two radiologists at two institutions can only be compared if both name the same thing the same way. That shared naming is what a finding model supplies, and it is why the [Imaging Problem List](/glossary/imaging-problem-list.md) can organize a patient's history by finding rather than by date. - -# What a finding is, and is not - -The scope is broader than pathology. The overview note counts as findings: pathologic entities, physiologic observations, devices and hardware, postsurgical states, anatomic variants, and image quality problems. It also refuses to separate description from diagnosis. "Diagnostic terms are first-class findings, do not question whether a diagnosis 'should' be a finding."[^oifm-overview] Both "bilateral perihilar opacities" and "pulmonary edema" are modeled, because radiologists write both. - -The test is whether a radiologist would write "there is X" or "no X" as a standalone statement. That test excludes a state of a finding ("stable cardiac silhouette"), a qualified version of one ("large pleural effusion"), normal anatomy, radiographic signs and interpretation techniques, exam metadata, clinical history, and recommendations. The guiding principle is that "a finding is a noun phrase; everything else is an attribute."[^oifm-overview] The full treatment, with its worked examples of too broad, too narrow, and right level, is in [the overview extract](/references/oifm-overview-extract.md). - -Negative assertions are inside the scope, not outside it. When a report says "no fracture," that is an active observation recorded as [presence](/glossary/presence.md) absent, which means the finding was looked for and not found. Models therefore have to exist at the breadth where radiologists make those sweeping statements, which is why "chest wall fracture" and "upper abdominal abnormality" are legitimate models. - -# What the definition carries - -A finding model is a knowledge base entry, not a label. Each definition carries a canonical name, a description written for a radiologist audience, synonyms covering the ways the same observation gets expressed, [tags](/glossary/tag.md) for browsing, [index codes](/glossary/index-code.md) linking to SNOMED CT, RadLex, and the Radiology Gamuts Ontology, optional [anatomic locations](/glossary/anatomic-location.md), [contributors](/glossary/contributor.md), and the attribute list.[^oifm-overview][^fm-model] - -Attributes are the characterization axes. Two are near-universal and come first by convention: presence, with the values absent, present, indeterminate, and unknown, then [change from prior](/glossary/change-from-prior.md). After those come the domain-specific axes, size, severity, density, morphology, enhancement pattern, and whatever else the finding needs. Each choice value gets its own code, so a stored Observation can cite not just the finding and the attribute but the specific value chosen. Every identifier in that chain is machine-generated and never written by hand. The field-level specification is in [the format document](/semantic-foundation/finding-models/finding-model-format.md). - -Quality in these definitions is load-bearing rather than cosmetic. "A sloppy synonym maps the wrong concept to an observation; a nonsensical attribute propagates meaningless data; a poorly scoped model conflates distinct clinical entities. Errors here don't just look bad, they cause incorrect downstream reasoning about patient care."[^oifm-overview] - -# What a finding model is not - -It is not a report template. Templates were the earlier approach to structured reporting, and a finding model does not prescribe what a report contains or in what order. It defines one finding and leaves composition to whatever produces the report. - -It is not a common data element, at least not yet. The January 2026 status deck frames finding models as "the CDE workbench": rapid, language-model-assisted content creation acting as "a proving ground for formal common data elements," carrying "rich definitions with embedded relationships and semantic tags that can graduate to standards."[^deck] The relationship runs from finding model toward [common data element](/glossary/cde.md), not the other way, and it is worked through in [finding models and CDEs](/semantic-foundation/finding-models/finding-models-and-cdes.md). - -It is also not an instance. The definition says what a pleural effusion is and how one is characterized. It says nothing about any particular patient. That separation is the whole point of the layering described in [architecture](/overview/architecture.md). - -# How the definitions get used - -Downstream structures reference finding models only by identifier. An [Exam Finding List](/glossary/exam-finding-list.md) entry carries a `findingCode` holding the model's OIFM identifier, an optional anatomic location, and an `attributes` array in which each element pairs an OIFMA attribute identifier with a value code drawn from that attribute's permitted values.[^efl-example] Nothing in the Exam Finding List repeats the definition; a reader that wants to know what `OIFM_GMTS_016552` means resolves it against the content repository. - -```json -{ - "findingCode": "OIFM_GMTS_016552", - "findingDescription": "urinary tract calculus", - "attributes": [ - { - "attributeCode": "OIFMA_GMTS_707209", - "attributeValueCode": "OIFMA_GMTS_707209.1", - "attributeValueDescription": "present" - } - ] -} -``` - -The same identifiers flow upward into the Imaging Problem List, which groups a patient's observations by finding code across exams. The details of both structures are in [the Exam Finding List document](/data-structures/exam-finding-list.md). - -An older statement of the same idea, from the 2023 site post that predates the finding model format, describes a radiology finding as a FHIR Observation "semantically labeled with ACR/RSNA Common Data Element identifiers," with the set identifier naming the finding type and element identifiers naming the attributes.[^site-post] The shape survived; what changed is that OIDM now mints its own identifiers for the definitions that no common data element covers. - -[^oifm-overview]: "Finding Models: Overview", findingmodels repository -[^deck]: Open Imaging Data Model 2026 Status Update, January 2026 -[^site-post]: "Findings, CDEs, and Observations", openimagingdata.org, 2023-06-24 -[^fm-model]: FindingModelFull definition, findingmodel repository, main branch -[^efl-example]: imaging-problem-list README, main branch diff --git a/knowledge/semantic-foundation/index.md b/knowledge/semantic-foundation/index.md deleted file mode 100644 index c44b3d3..0000000 --- a/knowledge/semantic-foundation/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Semantic foundation - - -* [finding-models](./finding-models/) - Open Imaging Finding Models: format, identifiers, content, authoring -* [common-data-elements](./common-data-elements/) - ACR/RSNA common data elements and how finding models relate to them -* [anatomic-locations](./anatomic-locations/) - The curated anatomic location set, its data model, and RadLex integration -* [exam-types](./exam-types/) - Exam types: goals and existing building blocks -* [terminologies](./terminologies/) - External terminologies and the lookup tooling diff --git a/knowledge/semantic-foundation/terminologies/index.md b/knowledge/semantic-foundation/terminologies/index.md deleted file mode 100644 index 76f1206..0000000 --- a/knowledge/semantic-foundation/terminologies/index.md +++ /dev/null @@ -1,6 +0,0 @@ -# Terminologies - -The external vocabularies OIDM points at rather than reinvents, and the tooling that resolves them. - -* [Ontologies used](./ontologies-used.md) - Every external terminology OIDM touches, who governs it, the shape of its identifiers, the field and structure that carries it, and which lookup tool resolves it. -* [med-ontology-lookup](./med-ontology-lookup.md) - The molu library and command line tool that resolves medical terms and codes across RadLex, SNOMED CT, FMA, LOINC, and UMLS, its current capabilities, its stated direction, and its status. diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..f4a67ad --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,3 @@ +# Local plugin ESM is hand-written source, despite the root dist/ rule. +!plugins/*/dist/ +!plugins/*/dist/** diff --git a/site/CURRENT_TASK.md b/site/CURRENT_TASK.md new file mode 100644 index 0000000..ffad3ba --- /dev/null +++ b/site/CURRENT_TASK.md @@ -0,0 +1,149 @@ +# Current task: the site build is on dev, awaiting the project lead's look and commit + +The public website now builds from `site/content/` and is deployed to dev: https://oidm-knowledge-dev.talkasab.workers.dev (2026-10-06). How the build works is in [README.md](README.md); what was done, in [HISTORY.md](HISTORY.md); what comes next, in [ROADMAP.md](ROADMAP.md); the project lead's words, in [guidance.md](guidance.md). The approach as agreed is kept at the end of this file. + +`site/content/` is the website's source Markdown and the SVG figures it embeds. It is **not** an OKF bundle: OKF rules and validators do not apply to it. A page carries `title`, `description`, and, once the project lead has reviewed it, a `verified` stamp. Citations are footnotes in the page text. + +## State (2026-10-22) + +- Nothing from the build work is committed. Each commit waits on the project lead's explicit yes. Nothing is staged while the index is shared with the Codex session that owns the root files; that session coordinates index ownership before any staging. +- Commit order agreed with that session on 2026-10-22: the site commit first, then the knowledge-graph reset last. Then `bootstrap` is pushed (with approval), a new pull request into `main` is opened, CI runs, and the project lead reviews and merges it. A main ruleset requires one approving review and blocks force-pushes and deletion. The local pre-push hook refuses any push involving `main`, so agents never push or merge `main`. No new worktrees for now. +- The site commit holds: + - everything under `site/`, including this file and the other three records; + - `.github/workflows/deploy.yml`; + - the deletion of `tools/prepare_site_content.py`; + - the root `Taskfile.yml` integration (`includes: site:`), prepared by the Codex session; + - the site-only parts of `README.md`, `CHANGELOG.md`, `docs/HANDOFF.md` and `DEV_LOG.md`, also prepared by that session. +- The graph and archive state and the contributor updates go in the graph commit. +- The site build needs nothing from `knowledge/`. A staging build from a copy holding only `site/` and `assets/brand/` succeeded on 2026-10-22. +- Suggested commit message: + + ``` + Build the public website from site/content/ with its own navigation and review gate + + - Navigation ordered by site/navigation.yaml replaces Quartz's explorer + - "DRAFT / Awaiting Review" banner on dev and staging; production refuses unstamped pages + - Site check, verify tool, and Taskfile under site/, run from the root as task site:... + - Deploy workflow triggers on site/**, assets/brand/** and itself; OIDM logo and favicon added + ``` + +- Before committing, run `task site:check` and `task site:build`. Both passed on 2026-10-22. +- The implementation report and review screenshots are in the ignored `scratch/review/site-build/` (`REPORT.md`), from `codex-impl-2`. +- Deploying to dev from a Claude session worked on 2026-10-06: `task -t site/Taskfile.yml deploy`, or `task site:deploy` with the root include. + +## Next, proposed: page comments with Giscus (awaiting the project lead's yes) + +The project lead asked on 2026-10-08 whether comments can run on all three sites, with each comment clearly showing which site it came from (`guidance.md`). The proposal: + +- Quartz's `@quartz-community/comments` plugin, already in `quartz.config.yaml` and switched off, using Giscus (GitHub Discussions). +- One Discussions category per environment: "Comments: dev", "Comments: staging", "Comments: production". `build.sh` sets the category for its `--target`, and the comment box is labelled with the environment. +- Mapping: `pathname`, so each page has one thread per environment. The default, `url`, would also split threads by host. +- The plugin does not pass Giscus's `data-term`, so the `specific` mapping is unavailable. A thread shared across environments could not show where a comment came from, which is why there is one category per environment. +- Consequence: comments are separate per environment, and dev comments do not appear on production. +- Production is public: anyone with a GitHub account can comment there, and moderation happens in GitHub Discussions. +- The project lead's steps: + 1. Turn on Discussions for `openimagingdata/oidm-knowledge`. + 2. Create the three categories. + 3. Install the giscus GitHub app on the repository. +- Then: read the repository and category IDs from giscus.app, wire them in, and confirm on dev that threads land in the right category. +- The alternative offered was Hypothesis with a private group: comments anchored to passages, private, and needing a Hypothesis account. It was recommended for draft review. The project lead's follow-up asked about all three sites, which reads as Giscus; confirm before building. + +## Open with the project lead + +- Look at dev and say yes to the commit (above). +- Giscus, as proposed above. +- The agreed pages' footnotes say his guidance is "recorded verbatim in the layout plan". That plan is now only in git history; the same words are in `guidance.md`. The footnotes are agreed text, so they are unchanged until he decides. +- Figure acceptances: see the Figures table in the roadmap. + +## Boundaries + +- **The other track.** The knowledge graph (`knowledge/`, `archive/`, `docs/`, `DEV_LOG.md`, `CHANGELOG.md`, the root `Taskfile.yml` and `README.md`) is a separate track. A Codex session in the Herdr workspace `oidm-knowledge` owns those files. On 2026-10-22 it was pane `w3:p2`; rediscover it with `herdr pane list`. +- **Its files.** Do not edit them. Hand root changes, including site pointers, to that session through Herdr. +- **The index.** While the checkout is shared, that session stages. This session edits only site files and checks what is staged. +- **Commits.** Commit only on the project lead's explicit yes, one concrete commit at a time, with the paths agreed in the State section above. +- **Implementation.** It goes to a `codex-impl` Codex tab in the `oidm-knowledge` workspace, from a written brief. Review the screenshots and code before deploying. + +## The approach as agreed (2026-10-06; implemented) + +Kept as the record of what was decided and why. The build as it now works is described in [README.md](README.md). For commits and coordination, the State and Boundaries sections above are authoritative; the handoff and commit steps below are historical. + +**Input.** `build.sh` builds `site/content/` directly, with `markdownLinkResolution: relative`; no staging copy, no link rewriting. `tools/prepare_site_content.py` is deleted. `index.md` (the Overview) is the landing page; Quartz does this for a root `index.md`. + +**Build target.** `build.sh --target dev|staging|production`, default `dev`, so a build is a draft unless production is asked for by name. The script exports `SITE_TARGET` and sets `baseUrl` for the target. The site Taskfile passes the target in each deploy task. + +**"DRAFT / Awaiting Review".** A new local component shows the banner at the top of every page, on every page type, unless `SITE_TARGET=production`. It reads the variable at build time, and an unset target counts as a draft. Quartz has no built-in banner or announcement component. + +**Production gate.** A site check, `site/tools/check_site.py`, which `build.sh` runs before Quartz so no build can skip it: + +- every page has `title` and `description`; +- every relative link and figure points at a file in `site/content/`; +- no Mermaid blocks; +- for `production` only, every page has a `verified` stamp. One missing stamp fails the build and names the page. + +`verify.py` is reworked as `site/tools/verify.py`. It writes the stamp and nothing else (no `status`), lists unverified pages, and reports verified/total. It covers `site/content/` only. Placeholder pages need stamps too before a production build. + +**Navigation (agreed 2026-10-06, after the project lead rejected both explorer routes; "make it look GOOD").** Quartz's explorer is replaced by our own navigation component, rendered at build time. It does not reorder the explorer. + +- **Why not the explorer.** The explorer builds its tree in the browser from the search index. That index carries only slug, title, links, tags, and text, so frontmatter cannot reach it. Its only ordering hook is a function shipped to the browser as text. Two upstream requests for frontmatter ordering, quartz#2125 and quartz#2363, were closed at the v5 release without being implemented. +- **The order lives in one data file, `site/navigation.yaml`.** It is a nested list of page paths: the Overview, then the five pillars in order (Foundation Context, Data Structures, SDKs, Use Cases, Sample Applications), each with its pages in order. There is no function and no number scheme, and nothing in Quartz is patched. +- **What the component renders.** A Quartz component receives every page and its frontmatter at build time. It reads the file and renders a nested list of links in the left column, labelled with each page's `title`. The current page is marked and its pillar is open. It is plain HTML, works without JavaScript, and on narrow screens collapses behind a toggle. +- **The site check covers it.** It fails if a page is missing from the file, or if an entry has no page. +- **Later, optional:** offer frontmatter ordering to the explorer upstream, drafted for the project lead's review first. + +**Duplicate title.** Exclude `@quartz-community/article-title` on content and folder pages; the page's own heading stays as written. The `reader-mode` excludes are fixed to full names at the same time. + +**Folder listings.** Hidden with one CSS rule, `.page-listing { display: none }` (agreed 2026-10-06). Each pillar's index is agreed text that lists its own pages, in its own order. + +**Plugins.** Plugins move to `site/plugins/`. + +- `okf-meta-plugin` is removed; the pages carry nothing it renders. +- `okf-lightbox-plugin` becomes `site/plugins/lightbox/`, unchanged in behavior. +- The banner, the header logo, and the navigation go in a new `site/plugins/site-chrome/`. +- A `site/.gitignore` re-includes `plugins/*/dist/`, so the plugin code is tracked without touching the root `.gitignore`. +- `build.sh` links preact into each plugin that needs it, as it does now for one. + +**Config clean-up.** + +- Mermaid off. +- Off, because nothing on the site uses them: `latex`, `canvas-page`, `bases-page`, `tag-page`, and `tag-list`. +- `pageTitle` becomes "Open Imaging Data Model: Knowledge" (the project lead, 2026-10-06). +- The explorer is off, replaced by the navigation above. +- Graph view, backlinks, search, the table of contents, RSS, and the theme stay as they are. Theme and typography are later work. + +**Brand assets.** + +- `build.sh` copies the files the site uses from `assets/brand/oidm/` into the checkout's `quartz/static/`, so nothing is duplicated in git. +- The square mark replaces Quartz's `icon.png`, from which the favicon plugin makes `favicon.ico`. +- The header shows the full logo, linked to the Overview, in place of the text title: `logo-color` in light mode, `logo-white` in dark. +- The brand guide notes that the mark's legibility at favicon size needs checking; I will look at it. + +**Placeholder pages.** These are the seven pages the agreed pages link to. Each has a `title` and a `description`. Its body says what the page will cover, drawn from the agreed pages and the project lead's guidance, then "This page is coming." The project lead agreed the text below on 2026-10-06. + +| Page | Title | Proposed body (before "This page is coming.") | +|---|---|---| +| `data-structures/index.md` | Data Structures | The patient-side structures: the Observation, the Exam Finding List, the Imaging Problem List, and the Imaging Persona, and how the patient graph and the Foundation Context graph work together. These structures are still coalescing, so each source's current version will be shown side by side, dated. | +| `sdks/index.md` | SDKs | The tools that wrap the data structures so developers can create, read, and manipulate instances in real applications, and the tools that help author and maintain Foundation Context itself. | +| `use-cases/index.md` | Use Cases | A list of the applications, tools, plugins, and output products an ecosystem built on this foundation could support, with a fuller page for some of them. | +| `sample-applications/index.md` | Sample Applications | The applications the project has actually built, from demonstrations to prototypes, each with the idea it illustrates and the use cases it relates to. | +| `foundation-context/anatomic-locations.md` | Anatomic Locations | The curated index of anatomic locations anchored in RadLex: the existing work, its containment, part-of, and laterality relationships, and its incorporation into RadLex itself. | +| `foundation-context/exam-types.md` | Exam Types | The exam-type content to come: preferred high-level exam types such as CT Chest, MRI Brain, and X-ray Knee over the LOINC/RSNA Radiology Playbook, each tied to the anatomy it always, usually, or possibly includes. | +| `foundation-context/standards.md` | Standards | The standards and references Foundation Context layers over and cites (RadLex, the LOINC/RSNA Radiology Playbook, SNOMED CT, FMA, ICD, CPT, Radiopaedia, and Wikipedia), and what each contributes. | + +**Tasks and deploys.** + +- Site tasks go in their own `site/Taskfile.yml`. The root Taskfile's tasks belong to the graph (its `check` runs the graph's validators; `sources:*` and `viz` are graph work), and the other track edits that file. The site's check, build, and deploy chain is its own. +- The site tasks are `check`, `build`, `serve`, `preview`, `verify`, `review-status`, `review-list`, and `deploy` (dev), `deploy:staging`, `deploy:prod`. Deploys depend on the site check, not the graph's validators. +- The root `Taskfile.yml` includes it as `site:` (`task site:deploy`), and its old site tasks are removed. The Codex session that owns the root files made this change, and it goes in the site commit (see State). `task -t site/Taskfile.yml deploy` also works. +- The deploy workflow triggers on `site/**`, `assets/brand/**`, and its own file. It builds the staging target, which runs the site check, and skips the deploy step with a notice while the Cloudflare secrets are unset, so pushes do not fail. + +**Order of work.** + +1. This approach agreed. +2. Implementation in a `codex-impl` tab, from this file. +3. Local build, looked at in a browser in light and dark, desktop and phone width. +4. Deploy to dev (or the project lead runs `task -t site/Taskfile.yml deploy` if the classifier refuses it). +5. The URL to the project lead. +6. `site/README.md`, `ROADMAP.md`, and `HISTORY.md` updated. +7. One commit on his yes. Superseded on 2026-10-22: the site commit also carries the root Taskfile integration and the site parts of the root documentation (see State). + +Pointer edits outside `site/` (root `README.md`, `docs/HANDOFF.md`, the root `Taskfile.yml`) go to the Codex session that owns the root files. diff --git a/site/HISTORY.md b/site/HISTORY.md new file mode 100644 index 0000000..57609f5 --- /dev/null +++ b/site/HISTORY.md @@ -0,0 +1,16 @@ +# History + +Completed work on the website, newest first. Page and figure work before 2026-10-05 is recorded in the repository's `DEV_LOG.md`. + +## 2026-10-06 + +- Built the website from `site/content/` alone and deployed it to dev (https://oidm-knowledge-dev.talkasab.workers.dev), on the approach agreed with the project lead. Our own build-time navigation, ordered by `site/navigation.yaml`, replaces Quartz's explorer, because the explorer can be ordered only by a function passed to the browser. A "DRAFT / Awaiting Review" banner appears on every page unless the target is production. A site check runs before every build, and a production build refuses any page without a `verified` stamp. The site has its own `site/tools/verify.py` and `site/Taskfile.yml`, and the deploy workflow triggers on `site/**`. The OIDM logo and favicon are published from `assets/brand/`. Duplicate titles, folder listings, Mermaid, and unused plugins are gone. The badge plugin was removed; the lightbox moved to `site/plugins/lightbox/` and is now tracked in git. Added seven placeholder pages with agreed summaries. Implemented in `codex-impl-2` from a brief. Checked: the site check, a crawl of the built HTML, and screenshots in light and dark at desktop and phone widths. Astra made the root Taskfile include and the pointer edits. +- Committed the site work without touching the graph track's uncommitted batch: `5c41ac0` (site area), `2b3aac9` (brand assets), `9085d85` (figure config). Each staged tree was validated on its own before committing. Wrote the next task, the build approach, into `CURRENT_TASK.md`. +- Renamed `site/pages/` to `site/content/`: it holds the website's source Markdown and figures (Quartz's own name for its input). Earlier entries use the new name. +- Cleaned up `site/` to hold only the website. Extracted the project lead's page guidance verbatim from the planning records in git history into `guidance.md`, in place of six restored plan files. Removed the tracked review renders (regenerable) and the material drafts (still at `knowledge/drafts/` in `9725105`). Stripped the pages' frontmatter to `title` and `description`, since `site/content/` is website content, not an OKF bundle; page text unchanged. Moved the figures' `.excalidraw` sources to `tools/diagrams/excalidraw/` and the candidate figures to ignored `scratch/review/candidates/`. Split the plan into `CURRENT_TASK.md`, `ROADMAP.md`, and this file. +- Moved the brand assets to `assets/brand/` at the repository root, since the site and the diagram tooling both use them; all 16 checksums match. +- Figure output paths are now configuration: `tools/diagrams/figures.toml` maps each figure to its builder, `.excalidraw`, and SVG; builders look up their path by name, and `render_excalidraw.py --figure ` renders one. Every builder rebuilt byte-identical; the two-planes render matched the published SVG. + +## 2026-10-05 + +- Moved all outward-facing page work out of the knowledge graph into `site/`, at the project lead's direction, so the pages and the graph cannot be confused. The five agreed pages and their embedded figures went to `site/content/` in the pillar layout by git moves (prose unchanged; link and citation paths only). The figure builders write to the new paths; the two-planes figure rebuilt byte-identical. Graph-side links repointed, path changes only; `task check` clean. diff --git a/site/README.md b/site/README.md index 4cbcf02..c045831 100644 --- a/site/README.md +++ b/site/README.md @@ -1,78 +1,167 @@ # Site -Renders `knowledge/` (the OKF bundle) as a static [Quartz](https://quartz.jzhao.xyz) site. +This directory is the website, a separate track from the knowledge graph in `knowledge/`: -Quartz itself is not vendored in this repository — only our config and one small plugin -live here. `build.sh` clones Quartz at a pinned commit into `site/.quartz-src/` -(gitignored) at build time and points it at `knowledge/`. +| Path | What it holds | +|---|---| +| `content/` | The website's content: Markdown pages and the SVG figures they embed. Not an OKF bundle. | +| [CURRENT_TASK.md](CURRENT_TASK.md), [ROADMAP.md](ROADMAP.md), [HISTORY.md](HISTORY.md) | The work in hand, what comes next with page and figure status, and what is done. | +| [guidance.md](guidance.md) | The project lead's guidance on the website, verbatim. | +| `build.sh`, `quartz.config.yaml`, `wrangler.jsonc`, the two plugins | The build. | -## What's here +Figure sources and builders are in `tools/diagrams/`; shared brand assets are in `assets/brand/` at the repository root. -| Path | Purpose | -|---|---| -| `quartz.config.yaml` | Site config: theme, plugins, layout | -| `okf-meta-plugin/` | Component that renders OKF frontmatter as a badge row | -| `build.sh` | Clones Quartz, wires up the plugin, builds | +The public website builds the Markdown and SVGs in `site/content/` directly with [Quartz 5](https://quartz.jzhao.xyz). The site uses relative Markdown links and does not stage or rewrite the knowledge bundle. + +## Build and targets + +Requires Node 22 or newer, npm, git, Python 3.11 or newer, and uv. Task is optional. Run these commands from the repository root: -`site/.quartz-src/` (the Quartz checkout), `public/` (the build output), and any -`node_modules/` under `site/` are gitignored. +```bash +bash site/build.sh # draft dev build into public/ +bash site/build.sh --target staging # draft staging build +bash site/build.sh --target production # requires review stamps on every page +bash site/build.sh serve # dev server with live reload +SITE_BASE_URL=example.com/path bash site/build.sh --target staging +``` -## Building locally +The default target is `dev`. Dev and staging show `DRAFT / Awaiting Review`; production omits the banner. Each target selects its Workers hostname. `SITE_BASE_URL` overrides the hostname and optional base path. The tracked config's `baseUrl` stays unchanged. -From the repository root: +`build.sh` runs the site check before accessing Quartz, copies the config into `site/.quartz-src/`, runs `npm ci`, links shared dependencies into the local plugins, installs the plugins, and copies brand assets. Quartz then builds `site/content/` into `public/`. The checkout, build output, and plugin `node_modules/` are gitignored; hand-written plugin `dist/` files are included by `site/.gitignore`. + +## Check and review pages ```bash -bash site/build.sh # one-shot build -> public/ -bash site/build.sh serve # local dev server with live reload +uv run site/tools/check_site.py +uv run site/tools/check_site.py --target production +uv run site/tools/verify.py --status +uv run site/tools/verify.py --list site/content +uv run site/tools/verify.py site/content/foundation-context/relationships.md +uv run site/tools/verify.py --by human:someone site/content/sdks/index.md +``` + +The site check requires non-empty `title` and `description` frontmatter on every Markdown page, valid local Markdown link and image targets under `site/content/`, no Mermaid fences, and complete navigation without duplicate entries. It ignores links inside fenced code. Production additionally requires a `verified` entry on every page, including index and placeholder pages. A missing stamp fails the build and names the page. + +`verify.py` defaults to reviewer `human:talkasab`. It adds only `verified`, after `description` for a new field, or appends to existing stamps. Other frontmatter and page text retain their formatting. The tool rejects paths outside `site/content/`. Status counts every Markdown page by top-level directory; the root index appears under `.`. Verification records a human review and is run after that review. + +## Navigation and site chrome + +`navigation.yaml` lists paths relative to `site/content/` in display order, with optional `page` and `pages` groups. Every page appears exactly once. The Overview comes first, followed by Foundation Context, Data Structures, SDKs, Use Cases, and Sample Applications. Link labels default to each page's frontmatter `title`. A page entry can supply a non-empty `label` to override it, as the root entry does with `Overview`: + +```yaml +- page: index.md + label: Overview ``` -Requires Node >=22 (Quartz's own requirement; the deploy workflow uses Node 24) and git. -Output lands in `public/` at the repo root. +`plugins/site-chrome/` provides `ReviewBanner`, `SiteLogo`, and `SiteNav`. The pinned Quartz layout loader selects one component per plugin entry, so three config entries use the `SiteChrome` adapter with a component option and their own layout position. All code is hand-written ESM with no build step. The build links Quartz's preact, YAML parser, and shared path utilities into the plugin. + +The build exports the absolute `SITE_NAVIGATION_FILE` path so server-rendered navigation does not depend on the checkout's working directory. Direct plugin use defaults to the navigation file relative to the plugin itself. Quartz's `pathToRoot`, `resolveRelative`, and slug helpers keep logo and navigation links valid under a base path. Native `details` toggles expand pillar pages and the phone's Contents panel without JavaScript. A small Quartz `nav` event handler updates active links and pillar expansion after SPA navigation. -## Deploying +The logo uses the color SVG in light mode and the white SVG when Quartz sets `saved-theme="dark"` on the root element. Theme-aware CSS styles navigation, focus states, and the draft banner. The automatic `.page-listing` is hidden; pillar indexes carry their own links. Article-title components are excluded on content and folder pages because the source pages include their own headings. Graph, backlinks, search, table of contents, RSS, sitemap, and existing theme fonts and colors remain configured. -`.github/workflows/site.yml` builds and deploys to GitHub Pages on every push to `main` -that touches `knowledge/**` or `site/**`. It runs the exact same `bash site/build.sh` a -contributor runs locally, so CI and local builds can't drift apart. +`note-properties` remains enabled to parse frontmatter, with its visible panel suppressed. -## The pinned Quartz commit +## Brand assets -`build.sh` pins Quartz to one commit (`QUARTZ_SHA`) so the site doesn't move under us -between builds. To bump it: +The build copies `assets/brand/oidm/logo-color.svg` and `logo-white.svg` into Quartz's `static/brand/`. It restores Quartz's tracked static files before copying `mark.png` over `static/icon.png`, which the favicon plugin uses to generate `favicon.ico`. Brand files remain shared with diagram tooling rather than duplicated in this directory. See the [brand notes](../docs/brand/README.md). -1. Update `QUARTZ_SHA` in `site/build.sh`. -2. Update the matching `QUARTZ_SHA` value in `.github/workflows/site.yml` (used only as - an npm cache key there — it doesn't need to match exactly, but keeping it in sync - avoids a stale cache). -3. Run `bash site/build.sh` locally and check the result before pushing, since a newer - Quartz commit can change config options or plugin APIs. +## Tasks -## okf-meta-plugin +Run `task -t site/Taskfile.yml ` from the repository root. Every task sets its working directory from `TASKFILE_DIR`, and the root Taskfile includes it as `site:`, so `task site:` works from the root as well. -A Quartz component plugin (hand-written ESM in `dist/`, no build step) that reads a -page's OKF frontmatter and renders it as a small badge row plus a sources list, using -the conventions in [`knowledge/guides/authoring-guide.md`](../knowledge/guides/authoring-guide.md#frontmatter): +| Task | Action | +|---|---| +| `check` | Validate pages and navigation; accepts `-- --target production` | +| `build`, `serve` | Build or serve the draft dev site; accept build arguments after `--` | +| `preview` | Build and serve through Wrangler's local static-asset server | +| `verify` | Stamp reviewed paths supplied after `--` | +| `review-status`, `review-list` | Count reviews or list unverified pages; list defaults to all content | +| `deploy` | Check, build dev, and deploy to the dev Worker | +| `deploy:staging` | Check, build staging, and deploy to staging | +| `deploy:prod` | Check, build with the production gate, and deploy to the public Worker | -- **Type** badge first, then **status** (`draft` / `stable` / `deprecated`). -- A **stale** badge once `stale_after` is in the past. -- One badge per `verified` entry, shown as `by (at)`. An entry whose `by` starts with - `human:` (a person's sign-off, per the actor convention) gets a distinct - "human-reviewed" style so it reads differently from an agent or process attestation. -- A **sources** list with each title linked to its `resource`, plus `author` and - `last_modified` when the source entry has them. +Deploy tasks depend on the site check. `DEV_URL`, `STAGING_URL`, and `PROD_URL` can override the target hostnames. -It declares `preact` as a `peerDependency` rather than bundling its own copy, since -Quartz already provides one. Because `quartz plugin install` symlinks a local plugin -in place instead of copying it, Node resolves `import "preact"` against the plugin's -*real* path — which sits outside Quartz's own `node_modules` tree — and fails to find -it. `build.sh` works around this by symlinking Quartz's `preact` into -`okf-meta-plugin/node_modules/preact` after `npm ci`. +## Deploy workflow and hosting -Note this Quartz fork moved frontmatter parsing out of core and into the -`note-properties` plugin's transformer. `quartz.config.yaml` keeps that plugin enabled -(with `hidePropertiesView: true`) purely so frontmatter gets parsed at all — its own -visible properties panel is suppressed so it doesn't duplicate okf-meta-plugin's badges. +`.github/workflows/deploy.yml` runs on pushes to `main` that change `site/**`, `assets/brand/**`, or the workflow itself, and on manual dispatch. It installs Node 22 and uv, caches npm using the build script, config, and plugin manifests, then checks and builds staging. It deploys only to staging when both Cloudflare secrets are set. With either secret unset, it emits a notice and succeeds after the build. -## Link resolution +`site/wrangler.jsonc` defines assets-only Cloudflare Workers serving `public/`: -The bundle uses two link styles the OKF spec allows: bundle-absolute links (`/dir/file.md`) in concept documents and `./file.md` links in directory indexes. Quartz's `markdownLinkResolution` handles one style at a time, so `build.sh` first runs `tools/prepare_site_content.py`, which copies `knowledge/` to a temporary directory outside the repository (Quartz skips gitignored paths) and rewrites every internal link to the bundle-absolute form. Quartz then builds from that copy with `markdownLinkResolution: absolute`. The bundle in git is never modified. +| Target | Worker | +|---|---| +| Dev | `oidm-knowledge-dev` | +| Staging | `oidm-knowledge-staging` | +| Production | `oidm-knowledge` | + +Wrangler's `html_handling: auto-trailing-slash` serves clean page URLs and folder indexes. Local previews use `npx wrangler dev --config site/wrangler.jsonc --env dev`. Deployment requires `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`. Worker endpoints can be added later without changing the static build. + +## Pinned Quartz + +`build.sh` pins `QUARTZ_SHA` to `97a2d05f80c4c50534959b1d0d41cc4b3895625e`. Quartz is cloned into `site/.quartz-src/` rather than vendored. To update the pin, change `QUARTZ_SHA`, review the dependency and workflow cache configuration, then build locally and inspect the result. A new commit can change plugin APIs or config options. + +## Lightbox + +A Quartz component plugin (hand-written ESM in `dist/`, no build step, no external +dependency) that turns every diagram image in an article into a click-to-zoom overlay, +so wide SVGs squeezed into the text column can be inspected without leaving the page. + +- Clicking an `article img` (or its wrapping link) opens the image in a full-viewport + overlay: mouse wheel or pinch to zoom around the cursor/touch point, drag to pan, + double-click to reset, Esc/click-outside/close-button to dismiss. +- The plain link is still the fallback — middle-click or Ctrl/Cmd-click bypasses the + overlay and opens the image in a new tab as normal, and the overlay's own "Open full + size" button does the same (that one's the true 1:1 resolution, in a new tab). +- The overlay itself opens at `scale = 0.92 * viewportWidth / imageWidth` — width-driven + only, no height term, floored at whatever scale the image was already rendering at + inline so opening it can never look like a regression. It opens scrolled to the top of + the diagram rather than vertically centered, since the point is reading top-to-bottom; + height overflow is normal and the stage pans. This is deliberately *not* a min(width, + height) "fit the whole image" calculation: these diagrams are all portrait (taller + than wide) while browser viewports are landscape, so a height term is almost always + the binding constraint and produces an overlay image *narrower* than the already + size-squeezed inline column — exactly backwards from the point of clicking in. + Double-click and the Reset button return to this same fitted scale, not literal 1:1. +- The `naturalW`/`naturalH` this is computed from is the image's real intrinsic + resolution, not the CSS-squeezed inline size and not `.naturalWidth` either. + Every diagram SVG on this site has a `viewBox` but no `width`/`height` attribute, so + `.naturalWidth` reports the CSS default-object-size fallback (~300px) rather than + the SVG's actual dimensions — the plugin fetches the SVG and reads its `width`/`height` + or `viewBox` directly instead of trusting `naturalWidth`. +- The component itself renders no markup — it only registers a component so Quartz + collects its CSS and client script (see `componentResources.ts` in Quartz core) and + is declared with `layout.position: afterBody`, which is part of Quartz's *shared* + layout and so applies to every page type, not just `content` pages. +- The click handler is registered on `document` in the capture phase specifically so it + runs before Quartz's SPA router's click listener on `window` — otherwise a click on + the image's wrapping `` would be treated as page + navigation to the raw `.svg` file. The overlay element is re-appended to `document.body` + on Quartz's `nav` event, since Quartz's SPA router (`micromorph`) diffs and can strip + dynamically-added body elements not present in the freshly-fetched page. +- The script ships as `beforeDOMLoaded`, not `afterDOMLoaded`, on purpose. In a + production build Quartz code-splits every component's `afterDOMLoaded` script into + its own hashed file and loads them all in parallel via dynamic `import()`, awaited as + a group before the SPA router itself initializes. That leaves a real window — narrow, + but observed in practice on the deployed site — where a user can click a diagram + before that chunk has loaded and registered its click interceptor, so the click falls + through to the SPA router uncontested and silently navigates to the raw SVG. + `beforeDOMLoaded` scripts are always bundled into one single, blocking `prescript.js` + loaded synchronously in `` before the body is even parsed, so registering the + interceptor there guarantees it exists before anything on the page is clickable at + all. The overlay DOM itself is still built lazily, on the first real open, which is + safe since a real click implies the body has already rendered. +- The backdrop scrim is a fixed dark color rather than derived from `--light`/`--dark`: + those are foreground/background *role* tokens that swap literal colors between light + and dark mode (in dark mode `--dark` is a near-white text color), so using either for + a dimming backdrop would invert in dark mode. Toolbar buttons and hint text do use + the theme variables and correctly adapt. + +## Quartz behaviour worth knowing (pinned commit `97a2d05`, checked 2026-10-06) + +- Layout `exclude` lists match a plugin's full `source` string (`@quartz-community/article-title`), not its short name. +- The explorer builds its tree in the browser from the content index, which carries only slug, title, links, tags, and text. It can be ordered only by a `sortFn` sent to the browser as text. Upstream requests for frontmatter ordering (quartz#2125, #2363) were closed at the v5 release. This is why `navigation.yaml` and our own component replace it. +- `folder-page` always appends a listing of the folder's pages, with no option to turn it off; the CSS hides it. Turning `folder-page` off would stop folder `index.md` pages from rendering at all. +- `markdownLinkResolution: relative` resolves `./`, `../`, `folder/index.md` and figure links as written in `site/content/`. +- `remove-draft` acts only on `draft: true`. +- Local plugins are symlinked into the checkout, so their bare imports resolve from their real path. `build.sh` links Quartz's `preact`, `@quartz-community/utils` and `yaml` into them. +- The Giscus `comments` plugin passes no `data-term`, so Giscus's `specific` mapping is unavailable. diff --git a/site/ROADMAP.md b/site/ROADMAP.md new file mode 100644 index 0000000..5de077f --- /dev/null +++ b/site/ROADMAP.md @@ -0,0 +1,45 @@ +# Roadmap + +What is planned for the website, and where each page and figure stands. The current task is in [CURRENT_TASK.md](CURRENT_TASK.md). + +`site/content/` is the website's content: Markdown pages and the SVG figures they embed. It is not an OKF bundle. A page carries `title` and `description`, and the `verified` stamp once the project lead has reviewed it; citations are footnotes in the text. The knowledge graph in `knowledge/` is a separate track; nothing here waits on it. + +## Now: build the public site from `site/content/` + +Built and on dev (https://oidm-knowledge-dev.talkasab.workers.dev), awaiting the project lead's look and the commit; see [CURRENT_TASK.md](CURRENT_TASK.md). Production stays closed until every page carries his `verified` stamp (`task site:verify`). + +## Then: page comments (proposed) + +Giscus on all three sites, one Discussions category per environment so every comment shows where it came from. Proposed 2026-10-08, awaiting the project lead's yes; details in [CURRENT_TASK.md](CURRENT_TASK.md). + +## Then: the remaining pages + +Standard clinical metadata next (text proposed, not agreed; it should carry the 2026-09-30 thought that time course and etiology may be properties of the edge to a diagnosis), then the rest of Foundation Context, then the other pillars. The agreed layout is the 2026-09-22 layout plan; earlier drafts with reviewed source material are at `knowledge/drafts/` in commit `9725105`. The agreed pages already link to seven of the unwritten ones: the four other pillar indexes, anatomic locations, exam types, standards. + +## Pages + +| Page | Status | File | +|---|---|---| +| Overview | agreed, reviewed; two-planes figure embedded; pillars stack awaits accept | `content/index.md` | +| Foundation Context | agreed; three-axes and mini-network embedded | `content/foundation-context/index.md` | +| Finding Models and Common Data Elements (hub) | agreed, reviewed | `content/foundation-context/finding-models-and-cdes.md` | +| The next-generation schema | agreed, reviewed; dated CDE figure embedded | `content/foundation-context/next-generation-schema.md` | +| Relationships among definitions | agreed, reviewed | `content/foundation-context/relationships.md` | +| Standard clinical metadata | text proposed, not agreed | not written | + +## Figures + +| Figure | For | Status | +|---|---|---| +| Two planes v10 | Overview | accepted 2026-09-29; embedded | +| Three axes v6 | Foundation Context | embedded; awaits accept | +| Foundation mini-network v8 | Foundation Context | embedded; awaits accept | +| Pillars stack v8 (B3) | Overview | candidate; awaits accept | +| Two collections v1 | hub | candidate; awaits accept | +| Pulmonary nodule neighborhood v3 | Relationships; possibly replaces the dated figure on the schema page | candidate; awaits accept and placement | + +Candidate SVGs are in ignored `scratch/review/candidates/`; on acceptance, the figure config points the figure at its page. + +## Working rules + +Page text is agreed with the project lead line by line and written by an Opus sub-agent that does not reword it. Organizations only; nothing called "the specification"; dated versions side by side. Figures through `tools/diagrams/`, looked at before reporting. Every tool or approach choice reviewed with the project lead first. Commits only on his explicit yes. diff --git a/site/Taskfile.yml b/site/Taskfile.yml new file mode 100644 index 0000000..2573836 --- /dev/null +++ b/site/Taskfile.yml @@ -0,0 +1,67 @@ +# Site tasks are independent of the knowledge graph's validators. +version: "3" + +vars: + SITE_REPO_ROOT: '{{.TASKFILE_DIR}}/..' + DEV_URL: '{{.DEV_URL | default "oidm-knowledge-dev.talkasab.workers.dev"}}' + STAGING_URL: '{{.STAGING_URL | default "oidm-knowledge-staging.talkasab.workers.dev"}}' + PROD_URL: '{{.PROD_URL | default "oidm-knowledge.talkasab.workers.dev"}}' + +tasks: + check: + desc: Check site pages, links, and navigation + dir: '{{.SITE_REPO_ROOT}}' + cmds: + - uv run site/tools/check_site.py {{.CLI_ARGS}} + build: + desc: Build the draft dev site into public/ + dir: '{{.SITE_REPO_ROOT}}' + cmds: + - bash site/build.sh {{.CLI_ARGS}} + serve: + desc: Serve the draft site with live reload + dir: '{{.SITE_REPO_ROOT}}' + cmds: + - bash site/build.sh serve {{.CLI_ARGS}} + preview: + desc: Build and preview through Cloudflare's local static-asset server + dir: '{{.SITE_REPO_ROOT}}' + cmds: + - bash site/build.sh + - npx wrangler dev --config site/wrangler.jsonc --env dev + verify: + desc: 'Stamp reviewed pages: verify -- site/content/path.md [...]' + dir: '{{.SITE_REPO_ROOT}}' + cmds: + - uv run site/tools/verify.py {{.CLI_ARGS}} + review-status: + desc: Show verified / total pages by top-level directory + dir: '{{.SITE_REPO_ROOT}}' + cmds: + - uv run site/tools/verify.py --status + review-list: + desc: 'List unverified pages: review-list -- site/content/foundation-context' + dir: '{{.SITE_REPO_ROOT}}' + cmds: + - uv run site/tools/verify.py --list {{.CLI_ARGS | default "site/content"}} + deploy: + desc: Build and deploy the draft dev site + dir: '{{.SITE_REPO_ROOT}}' + deps: [check] + cmds: + - SITE_BASE_URL='{{.DEV_URL}}' bash site/build.sh --target dev + - npx wrangler deploy --config site/wrangler.jsonc --env dev + deploy:staging: + desc: Build and deploy the draft staging site + dir: '{{.SITE_REPO_ROOT}}' + deps: [check] + cmds: + - SITE_BASE_URL='{{.STAGING_URL}}' bash site/build.sh --target staging + - npx wrangler deploy --config site/wrangler.jsonc --env staging + deploy:prod: + desc: Build and deploy reviewed pages to the public site + dir: '{{.SITE_REPO_ROOT}}' + deps: [check] + cmds: + - SITE_BASE_URL='{{.PROD_URL}}' bash site/build.sh --target production + - npx wrangler deploy --config site/wrangler.jsonc diff --git a/site/build.sh b/site/build.sh index 1a0d190..efacb85 100755 --- a/site/build.sh +++ b/site/build.sh @@ -1,67 +1,111 @@ #!/usr/bin/env bash -# Build the OIDM knowledge Quartz site. +# Build the public OIDM knowledge site from site/content/. # -# Quartz itself is NOT vendored in this repository. This script clones the -# pinned Quartz commit into site/.quartz-src (gitignored), points it at our -# config and plugin, and builds knowledge/ into public/ at the repo root. +# Quartz is not vendored here. This script checks the pages first, clones +# the pinned checkout into site/.quartz-src (gitignored), installs our config +# and local plugins, copies shared brand assets, and builds into public/. # -# Usage (from the repository root): -# bash site/build.sh # one-shot production build -> public/ -# bash site/build.sh serve # local dev server with live reload +# Usage (from any working directory): +# bash site/build.sh [--target dev|staging|production] [serve] +# SITE_BASE_URL=example.com/path bash site/build.sh --target staging # -# See site/README.md for details and how to bump the pinned commit. +# Dev is the default draft target. Production requires every page's review +# stamp. See site/README.md for the build and pinned-commit update procedure. set -euo pipefail QUARTZ_REPO="https://github.com/jackyzha0/quartz.git" QUARTZ_SHA="97a2d05f80c4c50534959b1d0d41cc4b3895625e" - -MODE="${1:-build}" - +TARGET=dev +MODE=build +while [ "$#" -gt 0 ]; do + case "$1" in + --target) + if [ "$#" -lt 2 ]; then echo '--target requires dev, staging, or production' >&2; exit 2; fi + TARGET="$2"; shift 2 ;; + serve) MODE=serve; shift ;; + *) echo "Unknown argument: $1" >&2; exit 2 ;; + esac +done +case "$TARGET" in + dev) DEFAULT_URL=oidm-knowledge-dev.talkasab.workers.dev ;; + staging) DEFAULT_URL=oidm-knowledge-staging.talkasab.workers.dev ;; + production) DEFAULT_URL=oidm-knowledge.talkasab.workers.dev ;; + *) echo "Invalid target: $TARGET" >&2; exit 2 ;; +esac +export SITE_TARGET="$TARGET" SITE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SITE_DIR/.." && pwd)" QUARTZ_SRC="$SITE_DIR/.quartz-src" -BUNDLE_DIR="$REPO_ROOT/knowledge" -CONTENT_DIR="${TMPDIR:-/tmp}/oidm-knowledge-staged-$$" # normalized copy of the bundle outside the repo (Quartz skips gitignored paths); see tools/prepare_site_content.py -trap 'rm -rf "$CONTENT_DIR"' EXIT +CONTENT_DIR="$SITE_DIR/content" OUTPUT_DIR="$REPO_ROOT/public" +# Components render in Node. An absolute path survives the checkout's cwd +# and local-plugin symlinks, and is never shipped as a browser dependency. +export SITE_NAVIGATION_FILE="$SITE_DIR/navigation.yaml" + +echo "==> Checking site ($TARGET)" +uv run "$SITE_DIR/tools/check_site.py" --target "$TARGET" echo "==> Quartz checkout" if [ ! -d "$QUARTZ_SRC/.git" ]; then echo "Cloning Quartz into ${QUARTZ_SRC#"$REPO_ROOT"/} ..." git clone --quiet "$QUARTZ_REPO" "$QUARTZ_SRC" fi -git -C "$QUARTZ_SRC" fetch --quiet origin "$QUARTZ_SHA" || true +git -C "$QUARTZ_SRC" fetch --quiet origin "$QUARTZ_SHA" git -C "$QUARTZ_SRC" checkout --quiet "$QUARTZ_SHA" -echo "==> Copying site/quartz.config.yaml into the checkout" -cp "$SITE_DIR/quartz.config.yaml" "$QUARTZ_SRC/quartz.config.yaml" +CONFIG_FILE="${QUARTZ_CONFIG:-$SITE_DIR/quartz.config.yaml}" +echo "==> Copying ${CONFIG_FILE#"$REPO_ROOT"/} into the checkout" +cp "$CONFIG_FILE" "$QUARTZ_SRC/quartz.config.yaml" +# Override only the checkout's config; the tracked baseUrl stays unchanged. +export SITE_BASE_URL="${SITE_BASE_URL:-$DEFAULT_URL}" +uv run --with pyyaml python - "$QUARTZ_SRC/quartz.config.yaml" <<'PY' +import os,re,sys,yaml +from pathlib import Path +path=Path(sys.argv[1]) +quoted=yaml.safe_dump(os.environ['SITE_BASE_URL'],default_style='"').strip() +path.write_text(re.sub(r'^(\s*)baseUrl:.*$',lambda m: m[1]+'baseUrl: '+quoted,path.read_text(),flags=re.M)) +PY echo "==> npm ci" ( cd "$QUARTZ_SRC" && npm ci ) -echo "==> Linking preact into the plugin's own node_modules" -# The okf-meta-plugin package is symlinked in place by `quartz plugin -# install`, not copied. Node resolves module specifiers against a symlinked -# file's REAL path, so a bare "preact" import inside the plugin does not see -# Quartz's own node_modules even though the plugin is declared right next to -# it in quartz.config.yaml. Symlinking preact into the plugin's own -# node_modules/ fixes the "Cannot find package 'preact'" resolution error -# without vendoring a copy of preact in this repo. -PREACT_SRC="$QUARTZ_SRC/node_modules/preact" -PLUGIN_NODE_MODULES="$SITE_DIR/okf-meta-plugin/node_modules" -if [ -d "$PREACT_SRC" ] && [ ! -e "$PLUGIN_NODE_MODULES/preact" ]; then - mkdir -p "$PLUGIN_NODE_MODULES" - ln -s "$PREACT_SRC" "$PLUGIN_NODE_MODULES/preact" -fi +echo "==> Linking shared dependencies into local plugins" +# Local plugins are symlinked, so bare imports resolve from their real paths +# outside Quartz's node_modules. Share Quartz's installed dependencies rather +# than installing or bundling extra copies of preact and the path utilities. +for PLUGIN in "$SITE_DIR"/plugins/*; do + if node --input-type=commonjs -e 'process.exit(require(process.argv[1]).peerDependencies?.preact ? 0 : 1)' "$PLUGIN/package.json"; then + mkdir -p "$PLUGIN/node_modules" + ln -sfn "$QUARTZ_SRC/node_modules/preact" "$PLUGIN/node_modules/preact" + fi +done +mkdir -p "$SITE_DIR/plugins/site-chrome/node_modules/@quartz-community" +ln -sfn "$QUARTZ_SRC/node_modules/@quartz-community/utils" "$SITE_DIR/plugins/site-chrome/node_modules/@quartz-community/utils" +ln -sfn "$QUARTZ_SRC/node_modules/yaml" "$SITE_DIR/plugins/site-chrome/node_modules/yaml" echo "==> Installing plugins declared in quartz.config.yaml" +# The plugin move leaves dangling symlinks in older local checkouts. Quartz's +# index generator stats all of them, so discard only broken plugin links. +python3 - "$QUARTZ_SRC/.quartz/plugins" <<'PY' +from pathlib import Path +import sys +for path in Path(sys.argv[1]).glob('*'): + if path.is_symlink() and not path.exists(): + path.unlink() +PY ( cd "$QUARTZ_SRC" && npx quartz plugin install --from-config ) -echo "==> Staging bundle with normalized links" -python3 "$REPO_ROOT/tools/prepare_site_content.py" --src knowledge --dest "$CONTENT_DIR" +echo "==> Copying brand assets" +# Restore tracked static files before replacing the icon so repeated builds +# start from Quartz's pinned assets, rather than a previous build's copies. +git -C "$QUARTZ_SRC" checkout -- quartz/static +mkdir -p "$QUARTZ_SRC/quartz/static/brand" +cp "$REPO_ROOT/assets/brand/oidm/logo-color.svg" "$QUARTZ_SRC/quartz/static/brand/logo-color.svg" +cp "$REPO_ROOT/assets/brand/oidm/logo-white.svg" "$QUARTZ_SRC/quartz/static/brand/logo-white.svg" +cp "$REPO_ROOT/assets/brand/oidm/mark.png" "$QUARTZ_SRC/quartz/static/icon.png" echo "==> Building" -if [ "$MODE" = "serve" ]; then +if [ "$MODE" = serve ]; then ( cd "$QUARTZ_SRC" && exec npx quartz build --serve -d "$CONTENT_DIR" -o "$OUTPUT_DIR" ) else ( cd "$QUARTZ_SRC" && npx quartz build -d "$CONTENT_DIR" -o "$OUTPUT_DIR" ) diff --git a/site/content/data-structures/index.md b/site/content/data-structures/index.md new file mode 100644 index 0000000..0f7913b --- /dev/null +++ b/site/content/data-structures/index.md @@ -0,0 +1,10 @@ +--- +title: "Data Structures" +description: "The patient-side structures: the Observation, the Exam Finding List, the Imaging Problem List, and the Imaging Persona, and how the patient graph and the Foundation Context graph work together. These structures are still coalescing, so each source's current version will be shown side by side, dated." +--- + +# Data Structures + +The patient-side structures: the Observation, the Exam Finding List, the Imaging Problem List, and the Imaging Persona, and how the patient graph and the Foundation Context graph work together. These structures are still coalescing, so each source's current version will be shown side by side, dated. + +This page is coming. diff --git a/site/content/foundation-context/anatomic-locations.md b/site/content/foundation-context/anatomic-locations.md new file mode 100644 index 0000000..133727e --- /dev/null +++ b/site/content/foundation-context/anatomic-locations.md @@ -0,0 +1,10 @@ +--- +title: "Anatomic Locations" +description: "The curated index of anatomic locations anchored in RadLex: the existing work, its containment, part-of, and laterality relationships, and its incorporation into RadLex itself." +--- + +# Anatomic Locations + +The curated index of anatomic locations anchored in RadLex: the existing work, its containment, part-of, and laterality relationships, and its incorporation into RadLex itself. + +This page is coming. diff --git a/site/content/foundation-context/exam-types.md b/site/content/foundation-context/exam-types.md new file mode 100644 index 0000000..94d8d12 --- /dev/null +++ b/site/content/foundation-context/exam-types.md @@ -0,0 +1,10 @@ +--- +title: "Exam Types" +description: "The exam-type content to come: preferred high-level exam types such as CT Chest, MRI Brain, and X-ray Knee over the LOINC/RSNA Radiology Playbook, each tied to the anatomy it always, usually, or possibly includes." +--- + +# Exam Types + +The exam-type content to come: preferred high-level exam types such as CT Chest, MRI Brain, and X-ray Knee over the LOINC/RSNA Radiology Playbook, each tied to the anatomy it always, usually, or possibly includes. + +This page is coming. diff --git a/site/content/foundation-context/finding-models-and-cdes.md b/site/content/foundation-context/finding-models-and-cdes.md new file mode 100644 index 0000000..fd82585 --- /dev/null +++ b/site/content/foundation-context/finding-models-and-cdes.md @@ -0,0 +1,38 @@ +--- +title: "Finding Models and Common Data Elements" +description: "Definitions of findings and diagnoses as the anchor for everything the project knows about them, held in two cross-linked collections: the beta channel and the release channel." +--- + +# Finding Models and Common Data Elements + +Definitions of findings and diagnoses anchor these concepts in a form that associated content can be attached to: a name and synonyms, a definition, the attributes that characterize it, the anatomy it applies to, the modalities that show it, its codes in outside terminologies, and its relationships to other definitions.[^lead-hub][^oifm-repo][^cde-nextgen] Everything the project knows about a kind of finding hangs off this one anchor.[^lead-hub] The project holds these definitions in two collections, and they are the same kind of content.[^lead-2026-09-22] + +**Open Imaging Finding Models** are an inclusive collection, maintained by the OIDM project.[^lead-hub][^oifm-repo] The OIFM repository is intended for rapid, community-based prototyping and iteration based on an open-source model, with changes proposed by anyone.[^lead-hub] A definition can start as little more than a name and a stub and be improved over time.[^lead-2026-09-22][^oifm-authoring] The collection holds a few thousand definitions today, drawn from several sources, and is being reorganized around exam-oriented taxonomies of the findings that actually appear in reports.[^oifm-repo][^build-plan][^oifm-taxonomies] + +**ACR/RSNA Common Data Elements** are the well-reviewed collection, maintained by the American College of Radiology and the Radiological Society of North America and published through radelement.org.[^lead-hub][^radelement] Entry is a review step, and the project treats the finding models as the workbench where definitions are proved out before that review.[^cde-nextgen][^cde-staging][^status-2026-01][^siim-2026] + +**The relationship.** The two collections describe the same ideas and will share the same schema.[^lead-hub][^lead-2026-09-22] The difference is how they are released: finding models are the beta channel, with rapid additions and updates; Common Data Elements are the release channel, reviewed and stable.[^lead-hub] The two are cross-linked and stay that way: when a definition migrates into the CDEs, it continues to be maintained in the OIFM repository and carries a reference to its CDE identity, so an application can resolve either one to the same concept.[^lead-hub][^oifm-cde-def] About a hundred finding models were derived directly from CDEs.[^oifm-repo][^oifm-cde-def] Coverage is measured the same way on both sides, by running real reports against the definitions and recording what they could not represent.[^lead-hub][^cde-staging] + +**Where both are going.** The ACR/RSNA CDE project is developing a next-generation, graph-based schema, and both collections are intended to move onto it, so that applications use them together.[^cde-nextgen][^lead-hub] Both CDEs and OIFMs are document-oriented today, and moving them onto that graph is an immediate priority.[^lead-hub][^lead-2026-09-22][^oifm-repo][^cde-schema][^cde-nextgen] + +# In this section + +- OIFM content: what the inclusive collection holds and where it is going +- Definition formats: what a finding model and a CDE look like today +- Identifiers: the OIFM scheme, RadElement identifiers, and index codes +- The next-generation schema, with relationships and standard clinical metadata +- Authoring and review + +[^lead-hub]: The project lead, 2026-09-23 to 2026-09-29, recorded verbatim in the layout plan: the finding/diagnosis definition as "an unified anchor for the CONCEPT of a finding/diagnosis in a form that can be used to attach associated context: name, synonyms, definition, attributes, etc."; the inclusive collection "maintained by the OIDM project" and "intended for rapid, community-based prototyping and iteration based on an open-source model ... with changes proposed by anyone"; the well-reviewed collection "published through radelement.org"; "they will have the same schema and describe the same ideas", with OIFMs as "beta"/rapid additions and updates and CDEs as "release"/reviewed and stable; the maintained cross-links, where a definition that migrates "will continue to be maintained in the OIFM repo but have a reference to its CDE identity"; "Both CDEs and OIFMs are document-oriented today"; and, on 2026-09-23, one next-generation schema "which will be for BOTH CDEs and OIFMs--I think applications will be able to use both at once". +[^lead-2026-09-22]: The project lead, 2026-09-22, quoted in the restructure joint notes: "A 'CDE' and a 'Finding Model' are the same thing, but they're in different repos", the OIDM-maintained collection "intended to be very INCLUSIVE, can almost be drafts" against the ACR/RSNA collection "intended to be WELL-REVIEWED ... more like something published", both to use the classes being defined in the CDE schema rewrite, and "OIFMs VERY soon need to get re-organized around the graph-based approach ... They're currently document-oriented and we need to fix that SOON." +[^build-plan]: Knowledgebase build plan: the project lead's content direction of 2026-09-21, that the taxonomy branch in the finding models repository is "the immediate direction for all of the content, probably replacing a lot of the Gamuts-based models". +[^oifm-repo]: Open Imaging Finding Models repository, main at 4475ac1, a snapshot of 2026-04-28. The README describes finding models as updatable data models defining open, standard semantic tags for imaging findings and their properties, with relationships to other ontologies; the schema document gives the definition fields (identifier, name, description, synonyms, tags, contributors, attributes, index codes into standard ontologies); `ids.json` holds 2,382 definitions across five organization segments (1,933 GMTS, 256 OIDM, 115 CDE, 47 MGB, 31 MSFT) and 5,709 attribute identifiers. The 115 under the CDE segment are the models derived from Common Data Elements. The January 2026 status update gives the collection as "nearly 3,000". For the document orientation of the collection today: the schema is a JSON Schema for a single self-contained finding model, and the corpus is 2,382 individual files under `defs/`, one per definition. +[^oifm-authoring]: The finding model authoring scripts and prompt fragments, findingmodels main at 4475ac1. `scripts/finding_authoring/create_model.py` creates "a complete finding model stub with IDs and codes", building it from a name and a description with the two standard attributes, then minting identifiers against the index; the single-model path requires a description, so a stub is not name-only. `prompts/fragments/enrichment.md` is the optional later step: it applies where "The existing description is a one-line placeholder and you want a clinically-grounded starting point", drafts a richer description with citations, and writes the reviewed result back through `fix_stub.py`, after which the normal review flow runs again. +[^oifm-cde-def]: Acute Aortic Syndrome, `defs/acute_aortic_syndrome.fm.json` at findingmodels main 4475ac1: `oifm_id` `OIFM_CDE_000126`, contributor "ACR/RSNA Common Data Elements Project", and an index code `system: RADELEMENT`, `code: RDES126` beside its RadLex code. All 115 definitions under the CDE organization segment carry a RadElement index code in the same way. This supports the retained CDE identity and the derivation of those models from Common Data Elements. It is evidence of the reference as it exists today in the direction CDE to finding model; the commitment to maintain the cross-links after a future migration, and to resolution of either identity to the same concept by an application, rests on the project lead's direction. +[^oifm-taxonomies]: The six per-exam finding taxonomies, findingmodels taxonomy-export-2026-08-15 at a30c3c9: chest radiograph, musculoskeletal radiograph, head CT, chest/abdomen/pelvis CT, mammography, and spine MRI, 3,789 rows in all. Each file is one hierarchy in which "Parents are generic, children add specificity", and `finding_type` separates an observation from a diagnosis. The files double as a coverage ledger: an identifier is filled only where a row matched a model that already exists, 1,028 of 3,789 rows, and "Blank rows are the ones needing triage." +[^radelement]: The published ACR/RSNA Common Data Element collection at radelement.org. Its public API returns sets attributed to the American College of Radiology and the Radiological Society of North America. The staging repository and the next-generation schema baseline both name it as the joint ACR/RSNA project's published corpus, public API, and vendor-facing interface. +[^cde-nextgen]: "Next-Generation CDE Schema: Current Understanding", ACR-RSNA-CDEs next-gen-2026 at b541b74, a draft for discussion dated 2026-08-19. §2.2: "The authoritative internal representation is a knowledge graph, not a document tree", following the committee's own move to "a graph model, where findings point to canonical attribute objects". §5.1: the finding model work "has independently built much of this and is roughly one iteration ahead", so "OIFMs can be treated as drafts for this project's FindingClasses", with the caveat that "Bringing models over is a review step, not a bulk import"; its table of what the finding models already answer includes worked metadata vocabularies for body regions, modalities, etiologies, sex specificity, age profile, and expected time course. Modality applicability is a field of the current CDE schema as well. §5.1 also discusses the finding models' file-based design directly, noting that it "makes choices that a graph makes differently". +[^cde-schema]: The current Common Data Element schema, `cde.schema.json` at ACR-RSNA-CDEs next-gen-2026 b541b74, last changed 2024-11-19: a draft-07 JSON Schema whose root is `element_set`, a nested document whose required properties include an `elements` array. This is the document form the collection takes today. +[^cde-staging]: CDE staging repository, main at b814a10: "a staging area for definitions of radiology common data elements in JSON format ... prior to their entering the review pipeline". The manual extraction process works one real pulmonary angiogram report into a list of mini-observations, each a finding with a presence value and its attributes. Three catalogs beside it record what the definitions could not carry. `negative_statements.md` groups real report sentences by body region that assert absence across several structures at once, some bundled with a technique caveat, with no definition links. `uncovered_findings.md` lists findings with their reported attributes and no definition behind them, also with no links. `missing_attributes.md` is the one that links: ten entries, each naming the finding and the attribute the report used and pointing at the definition file that lacked it. The catalogs exist as data; the method is described but not automated, and the same coverage question is asked prospectively as a frequency-and-priority table in the chest CT roadmap. This is evidence of the CDE-side measurement only; that coverage is measured the same way on the finding models side rests on the project lead's direction. +[^status-2026-01]: Open Imaging Data Model 2026 Status Update, January 2026: "OIFM: the CDE workbench" - rapid innovation and a proving ground before ACR/RSNA CDE adoption, with exploratory metadata "that can graduate to standards", named in the executive summary as the "workbench effect". Read from the project's transcription of the deck, `sources/gamma-2026-status-update.md`, which records the original presentation URL and the month but no modification date. +[^siim-2026]: SIIM 2026 annual meeting talk, June 2026, Mass General Brigham, slide 9 notes: "OIFM Finding Models today, ACR/RSNA CDEs next ... OIFM is the high-velocity 'workbench' that proves out content before formal CDE adoption." Read from the project's text extraction of the deck, `sources/bucket/text/siim2026-reports-of-the-future.md`; the presentation file records a modification date of 2026-06-11. diff --git a/site/content/foundation-context/foundation-network.svg b/site/content/foundation-context/foundation-network.svg new file mode 100644 index 0000000..3fed815 --- /dev/null +++ b/site/content/foundation-context/foundation-network.svg @@ -0,0 +1,2 @@ +Foundation Context: a mini-networkthree sectors of shared, curated knowledge — illustrative, not exhaustiveSector 1 — Finding / diagnosis definitionsSector 2 — Anatomic locationsSector 3 — Exam types(content to come)Legendlung cancerpulmonarynoduleLung-RADSpleural effusionsolidpulmonarynodulepart-solidpulmonarynodulenon-solidpulmonarynodulesolid componentsubtype ofsubtype ofsubtype ofhas componentmay manifest asmay progress toassessed bythoraxpleuralspaceupper abdomenright lunglungleft lungupper lobe ofright lunglungparenchymacontained bylateralitypart ofcontained byCT Chestpreferred (to come)CT Chest WO contrast29252-4CT Chest W contrast IV24628-0CT Chest WO and W contrast IV30598-7CTA Chest vesselsWO and W contrast IV30804-9CT Thoracic spineW contrast IV24979-7Region imaged:Thorax (RID1243)member of familyPlaybook partscoped toscoped toscoped tocoversincludededge (usually)seen onpossibly seen onfindingdiagnosisassessment schemeanatomic locationexam typeunlabeled grey = further nodes not shownsolid edge = within-sector relationshipcolored thick edge = cross-sector relationshipdashed = possible or planned \ No newline at end of file diff --git a/site/content/foundation-context/index.md b/site/content/foundation-context/index.md new file mode 100644 index 0000000..8831dea --- /dev/null +++ b/site/content/foundation-context/index.md @@ -0,0 +1,37 @@ +--- +title: "Foundation Context" +description: "The shared, curated clinical knowledge that every patient's imaging data is woven into: three axes over existing standards, the relationships among them, and the citations they rest on." +--- + +# Foundation Context + +Foundation Context is the shared, curated knowledge that every patient's imaging data is woven into. It is one layer for everyone: definitions of what can be found, where things are in the body, how exams see them, the relationships among all of these, and citations out to the standards and references the definitions rest on. It is open-source, general clinical knowledge, authored by the imaging informatics community in the open. It covers both the schema that definitions take and the work of building the content.[^siim-2026][^lead-2026-09-24] + +It has three axes. Each is a curated layer over a standard that already exists, adding imaging-specific knowledge rather than reinventing it.[^siim-2026] + +[![The three axes of Foundation Context: finding/diagnosis definitions, anatomic locations, exam types, each layered over an existing standard](./three-axes.svg)](./three-axes.svg) + +**Finding/diagnosis definitions: what was found.** One kind of content, held today in two collections: the Open Imaging Finding Models, inclusive and fast-moving, and the ACR/RSNA Common Data Elements, well-reviewed and closer to published. Both are moving onto a single graph-based schema being developed in the CDE project, which applications will use across both collections at once.[^lead-2026-09-24] + +**Anatomic locations: where it is.** A curated index of anatomic entities anchored in RadLex, with containment, part-of, and laterality made explicit, now being incorporated into RadLex itself.[^build-plan][^siim-2026] + +**Exam types: how it was seen.** Content to come: preferred exam families over the LOINC/RSNA Radiology Playbook, each connected to the anatomy it covers.[^build-plan][^siim-2026] + +The axes are not independent. A definition says which anatomy it applies to and which modalities can show it. An exam type says which anatomy it covers. Definitions relate to one another as subtype and supertype, cause and effect, diagnosis and the findings it manifests as, things confused with each other, things that occur together. These relationships, with the citations out to Radiopaedia, Wikipedia, SNOMED CT, RadLex, LOINC, FMA, ICD, and CPT, are what turn a flat dictionary into something an application can reason over.[^siim-2026] + +[![A mini-network of Foundation Context: finding/diagnosis definitions, anatomic locations, and exam types, with relationships within and between them](./foundation-network.svg)](./foundation-network.svg) + +(click the image for full size) + +# In this section + +- Finding/diagnosis definitions: finding models and CDEs, with the OIFM content, the definition formats, and the identifiers +- The next-generation schema, with the relationship family and standard clinical metadata +- Authoring and review +- [Anatomic locations](./anatomic-locations.md) +- [Exam types](./exam-types.md) +- [Standards the foundation layers over and cites](./standards.md) + +[^lead-2026-09-24]: The project lead, 2026-09-22 to 2026-09-24: the two collections ruling, the shared next-generation schema for both, the RadLex migration, and the wording of the opening paragraph; recorded verbatim in the layout plan. +[^siim-2026]: SIIM 2026 annual meeting talk, June 2026, Mass General Brigham; slides 6, 7, 8, 10, 11, and 12. +[^build-plan]: Knowledgebase build plan: the project lead's stated goals on anatomic locations and exam types (2026-09-20). diff --git a/site/content/foundation-context/next-gen-findingclass-neighborhood.svg b/site/content/foundation-context/next-gen-findingclass-neighborhood.svg new file mode 100644 index 0000000..1038928 --- /dev/null +++ b/site/content/foundation-context/next-gen-findingclass-neighborhood.svg @@ -0,0 +1 @@ +FINDING CLASSpulmonary noduleA rounded or oval opacity within the lung parenchyma measuring less than 30 mm.synonyms: lung nodule · SPN (abbreviation) · pulmonary node (deprecated)RDE2_000123 · v1 · proposed · 2026-08-19entity_type: findingSUBTYPE_OF — is anoduleFindingClassSCOPED_TO — anatomic scopelungAnatomicLocation · RID1301structure · requiredHAS_ELEMENT — characterized by · ×4presence→ existing element RDE2_000001 · shared · categoricalpresentpossibleabsentindeterminateunknownattenuationdefined here as RDE2_000015 · DataElement · categoricalAttenuation class of the nodule on CT, per Fleischner usage.drives managementsolidpart-solidground-glasssize (mean diameter)→ existing element RDE2_000014 · shared · lengthmargindefined here as RDE2_000031 · DataElement · categoricalCharacter of the nodule's interface with surrounding lung.smoothlobulatedspiculatedill-definedMAY_REPRESENT — may representlung cancerFindingClass · diagnosisASSESSED_BY — assessed byFleischner recommendationFindingClass · assessment - incidentalLung-RADS categoryFindingClass · assessment - screeningMAY_HAVE_COMPONENT — may have componentsolid componentFindingClass · finding - sub-finding of a part-solid nodule, with its own sizeCONTEXT — discoverability edgesneoplastic : benignHAS_ETIOLOGY · Etiologyneoplastic : malignantHAS_ETIOLOGY · Etiologyinflammatory : infectiousHAS_ETIOLOGY · EtiologycongenitalHAS_ETIOLOGY · EtiologyCTSEEN_ON · ModalityXRSEEN_ON · ModalitychestIN_REGION · BodyRegionchest radiologyIN_SUBSPECIALTY · Subspecialtysex-neutralSEX · SexSpecificityall agesAGE_APPLICABILITY · AgeProfileyears - progressive or stableTIME_COURSE · ExpectedTimeCourseMAPPINGS — skos:exactMatch / closeMatchpulmonary noduleRADLEX RID50149 · skos:exactMatchSolitary nodule of lung (finding)SNOMEDCT 427359005 · skos:exactMatch diff --git a/site/content/foundation-context/next-generation-schema.md b/site/content/foundation-context/next-generation-schema.md new file mode 100644 index 0000000..09deade --- /dev/null +++ b/site/content/foundation-context/next-generation-schema.md @@ -0,0 +1,71 @@ +--- +title: "The next-generation schema" +description: "The graph-based schema being developed in the ACR/RSNA CDE project that both collections of finding/diagnosis definitions will move onto: its node types, its edges, and the relationships among definitions." +--- + +# The next-generation schema + +Both Open Imaging Finding Models and ACR/RSNA Common Data Elements are moving onto one schema, being developed in the [ACR/RSNA CDE schema repository](https://github.com/RSNA/ACR-RSNA-CDEs/tree/next-gen-2026) on its next-gen-2026 branch, as a graph of interconnected concepts rather than a set of documents.[^lead-schema][^lead-2026-09-22][^cde-understanding] Applications will be able to use either or both collections at once via a common SDK based on the new schema.[^lead-schema][^cde-libraries-plan] What follows is the design as it stands on that branch, dated to the commit read.[^cde-branch] It is working material, and it carries the project's own provenance labels: decisions the project lead made, and defaults an assistant chose to keep moving that remain unreviewed.[^cde-decisions] + +# Node types + +- **FindingClass** defines the properties of the items described in imaging reports.[^lead-schema][^cde-structures][^cde-requirements] +- **Diagnosis** is a separate, closely related node type, distinguished by a different set of possible relationships.[^lead-schema][^cde-structures][^cde-relationships] +- **Grouping** defines broad classes of findings/diagnoses such as "renal abnormality", usually intended to be used in the negative.[^lead-schema][^cde-decisions][^cde-structures] +- **DataElement** defines a categorical descriptor: a value set, ordered or unordered, whose values are themselves shared nodes.[^cde-decisions][^cde-structures] +- **Measurement** defines a quantitative descriptor by the kind of quantity, such as length or CT density, with the unit family following from the kind.[^lead-schema][^cde-decisions][^cde-alpha-graph] +- **AssessmentScheme** definitions lay out the systems, such as Lung-RADS, via which a constellation of other findings is evaluated.[^lead-schema][^cde-decisions][^cde-structures] +- **AnatomicLocation** nodes are the entries of the anatomic locations index, keyed by their RadLex identity.[^cde-anatomy][^cde-decisions] + +Beyond these, shared **Concept** nodes hold the standard clinical metadata a definition can carry: modality, body region, subspecialty, sex, age, time course, etiology.[^lead-schema][^cde-decisions][^cde-structures] And the anatomic scope property of each FindingClass, Diagnosis, Grouping, DataElement, and Measurement indicates the locations and/or structures in which they may be localized (e.g., "lung parenchyma" for a pulmonary nodule).[^lead-schema][^cde-decisions][^cde-alpha-graph][^cde-anatomy] + +# Edges + +Edges integrate the nodes into a rich fabric of interrelated concepts, enabling reliable, reproducible inference.[^lead-schema][^cde-understanding][^cde-alpha-readme] Edges bind FindingClass and Diagnosis definitions to their anatomic scope and to the DataElements and Measurements used to specify their attributes.[^lead-schema][^cde-alpha-graph][^cde-structures] The edges themselves may contain properties that provide additional detail about the relationships between nodes: a binding may, for example, restrict a shared DataElement to a subset of its values for one FindingClass without changing the element itself.[^lead-schema][^cde-decisions][^cde-structures] No element is required by any definition.[^cde-decisions][^cde-structures][^table-check] + +| Relationship | From → to | Example | +|---|---|---| +| SUBTYPE_OF | each of FindingClass, Diagnosis, Grouping and AnatomicLocation → a node of its own kind | part-solid pulmonary nodule → pulmonary nodule | +| HAS_COMPONENT | FindingClass → FindingClass | part-solid pulmonary nodule → solid component | +| MAY_MANIFEST_AS | Diagnosis → FindingClass or Diagnosis | acute pyelonephritis → striated nephrogram | +| MAY_CAUSE | FindingClass or Diagnosis → FindingClass or Diagnosis | urinary calculus → hydronephrosis | +| MAY_PROGRESS_TO | FindingClass or Diagnosis → FindingClass or Diagnosis | parapneumonic effusion → empyema | +| OCCURS_WITH | FindingClass or Diagnosis ↔ FindingClass or Diagnosis | pleural effusion ↔ atelectasis | +| ASSESSED_BY | FindingClass or Diagnosis → AssessmentScheme | pulmonary nodule → Lung-RADS | +| SCOPED_TO | FindingClass, Diagnosis or Grouping → AnatomicLocation | pulmonary nodule → lung | +| SEEN_ON | FindingClass, Diagnosis or Grouping → Modality concept | pulmonary nodule → CT | +| HAS_DATA_ELEMENT, HAS_MEASUREMENT | FindingClass or Diagnosis → DataElement or Measurement | pulmonary nodule → presence; pulmonary nodule → mean diameter | + +[![The pulmonary nodule FindingClass and the typed edges it gathers: its parent class, its anatomic scope, the data elements that characterize it, the diagnosis it may represent, the assessment schemes that apply, its component, and its standard clinical metadata. Rendered from the pulmonary nodule neighborhood example in the ACR/RSNA CDE schema repository, next-gen-2026 at b541b74.](./next-gen-findingclass-neighborhood.svg)](./next-gen-findingclass-neighborhood.svg) + +(click the image for full size) + +*The diagram dates from 2026-09-03 and predates the separation of Measurement and AssessmentScheme into their own node types, so it still renders Lung-RADS and mean diameter as a FindingClass and a DataElement, and still writes the older edge names `HAS_ELEMENT` and `MAY_HAVE_COMPONENT`.*[^cde-fc-diagram][^cde-fc-diagram-input] + +The full family, with each relationship's inverse, symmetry, and provenance, is on the relationships page.[^cde-relationships][^cde-graph] + +# Evolution of CDEs and Finding Models + +Both CDEs and existing Open Imaging Finding Models will have to migrate from their current JSON document-based organization to a new graph-oriented format, likely using RDF/OWL as an underlying storage format.[^lead-schema][^lead-2026-09-22][^cde-schema][^oifm-repo][^cde-decisions] More ergonomic data structures will also be developed for use in SDKs and via an API.[^lead-schema][^cde-decisions][^cde-libraries-plan] + +[^lead-schema]: The project lead, 2026-09-29 and 2026-09-30, recorded verbatim in the layout plan. On this page: "Both Open Imaging FInding Models and RSNA/ACR Common Data Elements are moving..."; "Show a link to the ACR/RSNA CDE schema repository, on the next-gen-2026 branch"; "...as a graph of interconnected concepts rather than a set of documents."; "Applications will be able to use either or both collections at once via a common SDK based on the new schema."; the node-type wording, with the instruction that "For most of these node class definitions, we want to use 'defines' rather than 'is a' (this is a very tricky but important semantic distinction)"; "'AssessmentScheme' definitions layout the systems such as Lung-RADS via which a constellation of other findings is evaluated."; "The anatomic scope property of each other node indicates the locations and/or structures in which they may be localized (e.g., 'lung parenchyma' for a pulmonary nodule)."; "Edges integrate the notes into a rich fabric of interrelated concepts, enabling reliable, reproducible inference."; "Edges bind FindingClass and Diagnosis definitions to their anatomic scope and to the DataElements and Measurements used to specify their attributes. The edges themselves may contain properties that provide additional detail about the relationships between nodes."; "Concept nodes as a note below the list, introducing the paragraph that also talks about anatomic scope."; "Use the actual table of relationship types as examples."; and the whole Evolution section. On Measurements, 2026-09-29: "Measurements don't have EXPLICIT units, they indicate what KIND of thing they are: length (with appropriate length units, which could be mm, cm, whatever), CT density (HU), or count (unitless), ratio (unitless), velocity (m/s, cm/s), volume (cc, mL, mm^3..., L)". Two sentences on this page rest on this statement for what they actually promise. The repository has a proposed design for typed libraries and a query API, but nothing in it states that one SDK will consume both collections together, which is the project lead's own commitment.[^cde-libraries-plan] The repository puts inference in scope and has a reasoner regression suite, but neither establishes that inference over the vocabulary is reliable and reproducible today, which is the project lead's claim for the design.[^cde-understanding][^cde-alpha-readme] +[^lead-2026-09-22]: The project lead, 2026-09-22, quoted in the restructure joint notes: both collections "BOTH should soon be using the schema and classes being defined in the CDE schema re-write, with FindingClass, DataElement, Measurement, etc.", and "OIFMs VERY soon need to get re-organized around the graph-based approach we're developing in the next-gen-schema effort in CDEs. They're currently document-oriented and we need to fix that SOON." +[^cde-branch]: The ACR/RSNA CDE schema repository, next-gen-2026 branch, read on 2026-09-30 at commit b541b74 of 2026-09-23. Every statement below that is attributed to the repository is taken from files committed at that commit; the branch had uncommitted working-tree changes on that date, which were not read. Two sentences on this page rest on the project lead's statement alone and are identified as such in the note on that statement. +[^cde-understanding]: "Next-Generation CDE Schema: Current Understanding" §2.2: "The authoritative internal representation is a knowledge graph, not a document tree", following the committee's own move to "a graph model, where findings point to canonical attribute objects". §2.3 puts inference in scope as a first-class concern: "Subsumption, typed relations, ontology binding, and inference are in scope as first-class concerns rather than as things layered on afterwards." That is the intent, not a demonstration. The document's own open issues qualify it: Issue A asks what inference `is-a` licenses and notes that the negation sweep over a grouping is sound only under genuine transitive subsumption and a closed-world assumption that the radiologist assessed everything beneath the class; Issue B leaves undecided whether finding classes are OWL classes or SKOS-style individuals, where the second choice "has no inference semantics - surrendering exactly what Issue A wants"; Issue C asks "What reasoning do we actually need, and who owns the formal layer?" and is unanswered. +[^cde-requirements]: "What the Vocabulary Must Express" sets out what a FindingClass has to be able to carry, what is reported on (an imaging observation, a physiologic quantity, a device, the image itself), and the separation of a measured quantity from its interpretation and from any method that produced it. +[^cde-structures]: "Draft Structures for Worked Examples" §1 is the node-type table: FindingClass with its identity, definition, typed synonyms and `entity_type`; Diagnosis "as FindingClass, without `entity_type`", where "what still distinguishes Diagnosis from FindingClass is only which relationship types it can source, `MAY_MANIFEST_AS` above all"; Grouping as "the negative-only nodes (`renal abnormality`) that sit above findings and diagnoses alike"; DataElement with its "explicit ordered/unordered value-set distinction"; Measurement with "quantity type, permitted units, optional method (unspecified by default)"; AssessmentScheme, whose "descriptive dimensions are linked ordinary DataElements"; Value as a first-class node whose id derives from its element; AnatomicLocation; and the Concept nodes for the metadata vocabularies. §2 is the edge catalog and the properties edges carry, including the value-subset restriction and the removal of the `required` flag. §3, "The object view: a FindingClass is its gathered edges", is the source of the figure above. +[^cde-relationships]: "The Finding and Diagnosis Relationship Family" is the catalog: seven pairs and one catch-all, each with its domain, range, inverse or symmetry, character and meaning, plus the typicality and specificity properties that manifestation edges carry, the rules that keep manifestation, causation, subsumption and progression apart, and the differential as a derived view rather than a stored edge. Its §7 lists the questions still open for review. The relationships page of this knowledgebase is where that family belongs in full. +[^cde-worked-examples]: "Worked Examples, Acute Pyelonephritis, Pleural Effusion, and One Report" describes the two families written into the graph and warns, before any of their content is used: "Read the provenance before trusting a number. The owner chose the two examples and the effusion's cause list. Every typicality and specificity value, every `expected` hint, the subtype trees, the element choices, and the close-versus-exact code matches are Claude's clinical and terminological estimates, unreviewed." +[^cde-decisions]: The decision record carries a provenance column on every row. **OWNER** means "The owner stated it. The quotation is verbatim from the session. This is the decision."; **CLAUDE DEFAULT** means "Claude chose it to keep moving and recorded the choice. The owner has not reviewed it. Reversible."; **CLAUDE INVENTION** means content "that no one has checked. Treat as unverified." A second caution applies to every row from S49 onward: S49 records the project lead's own ruling that "nothing is 'settled'" by the project's internal discussion, so a decision the project lead and an assistant reached together is a proposal to put to the external reviewer, not a jointly adopted design. The rows used here, with the later ones that qualify them. S8, Grouping as a narrow node type, the negative-only nodes, OWNER inclination with the narrowing NOT OBJECTED. S9, "THERE IS NO SUCH THING AS A REQUIRED ELEMENT", OWNER. S10 and S11, standard clinical metadata as its own node class, with modality, body region and subspecialty as RadLex nodes and provisional codes for etiology, sex, age and time course, OWNER. S12, S37 and S53, an anatomic location's only id is its RID and the substrate is the anatomic locations overlay pointed to at a pinned commit, whose compound sided ids are used as they stand until RadLex mints real ones, OWNER. S31, the agreed terms **anatomic scope** ("it might not be a PLACE, it may be something like 'muscle' (NOS)") and **standard clinical metadata**, OWNER. S33 gave one `HAS_COMPONENT` relationship with inverse `COMPONENT_OF` carrying a count, PROPOSED and ACCEPTED, **and S58 withdraws that form**: the disposition is the reviewer's two distinct edges, the whole must have the part and the part occurs only in that whole, with a minimum count proposed on the first; the alpha's own documentation describes them as two separate assertions rather than one relationship read in two directions, and S34's lesion-family rule is unchanged.[^cde-alpha-shape] S3 and S13 held that `SUBTYPE_OF` propagates nothing, **and S55 moves the project lead's position**: the disposition is the reviewer's approach, under which a subtype carries every element and edge its parent declares, may add elements and narrow inherited values, and cannot drop an inherited element, with a standing requirement that authoring show the blast radius of a change to a parent. S36, "I think we're going to do JSON and OWL", with official TypeScript and Python access libraries wanted, OWNER. S38, the reviewer's implementation approach as the basis going forward, OWNER, who noted that selecting it "does not automatically accept every structural choice in the alpha". S39, Measurement distinct from DataElement and value sets explicitly ordered or unordered, PROPOSED and ACCEPTED with OWNER clarifications. S40 and S41, AssessmentScheme a separate definition type whose linked descriptors are ordinary DataElements, OWNER. S42, a binding "Takes this element, but only this subset of values", OWNER, **qualified by S64 and S65**: S64 makes the narrowing an annotation on the binding with enforcement left to tooling and proposes a strength so it can shade between a hint and an absolute restriction, the "restriction on meaning" reading of S42 becoming that absolute end; S65 proposes two further capabilities to put alongside it, that a binding may add permitted values for one use and may give context-specific language for values, neither changing the shared element. S43, anatomic locations bound directly to data elements and measurements, OWNER, **restated by S62 in the reviewer's form**: the descriptor is created in the CDE repository and points at the RadLex anatomy node, carrying the anatomic scope itself, and no edge is written on the anatomy node. S57, a diagnosis is never a subtype of a finding, with paired FindingClass and Diagnosis nodes for parent and grouping terms joined by a manifestation edge, superseding the cross-label taxonomy of S1. Examples of the weaker mark: S14, non-imaging causes as Diagnosis nodes with no elements, and S18, that the causal pair takes typicality, are both CLAUDE DEFAULT, recorded because the project lead never answered the question. +[^cde-anatomy]: "The Anatomy Axis, Current State" §1: the substrate is "the anatomic locations data behind the owner's published `anatomic-locations` Python package: 2,926 locations, each keyed by its RadLex id, with containment, part-of, left, right, and unsided variants, a body region, SNOMED and other codes, synonyms, and some definitions", pointed to at a pinned commit rather than copied, and settled by the project lead as "an overlay on RadLex, not a replacement and not the sole source"; S53 keeps that overlay as the reviewer's configured anatomy source, using its compound sided ids as they stand until RadLex mints real ones.[^cde-decisions] §2 records that the file lacks a lung parenchyma entry; it is on the running node request list to be fed upstream. §5 records the two anatomic scope families still to be developed, tissue types and structure types, which is why scope may name a kind of structure and not only a place. That is one representation. The alpha states a different one and the two are cited separately here: in its own README, "RadLex is the single source of truth for anatomy", `AnatomicLocation` is a role type whose occupants are native RadLex classes under RID3 keeping their native RID and IRI, the generated ontology imports the configured RadLex directly and leaves its native predicates (`Contained_In`, `Part_Of`, `Regional_Part_Of` and the rest) distinct and unchanged, and `scripts/anatomy.json` is a generated read-only index of the full native anatomy branch that consumers join against rather than copying into the definition graph.[^cde-alpha-readme] +[^cde-graph]: The vocabulary written as JSON Lines. `core.jsonl` declares each relationship type as its own node recording "its inverse, symmetry, formal name, domain and range, and the properties it may carry". `pyelonephritis.jsonl` and `pleural-effusion.jsonl` hold the two worked families. The directory README states the rule these files were authored under: "Every edge is explicit. In particular, `SUBTYPE_OF` does not copy or imply `HAS_ELEMENT`, `SCOPED_TO`, context, or any other outgoing edge on the subtype." That rule is the historical representation, not the current direction. S55 moves the project lead's position to the reviewer's approach, and the alpha's own documentation describes `SUBTYPE_OF` as carrying strict monotonic inheritance.[^cde-decisions][^cde-alpha-shape] +[^cde-alpha-graph]: The reviewer's alpha definition graph, a single JSON file of nodes and edges: 45 FindingClass, 25 Diagnosis, 36 DataElement, 155 Value, 16 Measurement, 5 AssessmentScheme, 28 AnatomicLocation and the metadata concept nodes, joined by 170 `HAS_DATA_ELEMENT`, 71 `SCOPED_TO`, 41 `HAS_MEASUREMENT`, 38 `MAY_MANIFEST_AS`, 10 `MAY_CAUSE`, 10 `SUBTYPE_OF`, 5 `ASSESSED_BY`, 4 `OCCURS_WITH` and single `HAS_COMPONENT` and `MAY_PROGRESS_TO` edges, among others. The project lead selected this implementation approach as the basis going forward on 2026-09-14, while noting that "Selecting the implementation approach does not automatically accept every structural choice in the alpha". This JSON file is the export inspected here; the same alpha model is also committed as `alpha/radcde-alpha.ttl` and `alpha/rdfxml/radcde-alpha.rdf`, which carry the same Measurement and AssessmentScheme individuals, MS-000001 and AS-000001 among them. Across these representations the alpha is where Measurement and AssessmentScheme exist as actual nodes rather than as decisions on paper, and where the anatomic scope edge runs from a DataElement or a Measurement as well as from a definition.[^cde-alpha-readme] +[^cde-alpha-readme]: The alpha's own README. On anatomy, it states that "RadLex is the single source of truth", that the generated ontology imports the configured RadLex release directly, and that `scripts/anatomy.json` is a generated read-only index of the native anatomy branch. Under "Reasoner regression" it records the testing done toward repeatable inference: the shipped alpha imports the complete configured RadLex ontology, full HermiT classification of that world "can be slow", so the suite uses "reduced fixtures containing the unchanged generated CDE ontology plus only the exact native RadLex axioms exercised by the tests", verified against the RadLex index before serialization, with `test_reason.py` and `test_probes.py` run against them and a companion file of "deliberate reasoning probes". The README is explicit that "The reduced fixtures are testing infrastructure only." This is evidence of work toward reasoning that can be repeated and checked; it does not establish that inference over the whole vocabulary is reliable, and the formal layer remains an open question in the baseline document.[^cde-understanding] +[^cde-alpha-shape]: The alpha's generated node and edge documentation. §4 tabulates every edge with its signature, count and properties, and opens with the caveat this page relies on: "Signatures are the tail and head types observed in the build, not everything the edge is permitted to take." It is also where the two component edges are described as separate assertions, `HAS_COMPONENT` as "The whole must have this sub-part" and `COMPONENT_OF` as "This sub-part belongs only to that whole; says nothing about whether the whole has one", and where `SUBTYPE_OF` is glossed as "Taxonomy, more to less specific. Strict monotonic inheritance." +[^cde-libraries-plan]: "Plan: typed definition libraries (Python, then TypeScript) over a PostgreSQL copy of the graph", written 2026-09-13 at the project lead's request and marked "proposed; nothing below is done". It sets out the library direction cited here: Pydantic models mirroring the graph node types, a loader and store, a query API with the operations the documents already perform, and generated TypeScript types so that "a TypeScript consumer of the export is typed from day one", under the rule that "Nothing is resolved only in a library or a loader." Its note of 2026-09-15 reconciles it toward the reviewer's implementation under S38: the plan's models, loader and query API "must be reconciled with that specification rather than built beside it", and its five open decisions stay open. It is a proposed design for libraries over the CDE graph; it does not itself establish that one SDK will serve both collections at once. +[^cde-fc-diagram-input]: The pulmonary nodule neighborhood example, the JSON view the figure is rendered from. Its own description is the figure's alt text at source: "The pulmonary nodule FindingClass node and its typed edges to shared nodes: data elements, values, anatomic location, related classes, and metadata concepts." It records the anatomic scope as lung, RID1301, "structure - required", the elements bound or defined in place, and the related classes over `SUBTYPE_OF`, `MAY_REPRESENT`, `ASSESSED_BY` and `MAY_HAVE_COMPONENT`. The structures document describes this view as generated rather than drawn, and the example files as interim, to be replaced once the renderer reads the graph directly.[^cde-structures] +[^cde-fc-diagram]: The pulmonary nodule FindingClass diagram, committed 2026-09-03. Its own description: "The pulmonary nodule FindingClass node and its typed edges to shared nodes: data elements, values, anatomic location, related classes, and metadata concepts." The project lead cleared its reuse here; the file embedded on this page is byte-for-byte the committed one. Its input is a specific artifact, not either of the graph exports this page uses to check the table: at this commit `tools/render_neighborhood.py` reads `examples/pulmonary-nodule.neighborhood.json` and emits the card, and rendering that example with that pinned script reproduces the figure exactly.[^cde-fc-diagram-input] It is not generated from the alpha definition graph or from the JSON Lines graph. It predates the decisions of 2026-09-14 that made Measurement and AssessmentScheme separate node types, so it labels Lung-RADS and Fleischner as FindingClasses and size (mean diameter) as a DataElement, and it predates the later edge names; the repository notes that the older rendered artifacts were not regenerated. It is kept here as a historical illustration of the object view, which is what its caption dates. +[^cde-schema]: The current Common Data Element schema, `cde.schema.json`, last changed 2024-11-19: a draft-07 JSON Schema whose root is `element_set`, a nested document whose required properties include an `elements` array. This is the document form the ACR/RSNA collection takes today. +[^oifm-repo]: Open Imaging Finding Models repository, main at 4475ac1, a snapshot of 2026-04-28. Its schema is a JSON Schema for a single self-contained finding model, and the corpus is 2,382 individual files under `defs/`, one per definition. This is the document form the OIFM collection takes today. +[^siim-2026]: SIIM 2026 annual meeting talk, June 2026, Mass General Brigham, slide 12, "Relationships and external citations". Its notes: "The relationships turn a flat dictionary into something an app can reason over - differentials, associations, and 'what else could it be.' Example shown: a radiodense urinary calculus may cause hydronephrosis." Read from the project's text extraction of the deck, `sources/bucket/text/siim2026-reports-of-the-future.md`; the presentation file records a modification date of 2026-06-11. +[^table-check]: How each row was checked, at ACR-RSNA-CDEs next-gen-2026 b541b74. Two different kinds of evidence are in play and the table does not merge them. **Declared** domains and ranges come from `graph/core.jsonl`, where each relationship type is a node recording its own domain, range, inverse and symmetry.[^cde-graph] **Observed** endpoints come from the alpha definition graph, whose own generated documentation states the limit plainly: its signatures are "the tail and head types observed in the build, not everything the edge is permitted to take".[^cde-alpha-shape][^cde-alpha-graph] Example edges cannot establish an exhaustive domain, and the alpha contains no Grouping nodes at all, so nothing in it can settle whether a Grouping may source the newer binding names. Rows whose relationship `core.jsonl` declares follow that declaration; `HAS_COMPONENT`, `HAS_DATA_ELEMENT`, `HAS_MEASUREMENT` and the AssessmentScheme range of `ASSESSED_BY` are not declared there, since that file still carries the earlier `MAY_HAVE_COMPONENT` and a single `HAS_ELEMENT`, so those follow the alpha together with the decisions that renamed them.[^cde-decisions] Four rows where the sources disagree, left as differences rather than combined: **SCOPED_TO** is declared from FindingClass, Diagnosis and Grouping, and the row says so; the alpha additionally runs it from a DataElement and from a Measurement, which is the direction S62 takes, and it is the descriptor that carries the scope edge in that form.[^cde-decisions][^cde-alpha-shape] **The binding row** excludes AnatomicLocation as a source: S62 records that a descriptor for a normal structure points at the RadLex anatomy node and no edge is written on the anatomy node itself, so what can be said about a structure is a query over what is scoped to it. The earlier representation is the one to read `core.jsonl` and S43 for, where an AnatomicLocation sources a `HAS_ELEMENT` edge directly; neither anatomy binding occurs in the alpha.[^cde-decisions][^cde-structures][^cde-graph] **MAY_PROGRESS_TO** is declared between either node type at either end, and the row follows that declaration; the relationship catalog states the narrower rule, FindingClass to FindingClass or Diagnosis to Diagnosis, and the alpha's one progression edge runs Diagnosis to Diagnosis.[^cde-graph][^cde-relationships][^cde-alpha-shape] **SUBTYPE_OF** is version-sensitive: `core.jsonl` and the relationship catalog both permit a taxonomy that crosses the finding and diagnosis labels, on S1, while S57 moves the project lead's position to a rule under which a diagnosis is never a subtype of a finding and a parent term such as "renal lesion" exists as a paired FindingClass and Diagnosis joined by a manifestation edge. The row follows S57 and stays within a label; the worked families were written under S1 and are to be re-authored.[^cde-graph][^cde-relationships][^cde-decisions] Each **Example** is an edge that exists in the graph at that commit: the part-solid nodule's parent and component, the pulmonary nodule's Lung-RADS assessment, its CT modality, its presence element, its mean diameter measurement, the pleural effusion and atelectasis association, and the parapneumonic effusion progressing to empyema are all in the alpha definition graph; acute pyelonephritis manifesting as a striated nephrogram is in both that graph and `graph/pyelonephritis.jsonl`.[^cde-alpha-graph][^cde-graph][^cde-worked-examples] None of these checks establishes that the example content is clinically validated, which the worked examples document is explicit about. Three examples the project lead gave were adjusted against the branch and are flagged for review. The anatomic scope of a pulmonary nodule is recorded as **lung** (RadLex RID1301), not lung parenchyma; the anatomy file has no lung parenchyma entry, and adding one is on the project's own node request list.[^cde-anatomy] The measurement bound to a pulmonary nodule is **mean diameter**, one of three quantitative descriptors it binds; length is the kind of quantity, not the name of the measurement.[^cde-alpha-graph] No progression edge runs from a non-solid to a part-solid pulmonary nodule; the example shown is the only progression edge that appears in both graph forms.[^cde-alpha-graph][^cde-graph] The causal example is the one the project lead gave and is the only row not drawn from the graph: no urinary calculus node exists in either authored graph at this commit, though the imported RadLex artifacts carry renal calculus as RID38586, and the edge itself comes from the SIIM 2026 deck.[^siim-2026][^cde-alpha-readme] One row carries a relationship type whose name is still unsettled in the repository: the anatomy is-a edge is written `SUBTYPE_OF` in the graph while the reviewer's viewer writes `IS_A`, and the names are on the project's own list to settle.[^cde-anatomy][^cde-decisions] diff --git a/site/content/foundation-context/relationships.md b/site/content/foundation-context/relationships.md new file mode 100644 index 0000000..3fd074c --- /dev/null +++ b/site/content/foundation-context/relationships.md @@ -0,0 +1,50 @@ +--- +title: "Relationships among definitions" +description: "The typed relationships among finding/diagnosis definitions: manifestation and causation, associated findings and components, how strongly a relationship holds, deriving the differential, assessment schemes, and a proposal for role tagging." +--- + +# Relationships among definitions + +Definitions relate to one another through a small, typed family of relationships, each declared once in the shared graph with its domain, range, inverse, and symmetry: subtype, component, manifestation, causation, co-occurrence, progression, and assessment.[^cde-relationships][^cde-graph][^cde-branch] The CDE project calls the family tentative and non-comprehensive.[^cde-relationships] + +# Findings ↔ Diagnoses: manifestation and causation + +A diagnosis connects to findings in two ways.[^cde-relationships] A finding can be the diagnosis as it appears on imaging: pyelonephritis *appears as* a striated nephrogram, and when the pyelonephritis resolves, the striation goes with it.[^lead-relationships][^cde-relationships][^cde-graph] Or a diagnosis can bring about a separate entity with its own course: pyelonephritis *may cause* a renal abscess, a complication rather than an appearance, which may have sequela beyond the course of the acute pyelonephritis episode.[^lead-relationships][^cde-relationships][^cde-graph][^cde-worked-examples] The schema records these as two edges, MAY_MANIFEST_AS and MAY_CAUSE.[^cde-graph][^cde-alpha-shape] + +# Associated findings + +Findings and diagnoses also occur together whether or not one causes the other: pleural effusion and atelectasis each occur alone and often occur together.[^lead-relationships][^cde-relationships][^cde-graph][^cde-alpha-graph][^oifm-associated] The schema records this as an OCCURS_WITH edge.[^cde-graph][^cde-alpha-shape] An associated finding is distinct from a component, which is an intrinsic part of another finding, such as the solid component of a part-solid nodule, and is joined by a HAS_COMPONENT edge.[^oifm-associated][^cde-decisions][^cde-alpha-shape][^cde-alpha-graph] + +# How strongly + +The edges themselves carry properties that say how commonly the relationship holds.[^lead-relationships][^cde-graph] A manifestation edge can carry a typicality, from obligate through very frequent, frequent, and occasional to very rare, and a specificity, whether the finding is pathognomonic for the diagnosis, highly suggestive of it, or merely suggestive.[^cde-relationships][^cde-graph] A property left off means no judgment has been recorded, not that the relationship is rare.[^cde-relationships] + +# Deriving the differential + +The schema distinguishes manifestation and causation to enable inference: a manifestation edge can be read backward, from a finding to the diagnoses that appear that way, while a causation edge is read forward, from a diagnosis to the complications to watch for.[^lead-relationships][^cde-relationships][^cde-graph] The differential for a finding is therefore derived, not stored: the diagnoses reachable over manifestation edges in reverse, ranked by the typicality and specificity on those edges, on the precedent of the Radiology Gamuts Ontology.[^cde-relationships][^rgo] No edge type called "differential" exists.[^cde-relationships][^cde-graph] + +# Assessment schemes + +A pulmonary nodule definition specifies how a pulmonary nodule will be described.[^lead-relationships][^oifm-scoring] The assessment is a separate assertion, defined by the AssessmentScheme definition, which allows for evaluation of the nodule according to the defined criteria.[^lead-relationships][^cde-decisions][^oifm-scoring] The scheme is its own node, joined to the definitions it evaluates by an ASSESSED_BY edge.[^lead-relationships][^cde-decisions][^cde-alpha-shape][^cde-graph] + +# A proposal: role tagging of elements + +From a project board: tag element definitions with standard codes for their role, presence, location, size, modality-specific measures, so a consuming system can find the right element across definitions by role rather than by hard-coded identifier.[^board-role-tagging] Proposed, not implemented.[^board-role-tagging] + +The relationship types, with their domains, ranges, inverses, and provenance labels, are listed on the [next-generation schema page](./next-generation-schema.md) and in the schema's own relationship file.[^cde-graph][^cde-relationships] + +[^lead-relationships]: The project lead, 2026-09-30, recorded verbatim in the layout plan under "The guidance, verbatim". On this page: the ruling that emphasizing the relationships are held "strictly apart" "makes it seem like THAT'S the issue", and the subsection titles "Findings <-> Diagnoses: Manifestation and Causation" and "Deriving the Differential"; "Pyelonephritis APPEARS AS striated nephrogram..."; "when the pyelonephritis resolves, the striation goes with it..."; "The schema distinguishes manifestation and causation to enable inference:..."; "We should ALSO include the idea of associated findings here, and the properties of the edges themselves that get at how commonly findings/diagnoses are associated in the specified way"; "Don't include the 'HPO and Orphanet' reference", which is why the origin of the typicality scale appears only in the note below and not in the text; "a pulmonary nodule definition specifies how a pulmonary nodule WILL BE DESCRIBED. The assessment is a separate assertion, defined by the AssessmentScheme definition, which allows for evaluation of the nodule according to the defined criteria."; and "...by an ASSESSED_BY edge". A second statement of 2026-09-30, recorded in the same plan after this page was reviewed, settles four wordings: (1) the associated-findings example changes to a pair the graph records as a co-occurrence edge; (2) the renal abscess clause becomes "...may have sequela beyond the course of the acute pyelonephritis episode."; (3) "whether or not one causes the other"; and (4) the schema's own specificity words, pathognomonic, highly suggestive, suggestive ("they're cooler"). Decisions 1, 3 and 4 move the prose onto what the repository states, and the sentences carrying them now rest on the repository as well as on this statement. Decision 2 is the project lead's own clinical wording. Two things on this page therefore rest on this statement alone for what they actually claim. The clause "which may have sequela beyond the course of the acute pyelonephritis episode" is the project lead's wording and clinical judgment: the repository establishes that the abscess is a separate entity the infection produced and says nothing about its subsequent course.[^cde-worked-examples] And the second sentence of the assessment-schemes section is the project lead's own framing: the repository separates an assessment scheme from what it assesses and joins the two by `ASSESSED_BY`, but nothing in it describes the assessment as "a separate assertion", and the reading that the scheme "allows for evaluation of the nodule according to the defined criteria" goes past S40, which is explicit that it "does not decide automated scoring or report-result structure".[^cde-decisions][^cde-structures] +[^cde-branch]: The ACR/RSNA CDE schema repository, next-gen-2026 branch, read on 2026-09-30 at commit b541b74 of 2026-09-23. Every statement attributed to the repository here is taken from files committed at that commit; the branch had uncommitted working-tree changes on that date, which were not read. The family is working material and carries the project's own provenance labels.[^cde-decisions] +[^cde-relationships]: "The Finding and Diagnosis Relationship Family", the catalog this page summarizes. Its own status line describes it as "a tentative, explicitly non-comprehensive catalog of the relationships needed between findings and diagnoses, to validate against the proposed edge object", written for review. §1 is the family: "Seven pairs and one escape hatch", `SUBTYPE_OF`, the component pair, `MAY_CAUSE`, `MAY_MANIFEST_AS`, `OCCURS_WITH`, `MAY_PROGRESS_TO` and `ASSESSED_BY`, which are the seven this page names, plus `MAY_BE_RELATED_TO`, a symmetric catch-all for "an association the author cannot yet type; a triage queue, not a home", which this page does not list. §2 keeps manifestation and causation apart: manifestation "is constitutive", where "A striated nephrogram is pyelonephritis appearing on imaging, not a second disease", and causation "is consequential", where "A renal abscess is a new thing pyelonephritis made"; the test is "if the target resolved, would a clinician say the source 'got better,' or that 'a complication resolved'? The first is manifestation, the second is causation." §3 is the two edge properties, both optional. Typicality is the HPO and Orphanet frequency scale adopted with its percentage anchors, `obligate` 100%, `very_frequent` 80 to 99%, `frequent` 30 to 79%, `occasional` 5 to 29%, `very_rare` 1 to 4%, and a sixth value this page does not name, `excluded` 0%, the negative assertion that the finding is essentially never seen with the diagnosis, whose fit on an edge named `MAY_MANIFEST_AS` the document itself flags for review; an exact cohort ratio or percentage may be given in place of a bin.[^hpo-format][^orphadata] Specificity is three ordered values, whose committed names and source definitions are `pathognomonic`, "seeing the finding is by itself sufficient to make the diagnosis"; `highly_suggestive`, which "strongly narrows the differential toward this diagnosis but is not independently conclusive; roughly Orphanet's 'major diagnostic criterion' territory"; and `suggestive`, which "meaningfully raises suspicion relative to a coincidental finding, without strongly narrowing the differential on its own".[^orphadata] The text above names these three values directly, on the project lead's decision of 2026-09-30, so its wording is the enumeration rather than a gloss on it; the only word that is not part of a committed name is "merely", which is the page's rendering of the third value and carries no meaning the catalog's own definition of `suggestive` does not.[^lead-relationships] The document is also explicit that there is no `nonspecific` value and that "an absent property means no judgment is recorded". §4 is the derived differential: "The differential of a finding is the set of diagnoses reachable over `MAY_REPRESENT`", the inverse of the manifestation edge, "filtered by the context edges (age stage, modality, anatomic scope), ranked by specificity then typicality", and "No `DIFFERENTIAL_OF` edge type exists". Two differences from the text above, left as differences. The catalog ranks by specificity and then typicality, in that order; this page names the two properties without fixing their order. And the catalog's derivation also gathers the diagnosis subtypes of what it reaches, on a taxonomy rule S57 has since moved.[^cde-decisions] §7 lists what is still open, including whether `OCCURS_WITH` needs an association-strength property and whether the causal pair takes typicality at all; the latter is assumed yes in the worked examples so they could be written, which is a recorded assistant default, not a decision.[^cde-worked-examples][^cde-decisions] +[^cde-graph]: The vocabulary written as JSON Lines. `core.jsonl` declares each relationship type as its own node carrying its definition, domain, range, inverse, symmetry, formal name where one exists, and the properties it may carry, which is the "declared once" this page's first sentence rests on. Checked one by one at this commit: `MAY_MANIFEST_AS`, domain Diagnosis, range FindingClass or Diagnosis, inverse `MAY_REPRESENT`, properties `typicality` and `specificity`; `MAY_CAUSE`, domain and range FindingClass or Diagnosis, inverse `MAY_BE_CAUSED_BY`, formal name RadLex `May_Cause`, properties including `typicality`; `OCCURS_WITH`, symmetric, domain and range FindingClass or Diagnosis, formal name SNOMED `Associated with`, and no strength property, whose declared definition on line 13 of that file is "Seen together; asserts and excludes nothing about causality or sequence", so the relation as the schema declares it is agnostic about causation rather than excluding it, which is what "whether or not one causes the other" says; `SUBTYPE_OF`, `MAY_PROGRESS_TO`, `SCOPED_TO`, `HAS_ELEMENT`, `INTERPRETED_FROM` and `ASSESSED_BY`. No relationship type named for a differential is declared. Four of the five edge names in the text above are committed names in this file: `MAY_MANIFEST_AS`, `MAY_CAUSE`, `OCCURS_WITH` and `ASSESSED_BY`. Only `HAS_COMPONENT` is absent under that name, and it is footnoted where it appears; the `ASSESSED_BY` declaration here differs from the alpha in its range rather than in its name.[^cde-alpha-shape][^cde-structures] `pyelonephritis.jsonl` holds the two pyelonephritis edges this page names: acute pyelonephritis `MAY_MANIFEST_AS` striated nephrogram, typicality `frequent` and specificity `highly_suggestive`, and acute pyelonephritis `MAY_CAUSE` renal abscess, typicality `occasional`, whose own note records that it "assumes 07 Q2 answered yes: the causal pair takes typicality". `pleural-effusion.jsonl` is where the pneumonia and pleural effusion pair is actually written, and it is written as `MAY_CAUSE` with typicality `frequent`, not as co-occurrence; the one `OCCURS_WITH` edge in that file joins pleural effusion to pleural thickening.[^cde-alpha-shape] +[^cde-worked-examples]: "Worked Examples, Acute Pyelonephritis, Pleural Effusion, and One Report", §2 on the pyelonephritis family: "`acute pyelonephritis MAY_CAUSE renal abscess`, typicality occasional, on the assumption that the causal pair takes typicality", with renal abscess a Diagnosis in its own right and a subtype of renal lesion. That node classification is the whole of what the repository supports about the abscess being an entity separate from the infection; it says nothing about the abscess's subsequent course. The clause "which may have sequela beyond the course of the acute pyelonephritis episode" is the project lead's wording and clinical judgment, decided on 2026-09-30, and it is consistent with the repository's own test for the distinction, "if the target resolved, would a clinician say the source 'got better,' or that 'a complication resolved'?"[^lead-relationships][^cde-relationships] The same document warns, before any of its content is used: "Read the provenance before trusting a number. The owner chose the two examples and the effusion's cause list. Every typicality and specificity value, every `expected` hint, the subtype trees, the element choices, and the close-versus-exact code matches are Claude's clinical and terminological estimates, unreviewed." The two examples' relationship content is recorded in the decision record as a Claude invention.[^cde-decisions] +[^cde-decisions]: The decision record carries a provenance column on every row. **OWNER** means "The owner stated it. The quotation is verbatim from the session. This is the decision."; **CLAUDE DEFAULT** means "Claude chose it to keep moving and recorded the choice. The owner has not reviewed it. Reversible."; **CLAUDE INVENTION** means content "that no one has checked. Treat as unverified." A further caution applies from S49 onward: S49 records the project lead's own ruling that nothing is settled by the project's internal discussion, so a decision the project lead and an assistant reached together is a proposal to put to the external reviewer. The rows used here. S33 (2026-09-13) is where `HAS_COMPONENT`, with inverse `COMPONENT_OF`, is named, PROPOSED and ACCEPTED, replacing an earlier two-relationship form, with a count on the container's side and a component-of scope on the component's side, and the rule that "A class with a component-of scope is never reported on its own". **S58 withdraws that form**: the disposition is the reviewer's two distinct edges, the whole must have the part and the part occurs only in that whole, with a minimum count proposed on the first, OWNER, pending joint confirmation. The edge name this page uses survives both rows. S39, Measurement and DataElement as distinct definition types, PROPOSED and ACCEPTED with OWNER clarifications. S40, AssessmentScheme "a separate definition type, organized more like FindingClass with edges to multiple DataElement-like descriptors", OWNER, from "I actually like the idea of AssessmentScheme being a separate definition type"; the same row states that it "does not decide automated scoring or report-result structure", and records that Lung-RADS and cancer staging are the project lead's motivating examples, not clinical definitions adopted there. S41 settles the descriptors as ordinary DataElements, OWNER. S40 and S41 are marked documentation only. No row in the record decides `OCCURS_WITH` itself; the two uses of it in the worked families, hydronephrosis with pyelonephritis and pleural thickening with pleural effusion, are recorded under C2 and C4 as CLAUDE INVENTION.[^cde-worked-examples][^cde-graph] S57 has since moved the taxonomy rule the differential derivation in the catalog relies on, so that a diagnosis is never a subtype of a finding.[^cde-relationships] +[^cde-structures]: "Draft Structures for Worked Examples". Its §1 node table carries the AssessmentScheme row, and the §1 update under S40 and S41 states the separation this page's assessment section describes: "a scheme has edges to multiple ordinary DataElements, analogous to FindingClass's organization"; "The scheme, its dimensions, and their permissible values are different objects"; and "Older examples that represent assessment schemes as finding definitions are not the current direction". The same row leaves binding names and any use of Measurements for quantitative components open. This document is also where the component name collision is visible: at this commit it writes the definition-plane edge `MAY_HAVE_COMPONENT` and reserves `HAS_COMPONENT` for the report-plane edge between two Observations, so the two planes used the same word for different things before S33 renamed the definition-plane edge.[^cde-decisions] +[^cde-alpha-shape]: The alpha's generated node and edge documentation, generated from the build on 2026-09-14 and not hand-edited. §4 tabulates every edge with its signature, count and properties, and opens with the caveat any use of it has to carry: "Signatures are the tail and head types observed in the build, not everything the edge is permitted to take." All five edge names in the text above are committed names here: `MAY_MANIFEST_AS` (Diagnosis to FindingClass, 38 edges, properties `specificity` and `typicality`), `MAY_CAUSE` (10 edges, property `typicality`), `OCCURS_WITH` (FindingClass to FindingClass, 4 edges, symmetric and "Stored once; consumers traverse the predicate in both directions"), `HAS_COMPONENT` ("The whole must have this sub-part", 1 edge) alongside `COMPONENT_OF` ("This sub-part belongs only to that whole; says nothing about whether the whole has one", 2 edges), and `ASSESSED_BY` (FindingClass or Diagnosis to AssessmentScheme, 5 edges). Two differences from the earlier representation are left standing rather than merged, and they are different kinds of difference: one changed name and one changed range. The name: `core.jsonl` at the same commit still declares the component edge as `MAY_HAVE_COMPONENT` with inverse `MAY_BE_COMPONENT_OF`, so `HAS_COMPONENT` is the one body edge name absent from that file. The range: that file does declare `ASSESSED_BY` under this same name, but ranges it on a FindingClass acting as an assessment rather than on an AssessmentScheme node. That file predates S33, S40 and S41.[^cde-graph][^cde-decisions][^cde-structures] This document is also where the rule is written that the associated-findings example above satisfies: `occurs-with-same-type` states that "OCCURS_WITH relates two findings or two diagnoses. Between a diagnosis and a finding a more specific edge already exists (MAY_MANIFEST_AS or MAY_CAUSE), so reaching for co-occurrence there is declining to say which". Pleural effusion and atelectasis are both FindingClass nodes, so the pair sits inside that rule. The page's earlier example did not: pneumonia is a Diagnosis and pleural effusion a FindingClass, and the alpha records that pair as "a **`MAY_CAUSE`**, not a `MAY_MANIFEST_AS`. Pneumonia does not show itself as an effusion, it produces one", which is why the project lead replaced the example on 2026-09-30.[^cde-alpha-graph][^cde-graph][^lead-relationships] +[^cde-alpha-graph]: The reviewer's alpha definition graph, the export inspected here, which the project lead selected as the implementation approach going forward on 2026-09-14 while noting that "Selecting the implementation approach does not automatically accept every structural choice in the alpha". The component edges in it, read one by one: `HAS_COMPONENT` from part-solid pulmonary nodule to solid component of part-solid pulmonary nodule, the single edge of that type and the example this page names, and `COMPONENT_OF` from that solid component back to the part-solid nodule and from mural nodule to complex renal cyst. Its four `OCCURS_WITH` edges all run from pleural effusion, to atelectasis, consolidation, ground-glass opacity and mediastinal lymphadenopathy, each between two FindingClass nodes. Pleural effusion with atelectasis is the first of those and is the example the sentence above uses. One limit of it: that edge carries no properties, and no co-occurrence edge in either authored graph carries any, because `core.jsonl` declares the relation with no strength property and whether it needs one is still an open question in the catalog. "Often occur together" is therefore an ordinary clinical statement rather than something the graph records; the frequency properties described further down this page sit on manifestation and causal edges only.[^cde-graph][^cde-relationships] Pneumonia reaches pleural effusion here by `MAY_CAUSE` with typicality `frequent`, as it does in the JSON Lines family, which is why it is no longer the page's co-occurrence example.[^cde-alpha-shape][^lead-relationships] +[^oifm-associated]: "Associated findings vs components", the Open Imaging Finding Models authoring fragment, which is where the distinction this page draws is stated on the finding-model side and where the pneumonia and pleural effusion example comes from. An associated finding is "a separate, independent finding that happens to co-occur with the index finding. It has its own lifecycle", and its independence test is "could you see this related thing *without* the index finding, or vice versa?" The example, verbatim: "pneumonia and pleural effusion. A patient can have pneumonia without effusion, or effusion without pneumonia. They co-occur frequently but are separate entities with their own attributes". A component is "a structural part of the index finding itself - not a separate entity, but a piece of it complex enough to justify its own model", and its test is "would you ever document this thing as a standalone observation without its parent? If not, it's a component"; its example is the solid and ground-glass components of a mixed pulmonary nodule, "parts of what the nodule IS". Note that the authoring rule is document-shaped rather than graph-shaped: an associated finding is recorded as "One multichoice `associated findings` attribute on the index model", presence-level only, whose values name other finding models, while a component is extracted into a model of its own. The graph edges above are the CDE schema's way of holding the same distinction; no source read for this page states that the two mechanisms were reconciled.[^cde-graph][^cde-alpha-shape] Note that this fragment's own example of an associated pair is pneumonia and pleural effusion, which is not the pair the sentence above uses. Both authored CDE graphs join pneumonia to pleural effusion by a causal edge instead, and the alpha's same-type rule would not permit a co-occurrence edge between a diagnosis and a finding, so the project lead's decision of 2026-09-30 moved the page's example onto a pair the graph does record as a co-occurrence.[^cde-graph][^cde-alpha-shape][^cde-alpha-graph][^lead-relationships] This fragment is cited above for the distinction itself, which both collections draw the same way, not for the choice of example. On the sentence's other half, the independence test establishes that either finding can occur without the other, and it takes no position on whether one causes the other when they do occur together, which is the same agnosticism the CDE catalog states in SNOMED's words, an association "without either asserting or excluding a causal or sequential relationship".[^cde-relationships] +[^oifm-scoring]: The Open Imaging Finding Models authoring prompt, which states on the finding-model side that the thing assessed and the assessment are different definitions: "**Scoring systems and structured assessments are valid findings**, but should be modeled separately from the observations they assess. A 'pulmonary nodule' finding model captures what the nodule looks like (size, morphology, location). A 'Lung-RADS category' finding model captures the risk assessment assigned to it. These are separate because different radiologists might describe the same nodule but assign different risk categories - and systems need to reason about both independently." The companion authoring fragment on scope repeats it: scoring systems "are valid findings in their own right, modeled **separately** from the observations they assess". This supports the separation and the pulmonary nodule and Lung-RADS pairing, not the AssessmentScheme node type, which is the CDE schema's construct; in this collection both sides are finding models.[^cde-decisions] +[^rgo]: Radiology Gamuts Ontology, the precedent the CDE relationship catalog cites by name for deriving rather than storing a differential: "The Radiology Gamuts Ontology, the ontology-backed successor to Reeder and Felson, carries 55,000+ links using exactly two relation types, is-a and may-cause, and produces every differential by traversing incoming causal links to a finding (Budovec, Lam, and Kahn, RadioGraphics 2014; gamuts.net)." The catalog also records that no source it surveyed, across that ontology, HPO and Orphanet, SNOMED CT, UMLS, Radiopaedia and STATdx, stores differentials as diagnosis-to-diagnosis edges. Cited here as the CDE catalog cites it; the ontology's own site and the 2014 paper were not re-read for this page.[^cde-relationships] +[^hpo-format]: Human Phenotype Ontology annotation format, named in the CDE relationship catalog as the source of the typicality scale, whose bins and percentage anchors the catalog adopts verbatim and whose permission to give an exact cohort ratio in place of a bin it also adopts. Recorded here only, at the project lead's instruction that the reference stay out of the prose.[^lead-relationships][^cde-relationships] +[^orphadata]: Orphadata science data description, named in the CDE relationship catalog alongside the HPO annotation format for the frequency bins, and as the source of the `pathognomonic` definition the catalog quotes, "a sign whose presence indicates that a particular disease is present beyond any doubt". Recorded here only, at the project lead's instruction that the reference stay out of the prose.[^lead-relationships][^cde-relationships] +[^board-role-tagging]: Open Imaging Data Model project board (internal), undated; schema idea only. The proposal is to tag each element definition with a standard code, SNOMED or RadLex, naming the role it plays: presence, location, size in its several measures, and modality-specific measures such as radiodensity, echogenicity and MR signal, so that a consuming system finds the right element by looking up the code for the role rather than hard-coding a set-specific identifier. It is a working proposal with no date and no implementation cited, and nothing read for this page connects it to the graph's own route to the same reuse, one shared DataElement node bound by many definitions.[^cde-structures] diff --git a/site/content/foundation-context/standards.md b/site/content/foundation-context/standards.md new file mode 100644 index 0000000..60453c7 --- /dev/null +++ b/site/content/foundation-context/standards.md @@ -0,0 +1,10 @@ +--- +title: "Standards" +description: "The standards and references Foundation Context layers over and cites (RadLex, the LOINC/RSNA Radiology Playbook, SNOMED CT, FMA, ICD, CPT, Radiopaedia, and Wikipedia), and what each contributes." +--- + +# Standards + +The standards and references Foundation Context layers over and cites (RadLex, the LOINC/RSNA Radiology Playbook, SNOMED CT, FMA, ICD, CPT, Radiopaedia, and Wikipedia), and what each contributes. + +This page is coming. diff --git a/site/content/foundation-context/three-axes.svg b/site/content/foundation-context/three-axes.svg new file mode 100644 index 0000000..f50554f --- /dev/null +++ b/site/content/foundation-context/three-axes.svg @@ -0,0 +1,2 @@ +Findings/DiagnosesWHAT was foundFindingspulmonary nodulesolidpart-solidnon-solidDiagnosespulmonary neoplasmnon-small cell lung canceradenocarcinoma of the lungAssessmentsLung-RADSSalter-Harris categoryData Elementspresencepresent · absent · indeterminate · unknownseverityminimal · mild · moderate · severeMeasurementslengthmm, cm, ...CT densityHUlayered over: finding models· ACR/RSNA CDEsAnatomic LocationsWHERE it isthoraxlungright lungupper lobe of right lungmiddle lobe of right lunglower lobe of right lungleft lungpleural spacemediastinumheartabdomenkidneyleft kidneyright kidneyrenal pelvisliverspleenheadbraincerebellumfrontal lobeorbitpelvisurinary bladderprostatelayered over: RadLexExam TypesHOW it was seenCTCT Chest(preferred family)CT Chest WO contrastCT Chest W contrast IVCT Chest WO and W contrast IVCT Lung parenchyma WO contrastCTA Chest vesselsMRMR Knee(preferred family)MR Knee WO contrastMR Knee W contrast IVMR Brain(preferred family)MR Brain WO contrastMR Brain WO and W contrast IVXRXR Knee(preferred family)XR Knee 2 ViewsXR Knee 3 ViewsXR Chest(preferred family)XR Chest 2 ViewsXR Chest PA and LateralUSUS Abdomen(preferred family)US Abdomen limitedUS Abdomen RUQUS Thyroid glandeach a LOINC/RSNA Playbook entrylayered over: LOINC/RSNAPlaybook \ No newline at end of file diff --git a/site/content/index.md b/site/content/index.md new file mode 100644 index 0000000..0ce8121 --- /dev/null +++ b/site/content/index.md @@ -0,0 +1,70 @@ +--- +title: "Open Imaging Data Model" +description: "What the Open Imaging Data Model is for, the two contexts it separates, its core pieces, what it is used for, what has been built, and where it stands." +--- + +# Open Imaging Data Model + +The Open Imaging Data Model (OIDM) is an effort to model the context of imaging work across its whole lifecycle, from ordering through acquisition, interpretation, reporting, and follow-up, so that the medical imaging ecosystem has a common platform on which to build tools that assist radiologists, technologists, ordering providers, and back-office staff.[^lead-2026-09-24] + +The current focus is one part of that: data structures and semantics for representing imaging results. That means results from the current exam and from prior exams, produced by radiologists, technologists, AI, or the modalities themselves, and integrated across time so that a finding has a history rather than a series of disconnected mentions. Later work will model the broader patient context, integrating imaging results into the larger fabric of the patient's history and linking them to the patient's other diagnoses, procedures, and issues.[^lead-2026-09-24] + +OIDM defines data structures for use inside applications, to both read and create data. It is not a transport format. FHIR and DICOM expressions of the structures are designed separately and are guided by them.[^lead-2026-09-19] + +# Why results as data + +Today the findings in a radiology report are written for a human reader and then locked in narrative text. Represented as data, once, each finding becomes reusable in two directions: forward to clinicians, to drive treatment decisions, follow-up, and care coordination; and forward to the next radiologist reading the subsequent exam, who needs to know what was there before, where, and whether it changed.[^siim-2026] + +# The two contexts + +A structured finding, say "pulmonary nodule, present, right upper lobe, 8 mm, new", says what exists, where, and on what exam. It does not say how a nodule associates with other findings, how it might be precancerous, which prior exams could show it, what the implications are, or what else it could be. That knowledge is not in the patient's record. It is background knowledge of anatomy, pathology, and imaging technique.[^siim-2026] + +OIDM therefore separates two things. **Patient Context** is this patient's results and clinical context: one per patient, protected health information, changing as the patient's condition evolves. **Foundation Context** is a single shared layer of definitions, relationships, and citations: what a pulmonary nodule is, where it can occur, what it can cause, what it can be confused with. It is open, not patient-specific, authored and versioned in the open.[^siim-2026] The two are deeply interconnected: every contingent fact about this patient, this nodule on this exam, is attached to the baseline clinical knowledge about what such a thing is, so that an application reading the patient's record has the knowledge to interpret it.[^lead-2026-09-24] + +[![Two planes: a patient's exam and findings in Patient Context, attached to the exam type, the finding definitions, and the anatomic locations in Foundation Context](./two-planes.svg)](./two-planes.svg) + +(click the image for full size) + +# The core pieces + +**[Foundation Context](./foundation-context/index.md).** The shared semantic layer. It has three axes, each a curated layer over an existing standard: what was found (finding/diagnosis definitions, held as Open Imaging Finding Models and as ACR/RSNA Common Data Elements), where it is (anatomic locations anchored in RadLex), and how it was seen (exam types over the LOINC/RSNA Radiology Playbook). The axes relate to each other and cite external references. This pillar covers both the shape a definition takes and the work of building the content.[^siim-2026][^build-plan] + +**[Data Structures](./data-structures/index.md).** The patient-side structures. The Observation is the atomic unit: one finding or diagnosis, its presence, its change from prior, its location, its characterizing attributes, and its ties into Foundation Context. Observations can come from a radiologist, a technologist, an AI, or the modality itself. An Exam Finding List holds all the Observations from one exam. An Imaging Problem List reorganizes a patient's Observations by finding, across exams, so that each finding has a history. The Imaging Persona is the proposal that goes furthest: the imaging results integrated into a single larger graph of the patient's history, with the patient's other diagnoses, procedures, and issues, the whole of it underpinned by Foundation Context. Patient-side data forms one graph and Foundation Context forms another, and the patient graph is woven into the foundation graph at every finding, diagnosis, location, and exam type.[^siim-2026][^lead-2026-09-24] + +**[SDKs](./sdks/index.md).** The tools that wrap these structures so that developers can create, read, and manipulate instances in real applications, and that attach the shared knowledge to a patient's data the same way every time. Some SDK capabilities also support the authoring of Foundation Context itself.[^build-plan][^siim-2026] + +# Use cases + +With structured results and shared context, the same machinery serves many purposes: reporting assistance that knows what a finding is; longitudinal tracking of findings across exams; decision support, follow-up management, and care coordination; quality surveillance; registry submission and research extraction; and grounding for generative tools, which get a citable shared context instead of inventing one.[^webinar-2026-07][^siim-2026][^use-cases-index] The [Use Cases](./use-cases/index.md) section lists every purpose the project has written down, with a page for each one worked out in detail. + +# Sample applications + +The project has built demonstrations to make the ideas concrete. Each is listed in [Sample Applications](./sample-applications/index.md) with the idea it illustrates. + +- [Finding Model Forge](https://fmf.oidm.org), for authoring and reviewing finding/diagnosis definitions.[^forge-docs] +- The [finding models catalog](https://openimagingdata.github.io/finding-models-site/), for browsing the definitions.[^catalog-site] +- Command-line tools for finding models and for anatomic locations.[^fm-cli] +- Two Imaging Problem List viewers: [the first](https://imaging-problem-list.pages.dev), and [the second](https://main.ipl-anatomy.pages.dev), organized by anatomy.[^webinar-2026-07][^ipl-readme] +- A report extraction and coding platform that turns report text into Observations.[^ipl-readme] +- Public demonstrations of rendering a coded finding as prose (2023), ontology search (2024), and assisted reporting (2024).[^site-2023-06][^site-2024-07][^site-2024-01] + +# Where things stand + +Nothing here is formally specified. The project is coalescing, and its structures exist in several dated versions: in code, in manuscripts under review, in talks, and in working documents. This knowledgebase presents each version beside the others, attributed and dated, and states disagreements without resolving them.[^build-plan] + +The project asks three things: contribute or propose finding/diagnosis definitions; review AI-generated content and connections; build against the SDKs.[^siim-2026] + +[^lead-2026-09-24]: The project lead, 2026-09-24, on the scope of OIDM, the Imaging Persona, and the interconnection of the two graphs; recorded verbatim in the layout plan. +[^lead-2026-09-19]: The project lead, 2026-09-19, notes on the revised Imaging Problem List manuscript: data structures for use within applications, not a transport definition. +[^siim-2026]: SIIM 2026 annual meeting talk, June 2026, Mass General Brigham; slides 3, 4, 5, 6, 7, 8, 14, and 15. +[^status-2026-01]: Open Imaging Data Model 2026 Status Update, January 2026. +[^webinar-2026-07]: SIIM Enterprise Imaging webinar, 15 July 2026, the six application families and the two live-prototype slides. +[^use-cases-index]: Use case catalog index, UseCases repository, commit 71a90d2: "Add information to ACR GRID registry". +[^forge-docs]: Finding Model Forge, finding model creation workflow document, dev branch. +[^catalog-site]: finding-models-site repository. +[^fm-cli]: findingmodel README, main branch, command-line tools. +[^ipl-readme]: imaging-problem-list README, dev branch. +[^site-2023-06]: "SIIM update", openimagingdata.org, June 2023. +[^site-2024-01]: "2024 New Year Update", openimagingdata.org, January 2024. +[^site-2024-07]: "SIIM 2024 update, hackathon edition", openimagingdata.org, July 2024. +[^build-plan]: Knowledgebase build plan, the project lead's stated goals (2026-09-20) and "No specification exists" (2026-09-21). diff --git a/site/content/sample-applications/index.md b/site/content/sample-applications/index.md new file mode 100644 index 0000000..436ebdb --- /dev/null +++ b/site/content/sample-applications/index.md @@ -0,0 +1,10 @@ +--- +title: "Sample Applications" +description: "The applications the project has actually built, from demonstrations to prototypes, each with the idea it illustrates and the use cases it relates to." +--- + +# Sample Applications + +The applications the project has actually built, from demonstrations to prototypes, each with the idea it illustrates and the use cases it relates to. + +This page is coming. diff --git a/site/content/sdks/index.md b/site/content/sdks/index.md new file mode 100644 index 0000000..4231169 --- /dev/null +++ b/site/content/sdks/index.md @@ -0,0 +1,10 @@ +--- +title: "SDKs" +description: "The tools that wrap the data structures so developers can create, read, and manipulate instances in real applications, and the tools that help author and maintain Foundation Context itself." +--- + +# SDKs + +The tools that wrap the data structures so developers can create, read, and manipulate instances in real applications, and the tools that help author and maintain Foundation Context itself. + +This page is coming. diff --git a/site/content/two-planes.svg b/site/content/two-planes.svg new file mode 100644 index 0000000..e983ff9 --- /dev/null +++ b/site/content/two-planes.svg @@ -0,0 +1,2 @@ +Patient Contextthis patient · this examFoundation Contextshared, curated knowledgeCT chest without contrastthis patient · 2026-03-04pulmonary nodulepresent · 8 mm · newpleural effusionabsentfound onfound onCT Chest WO contrastexam type · 29252-4pulmonary nodulefinding/diagnosis definitionpleural effusionfinding/diagnosis definitionCT Chestexam family · preferredCTmodalitymember of familymodalityseen onthoraxanatomic locationlungupper lobe of right lungpleural spacecontainscontainscontainscoversoccurs atoccurs atis ais alocated atis alocated atfinding/diagnosis definitionanatomic locationexam typemodalityevery fact about this patient is attached to the shared knowledge of what such a thing is \ No newline at end of file diff --git a/site/content/use-cases/index.md b/site/content/use-cases/index.md new file mode 100644 index 0000000..d34039e --- /dev/null +++ b/site/content/use-cases/index.md @@ -0,0 +1,10 @@ +--- +title: "Use Cases" +description: "A list of the applications, tools, plugins, and output products an ecosystem built on this foundation could support, with a fuller page for some of them." +--- + +# Use Cases + +A list of the applications, tools, plugins, and output products an ecosystem built on this foundation could support, with a fuller page for some of them. + +This page is coming. diff --git a/site/guidance.md b/site/guidance.md new file mode 100644 index 0000000..aaaffa2 --- /dev/null +++ b/site/guidance.md @@ -0,0 +1,181 @@ +# The project lead's guidance on the website, verbatim + +Everything the project lead has said about the outward-facing pages, the figures, and the site, in his own words, dated, so pages and decisions can cite it. New guidance is added at the end. The earlier sections are copied unchanged from the planning records they were first written into, at commit `9725105`; each names its record. Those records also hold some session framing around the quotations, kept as recorded. + +## 2026-09-22 to 2026-09-30: the layout, the Overview, the hub, the schema and relationships pages, and the figures + +From docs/plans/2026-09-22-layout-plan.md, "The guidance, verbatim", commit `9725105`. + +Project lead, 2026-09-27 to 2026-09-29, on the Finding Models and Common Data Elements hub page: + +> I think what we want to say is that the finding/diagnosis definition is an unified anchor for the CONCEPT of a finding/diagnosis in a form that can be used to attach associated context: name, synonyms, definition, attributes, etc. OIFM are AN inclusive collection, intended for rapid prototyping... ACR/RSNA Common Data Elements (always include the organizations) are the well-reviewed... published through radelement.org. Forget about talking about the current schema's sets and elements--waste of bits. + +> "Finding Models and Common Data Elements" / "Definitions of findings and diagnoses anchor these concepts in a form that associated content can..." + +> "...maintained by the OIDM project..." / "The OIFM repository is intended for rapid, community-based prototyping and iteration based on an open-source model." / "...with changes proposed by anyone." / Take out "uneven" / "in the project lead's words" is RIGHT OUT. I think the thing to emphasize is that they will have the same schema and describe the same ideas. Major difference is that OIFMs is much more "beta"/rapid additions and updates and CDEs is much more "release"/reviewed and stable. + +> I think we should also emphasize that we will maintain cross-links: when something migrates to CDEs, it will continue to be maintained in the OIFM repo but have a reference to its CDE identity. "Both CDEs and OIFMs are document-oriented today, and moving..." + +The agreed hub text (2026-09-29) is the one the Opus agent writes verbatim into `knowledge/drafts/finding-models-and-cdes-hub.md`. + +Project lead, 2026-09-30, on figures: "Let's make the AssessmentSchemes ovals rather than diamonds--those have a flowchart implication that doesn't obtain here." Recorded as a convention in tools/diagrams/README.md. + +Project lead, 2026-09-30, four prose decisions on the relationships page: (1) change the associated-findings example to a pair the graph records as OCCURS_WITH; (2) the renal abscess clause becomes "...may have sequela beyond the course of the acute pyelonephritis episode."; (3) "whether or not one causes the other"; (4) use the schema's specificity words: pathognomonic, highly suggestive, suggestive ("they're cooler"). + +Project lead, 2026-09-30, on the pulmonary nodule neighborhood figure (illustrative; reflects updates the project lead, as the CDE project's clinical expert, intends for the CDE graph): rename "attenuation" to "composition" and "pulmonary margin" to "margin"; drop "lesion count"; "ground glass nodule" for "non-solid pulmonary nodule"; add the Grouping "pulmonary parenchymal abnormality"; add the diagnoses "pulmonary neoplasm" and "pulmonary granuloma" (subtype of the grouping); add "Fleischner criteria" as an assessment scheme; add subspecialty; show SEEN_ON CT, XR, and MR; no values; no time course or etiology for now: "I'm starting to think that may need to be a property of the edge connecting it to a diagnosis. Actually, that's the same for etiology." + +Project lead, 2026-09-30, on the relationships page: + +> Emphasizing that they're "strictly apart" makes it seem like THAT'S the issue. / Let's just call this subsection "Findings <-> Diagnoses: Manifestation and Causation" / "Pyelonephritis APPEARS AS striated nephrogram..." "when the pyelonephritis resolves, the striation goes with it..." / Next subsection called "Deriving the Differential" / "The schema distinguishes manifestation and causation to enable inference:..." / We should ALSO include the idea of associated findings here, and the properties of the edges themselves that get at how commonly findings/diagnoses are associated in the specified way / Don't include the "HPO and Orphanet" reference / "a pulmonary nodule definition specifies how a pulmonary nodule WILL BE DESCRIBED. The assessment is a separate assertion, defined by the AssessmentScheme definition, which allows for evaluation of the nodule according to the defined criteria." / "...by an ASSESSED_BY edge" + +Project lead, 2026-09-30, on the next-generation schema page: + +> "Both Open Imaging FInding Models and RSNA/ACR Common Data Elements are moving..." / Show a link to the ACR/RSNA CDE schema repository, on the next-gen-2026 branch. / "...as a graph of interconnected concepts rather than a set of documents." / "Applications will be able to use either or both collections at once via a common SDK based on the new schema." / "FindingClass" defines the properties of the items described in imaging reports. A "Diagnosis" is a separate, closely-related node type, distinguished by a different set of possible relationships. Groupings are broad classes of findings/diagnoses such as "renal abnormality", usually intended to be used in the negative. / For most of these node class definitions, we want to use "defines" rather than "is a" (this is a very tricky but important semantic distinction) / "AssessmentScheme" definitions layout the systems such as Lung-RADS via which a constellation of other findings is evaluated. / "The anatomic scope property of each other node indicates the locations and/or structures in which they may be localized (e.g., "lung parenchyma" for a pulmonary nodule). / "Edges integrate the notes into a rich fabric of interrelated concepts, enabling reliable, reproducible inference." / "Edges bind FindingClass and Diagnosis definitions to their anatomic scope and to the DataElements and Measurements used to specify their attributes. The edges themselves may contain properties that provide additional detail about the relationships between nodes." / Concept nodes as a note below the list, introducing the paragraph that also talks about anatomic scope. / Use the actual table of relationship types as examples. / "Evolution of CDEs and Finding Models: Both CDEs and existing Open Imaging Finding Models will have to migrate from their current JSON document-based organization to a new graph-oriented format, likely using RDF/OWL as an underlying storage format. More ergonomic data structures will also be developed for use in SDKs and via an API." + +Project lead, 2026-09-30, on the SDKs (for the pillars figure and the SDKs index): "I think we should also say that the SDKs help AUTHOR and MAINTAIN the underlying foundation context." + +Project lead, 2026-09-29, on Measurements (for the three-axes figure and the next-generation schema page): + +> Measurements don't have EXPLICIT units, they indicate what KIND of thing they are: length (with appropriate length units, which could be mm, cm, whatever), CT density (HU), or count (unitless), ratio (unitless), velocity (m/s, cm/s), volume (cc, mL, mm^3..., L) + +Project lead, 2026-09-24, on the Overview page: + +> OIDM is NOT just for modeling imaging results, it is for modeling the ENTIRE imaging workflow context throughout its lifecycle. It's meant to enable a common platform across the medical imaging ecosystem that enables tools that will assist radiologists, technologists, ordering providers, and back-office staff. RIGHT NOW, we happen to be focused on data structures and semantics for representing imaging results, whether from current exams or prior, and from AIs, radiologists, technologists, or modalities themselves, and for integrating those results across time. We will ALSO model the broader patient context and enable linking between imaging results and patients' OTHER diagnoses, procedures, and issues. + +> The imaging persona doesn't place the rest of the patient context "alongside"--it integrates them into a single, larger graph, underpinned by the Foundation Context. Basically, the imaging results can be integrated into the larger fabric of the patient's history. + +> Let's call it "finding/diagnosis definitions" since they're used so interchangably. It really might be that "ObservationType" is the better catch-all term. + +> "Pointers" is a... well, pointless inclusion. The idea is that the patient graph is deeply interconnected with the Foundation Context graph, attaching baseline clinical knowledge to the contingent patient facts. [...] every finding, diagnosis, location, and exam type. + +Source added by the project lead on 2026-09-24: `~/exam-types/sources/LoincRsnaRadiologyPlaybook.xlsx` (44,178 Playbook part rows with LOINC numbers, part types, RadLex IDs and preferred names; a second sheet `core-playbook-dev`, 995 rows) for the exam-types page and diagrams. The ACR-RSNA-CDEs next-gen graph (`docs/next-gen-schema/graph/*.jsonl`, `alpha/graph/definition-graph.json`, `diagrams/*.svg`) supplies real nodes and typed edges for the Foundation Context figures. + +Wording rules from these: "finding/diagnosis definitions" is the term for the WHAT axis for now ("Observation Type" is under consideration as the catch-all, not adopted); the connection between the graphs is described as interconnection that attaches knowledge to facts, never as pointers; the Imaging Persona integrates imaging results into one larger graph of the patient's history. + +Project lead, 2026-09-23, correcting the first version of this plan: + +> I think in the foundation context page, we need the document on finding models to be something that encompasses both OIFMs (and that repo) but also the RSNA/ACR Common Data Elements and the relationshpi between the two. We should ALSO think about how to present the work on the next-generation schema (which will be for BOTH CDEs and OIFMs--I think applications will be able to use both at once). + +Project lead, 2026-09-22: + +> There should be a big overview page, layout out a HIGH-LEVEL justification of the project, talking about what the CORE PIECES of it ARE (not the DETAILS, just basics about what they ARE). These are the first three pillars: foundation context, data structures, SDK. +> Then the next couple of things: start with some basics of USE CASES. We can have a LIST oriented view of cases, only SOME of which have a more fleshed out page. +> We can also have a list of our sample/demo applications, some of which are RELATED to the use cases. +> There should be a directory on the foundation context, with some documents on the key ideas related to FindingModels/CDEs and the definitions that go into them. There should be a page on the OIFM repository (with a node to its relationship to the CDE project). Tehre should be a page on AnatomicLocations, referencing existing work but also pointing to the fact that we're migrating to incorporate with RadLex. We can also describe the ExamType content to come. + +And, on the shape: no page budget; the number of pages follows from the ideas. "Repository pages" are not a pattern: the one page on the OIFM repository exists because that repository *is* the inclusive collection of finding models. + +## 2026-09-22: the two collections and the restructure + +From docs/plans/restructure-joint-notes.md, "Decisions of 2026-09-22", commit `9725105`. + +These supersede the four-pillar page list that stood here earlier today. The owner's words are quoted; everything else is the two sessions' shared understanding, confirmed in conversation. + +1. **Five named pillars. Use the names, never numbers.** "1. Foundation Context / Semantic layer: includes both the schema/meta-definitions as well as the efforts to build out content. 2. Data Structures, most ESPECIALLY in the Observation layer, and how there are two graphs and they work together. 3. SDKs, the tools that wrap the data structures and enable developers working in the application layer to manipulate instances in real-world applications (and some around helping with definition of the foundation context, obviously). 4. Theoretical Use Cases: Imaging all the different kinds of applications/tools/plugins/output products we could create using an ecosystem built around this foundation. 5. ACTUAL sample applications we've created to try to illustrate some of these ideas." The fourth was then shortened to **Use Cases**. So: **Foundation Context**, **Data Structures**, **SDKs**, **Use Cases**, **Sample Applications**. +2. **A CDE and a finding model are the same content.** "A 'CDE' and a 'Finding Model' are the same thing, but they're in different repos. Open Imaging Finding Models are in the OIDM-maintained repo, and are intended to be very INCLUSIVE, can almost be drafts. Common Data Elements live in the ACR/RSNA repository, and are intended to be WELL-REVIEWED. They're more like something published. They BOTH should soon be using the schema and classes being defined in the CDE schema re-write, with FindingClass, DataElement, Measurement, etc." Consequence: explain the common model once, then the inclusive OIDM collection and the well-reviewed ACR/RSNA collection and their relationship. Collection expectations are not per-item facts. Migration is intended, not complete. The "Observation Type" umbrella name the sessions had floated is withdrawn as unnecessary and was never approved. +3. **The OIFM reorganization around the graph is urgent.** "OIFMs VERY soon need to get re-organized around the graph-based approach we're developing in the next-gen-schema effort in CDEs. They're currently document-oriented and we need to fix that SOON." A change of organization, not added fields. Stated prominently under Foundation Context; current implementation kept distinct from direction; no storage, schema, or migration design invented beyond the source work. +4. **Source eligibility rule.** Repository documents, prompts, plans, skills, ADRs, and code are team artifacts eligible for substantive content. They are not demoted for being agent-assisted, and no statement needs separate human approval to be cited. Committing a document does not make a draft proposal or one implementation project-wide consensus. Each claim carries its source's actual status, date, and any explicit provenance label the source supplies (the CDE decision record's OWNER and CLAUDE DEFAULT, for example). Differences between sources and the owner's later corrections are preserved, not reconciled. No speculative authorship audit. +5. **Terminology.** Foundation Context (shared definitions, relationships, references) and Patient Context (this patient's imaging results plus relevant clinical context), from the SIIM 2026 deck. The CDE report graph, the Imaging Problem List representations, and the broader Patient Context are *related representations* of the Observation idea, never "the same thing serialized differently". Foundation Context and Data Structures correspond to the two graphs at the explanatory level; the report graph is the imaging-assertion part of Patient Context, not a clinical-context schema. The deck's "connective tissue" means relationships and citations between foundation concepts and is used only in that sense. Retained artifact names are used where a source's current files are cited: OIFM finding model, CDE set and element, the next-generation FindingClass, Diagnosis, Grouping, DataElement, Measurement. +6. **Organizing method** (agreed with Codex). The introduction explains the two-graph connection once within the five pillars. Idea anchors carry the recovered substance. Patient-side ideas say which shared definitions they reference; foundation-side ideas say how observations use them; related anchors link both ways. Existing worked examples are used for what each actually shows (the CDE pyelonephritis report and the deck's calculus figure show both graphs; the IPL example2 study is patient-side only; the deck's aneurysm and kidney tables are foundation-side only). Sources attach to ideas; repositories do not become chapters. Redundant generated exposition is deleted after its unique ideas are recovered; unique team-authored material is kept and counted in the budget. A glossary or question register is not a mandate to duplicate content. + +## 2026-09-20: the stated goals + +From knowledge/plans/2026-09-20-knowledgebase-build-plan.md, "The project lead's stated goals", commit `9725105`. + +Recorded verbatim from the 2026-09-20 request that started this work, so that documents citing `scope:grilling-session-2026-09-20` have a written source. Only spelling was normalized. + +- **Open Imaging Finding Models.** "Definition formats, needs overhaul for increased metadata and interesting relationship/graph work coming from CDEs." "Tooling for working with these, using them, probably needs corresponding overhaul." "Actual content efforts." +- **ACR/RSNA Common Data Elements.** "Should dovetail with the OIFM work." +- **FindingModelForge.** "An authoring tool for creating OIFMs / CDEs." +- **Imaging Problem List.** "A format for representing a patient's imaging history." "Define appropriate data structures for representing both observations in radiology reports and their relationships." "Manage them (and appropriately collate them) across the patient's imaging history." "Design interesting applications that use this tool and demonstrate how this works." "Create standard data structure formats, etc. for working with these." +- **Anatomic Locations.** "There are repos, and this will be blended in with RadLex." "Also needs tooling for people working with them." +- **Exam types.** "Needs wrapper tooling around the knowledge graph represented by RadLex LOINC playbook." "Need to define the PREFERRED high-level entries like 'CT Chest', 'MRI Brain', 'X-ray Knee'." "Need to TIGHTLY wrap up with Anatomic Locations, including edges for both 'always included' (which can include hierarchies) as well as 'usually included' (for edge things) and something like 'POSSIBLY included, would have to check'." +- **Content direction (2026-09-21).** "In findingmodels, there's a recent taxonomy branch with new files in lists that's the immediate direction for all of the content, probably replacing a lot of the Gamuts-based models." The taxonomies are to be called the "MGB exam-oriented sub-taxonomies." + +## 2026-09-21: decisions + +From knowledge/plans/2026-09-20-knowledgebase-build-plan.md, "Decisions added 2026-09-21", commit `9725105`. + +| Topic | Decision | +|---|---| +| Manuscripts in the bucket | Paraphrase facts, structures, and numbers; cite as "manuscript under review at JDIM, 2026" or "prepared for submission"; never quote; reviewer correspondence never reproduced or paraphrased. | +| Excalidraw boards | Publish the public-facing boards (object model, structured report sections, outcome tracking, exhibit figures, lineage, use-case notes). From internal boards, publish only schema and data-model ideas with organizations named; people, meeting histories, and vendor demo logistics omitted; the rest recorded as reviewed and held. | +| Diagrams | No Mermaid. Excalidraw files rendered to SVG beside each document. Per diagram, a builder is the source until the file is hand-edited, after which the builder is retired (tools/diagrams/README.md). | +| Vendor name in the deck extract | Generalized to "a reporting vendor". | +| Remote | openimagingdata/oidm-knowledge created public; site deploy workflow disabled until the site generator is settled; the project lead pushes main (agent pushes are blocked by a local guard). | +| Upstream defects | Drafted for the project lead's review before any issue is filed. | +| No specification exists | The project is in a coalescing phase; nothing is formally defined. The knowledgebase presents each source's current version of a structure side by side (repo branch and commit, manuscript under review, deck or webinar, board), dated and attributed, and states disagreements (for example the three Imaging Problem List status vocabularies) without resolving them. Words like "specification" or "canonical model" are not used for any of them. | +| Data structures versus transport | Per the project lead's 2026-09-19 manuscript notes: OIDM defines a system of data structures for imaging exam result information used inside applications; FHIR and DICOM are transport expressions designed separately and guided by the structures. The data-structure documents are to be reframed accordingly. | +| Page feedback (later) | Wanted: off-the-shelf page comments and suggested changes, possibly dictation; candidates Hypothesis or Giscus. Not this iteration. | +| Site generator and host | Decided 2026-09-21 after a survey of wiki and knowledge-graph tools (sources/site-tools-comparison.md): Quartz stays, on Cloudflare Workers static assets, which serve its clean URLs natively. Three targets: oidm-knowledge-dev (default, `task deploy`), oidm-knowledge-staging (`task deploy:staging`, also the push-to-main workflow), oidm-knowledge (`task deploy:prod`). Typed-edge graph from OKF frontmatter is a Quartz plugin to write later. The object-storage pipeline was removed. | + +## 2026-09-22: decisions + +From knowledge/plans/2026-09-20-knowledgebase-build-plan.md, "Decisions added 2026-09-22", commit `9725105`. + +| Topic | Decision | +|---|---| +| Structure | Five named pillars, always by name: Foundation Context (schema and meta-definitions plus the content build-out), Data Structures (especially the Observation layer and how the patient graph and the foundation graph work together), SDKs, Use Cases (the team's documented possible applications, tools, plugins, and outputs), Sample Applications (what was actually built). The earlier three-layer and four-pillar organizations are superseded. Ideas mined from repository documents, plans, prompts, skills, and ADRs are the team's own ideas and enter as content. | +| CDEs and finding models | The same content in two collections: the inclusive OIDM-maintained finding models and the well-reviewed ACR/RSNA CDEs. Both are intended to use the schema and classes of the CDE schema rewrite (FindingClass, DataElement, Measurement, and the rest). Explained once as a common model, then the two collections and their relationship. Collection expectations are not per-item facts; migration is intended, not complete. | +| OIFM reorganization | Reorganizing the finding models around the graph-based approach of the CDE next-generation schema effort is an immediate priority, stated as direction under Foundation Context with current implementation kept distinct. No storage, schema, or migration design beyond the source work. | +| Source eligibility | Repository documents, prompts, plans, skills, ADRs, and code are team artifacts eligible for substantive content, not demoted for being agent-assisted. Each claim carries its source's status, date, and any provenance label the source supplies. Differences and later corrections are preserved, not reconciled. | +| Terminology | Foundation Context and Patient Context from the SIIM 2026 deck. The CDE report graph and the Imaging Problem List representations are related representations of the Observation idea. Retained artifact names are used where a source's files are cited. | +| Restructure record | The operating record is `docs/plans/restructure-joint-notes.md` (decisions, agreed outline, next steps) with `docs/plans/idea-placement.md` mapping every inventoried idea to its section. | + +## 2026-10-01: the layers and release gating + +From docs/plans/2026-10-01-sources-and-layers.md, "The project lead's description, verbatim" and "Decisions, 2026-10-01", commit `9725105`. + +> I'd like to have a literal knowledge bundle of OKF files which are mostly for internal team use. They're in a public repo, and anyone can look at them and see what they see, but they're not what we'd present. We'd ALSO like to have a series of interconnected pages that ARE intended as the outward presentation of these ideas, where the knowledge for them is a distillation of the broader knowledgebase to make it more accessible to outsiders. + +> I would ALSO like to maintain something like source material. I will create a non-public bucket on tigris for raw source material (presentations, manuscripts, emails, etc.). I (or people I give appropriate credentials to) can access that bucket to fill out a raw-sources directory, but this is NOT in the repo. We will generate DETAILED distilled summaries with key quotes and ESPECIALLY extracted graphics into a references directory. We can cite these in our knowledge bundle as needed, and if required go back and dig into the raw source. + +> I would promote "paraphrase and describe figures" to "generate new versions of the figures capturing key points". + +- Presentation directory: `pages/`. +- The bundle gets an internal site build on a Cloudflare static Worker. +- Nothing pre-review on production; staging only, every page labeled "DRAFT / Awaiting Review". +- Bucket: `oidm-knowledge-sources` (private). + +## 2026-10-05 and 2026-10-06: the site as its own area + +> There should be NO overlap between the OUTWARD-facing pages you're creating at the INWARDLY-oriented knowledge graph the other agent is working on. + +> OK, then it is CRITICAL that you pull ALL the good work we've been doing on PAGES out into a dedicated are where it WON'T get confused with the graph work. + +On the area's name: "Let's call it "site" instead of "presentation"." + +> "Figure builders" output path should be configuration, not something hardcoded. +> I have to say, the organization of the `site/` directory seems hopelessly muddled with a lot of extra cruft and duplication. +> The pages directory is NOT an OKF bundle--it's the material for some kind of site. Why are we STILL F---ING CONFUSED about this?! +> It makes NO sense for the assets to be in "knowledge"--they are NOT knowledge. If we're going to use them for both the OKF pages AND the site, let's put them in `./assets`. If they're only on the site, put them in `site/assets`. + +On whether the site needs its own log: "Does the SITE need some kind of LOG.md or HISTORY.md for us to record our work? (Distinct from the larger repo/knowledge graph.)" + +> "Frontmatter a site page should carry"?! What sense is that? + +On the site's records: "Maybe we have have `site/CURRENT_TASK.md`, `site/ROADMAP.md` and `site/HISTORY.md`." + +On what `site/pages` held: "I'm unclear on what `site/pages` is. Is it our source Markdown files?" Then: "Rename to `site/content` and go ahead with the commit plan." + +> Could we commit our page separate WITHOUT disrupting any of the graph reorganization work our colleague is doing? + +## 2026-10-06: the build approach + +On ordering the explorer by a function in the YAML config or by our own `quartz.ts` over Quartz's: "NEITHER of your choices are appealing. Really? This is the best we can do? TS-in-yaml or PATCHING THE TOOL THAT'S THE STATE OF THE ART that we don't even fully understand?! Try harder." + +On the automatic folder listings: "I agree, hide the folder lsits." + +> Site title is "Open Imaging Data Model: Knowledge" + +On placeholder pages that only say the page is coming: "Why so generic? Can't we have a quick summary of what we're planning to put there?" + +> Should we have a site-specific Taskfile.yml? + +On the navigation component, `site/navigation.yaml`, the placeholder summaries, and the site Taskfile: "OK, but make it look GOOD." Then: "Start implementing." + +## 2026-10-08: page comments + +> We were talking about gdiscus (?) as a commenting option... Is that something we can do on our dev/staging areas? + +> Can we have it in all three? And can it be clear which env the comment came from? diff --git a/site/navigation.yaml b/site/navigation.yaml new file mode 100644 index 0000000..cf341cd --- /dev/null +++ b/site/navigation.yaml @@ -0,0 +1,16 @@ +# Order of the site navigation. Paths are relative to site/content/. +# Every page must appear exactly once; site/tools/check_site.py enforces it. +- page: index.md + label: Overview +- page: foundation-context/index.md + pages: + - foundation-context/finding-models-and-cdes.md + - foundation-context/next-generation-schema.md + - foundation-context/relationships.md + - foundation-context/anatomic-locations.md + - foundation-context/exam-types.md + - foundation-context/standards.md +- data-structures/index.md +- sdks/index.md +- use-cases/index.md +- sample-applications/index.md diff --git a/site/okf-meta-plugin/dist/components/index.js b/site/okf-meta-plugin/dist/components/index.js deleted file mode 100644 index 14f6f35..0000000 --- a/site/okf-meta-plugin/dist/components/index.js +++ /dev/null @@ -1,173 +0,0 @@ -import { h } from "preact" - -const css = ` -.okf-meta { - display: flex; - flex-wrap: wrap; - gap: 0.4rem; - align-items: center; - margin-bottom: 1rem; - font-size: 0.8rem; -} -.okf-meta .okf-badge { - display: inline-flex; - align-items: center; - gap: 0.25rem; - padding: 0.1rem 0.5rem; - border-radius: 999px; - background: var(--lightgray); - color: var(--darkgray); - white-space: nowrap; -} -.okf-meta .okf-badge.status-verified { background: #dde8fb; color: #1f4e91; } -.okf-meta .okf-badge.status-human-reviewed { background: #d9f2e3; color: #1c6b3f; font-weight: 600; } -.okf-meta .okf-badge.status-draft { background: #fbeedd; color: #8a5a20; } -.okf-meta .okf-badge.status-stable { background: #d9f2e3; color: #1c6b3f; } -.okf-meta .okf-badge.status-deprecated { background: #ececec; color: #6b6b6b; text-decoration: line-through; } -.okf-meta .okf-badge.status-stale { background: #fbdcdc; color: #922b2b; } -.okf-sources { - margin: 0 0 1.5rem 0; - font-size: 0.85rem; -} -.okf-sources ul { - margin: 0.25rem 0 0 0; - padding-left: 1.2rem; -} -.okf-sources li { - margin: 0.15rem 0; -} -.okf-sources .okf-source-meta { - color: var(--gray); - font-size: 0.85em; -} -` - -// Trim a full ISO-8601 timestamp down to its date portion for compact display. -// Leaves plain dates (and anything that isn't a recognizable timestamp) untouched. -function formatDate(value) { - if (value === undefined || value === null || value === "") return "" - if (value instanceof Date) return value.toISOString().slice(0, 10) - const s = String(value) - const tIndex = s.indexOf("T") - return tIndex === -1 ? s : s.slice(0, tIndex) -} - -// stale_after is due once its date is strictly before today (UTC, date-only -// comparison so the badge doesn't flip at an arbitrary time of day). -function isPastDue(staleAfter) { - if (!staleAfter) return false - const d = staleAfter instanceof Date ? staleAfter : new Date(String(staleAfter)) - if (Number.isNaN(d.getTime())) return false - const now = new Date() - const todayUTC = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) - const dueUTC = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) - return dueUTC < todayUTC -} - -function Badge(label, value, extraClass) { - if (value === undefined || value === null || value === "") return null - return h( - "span", - { class: `okf-badge ${extraClass ?? ""}` }, - h("strong", null, label + ": "), - String(value), - ) -} - -// One badge per `verified` entry, rendered as "by (at)". Entries whose actor -// follows the `human:` convention (see authoring guide, OKF §7) get the -// stronger "human-reviewed" style so a human sign-off reads differently from -// an agent or process attestation. -function verifiedBadges(verified) { - if (!Array.isArray(verified) || verified.length === 0) return [] - return verified - .map((entry, i) => { - if (!entry || typeof entry !== "object" || !entry.by) return null - const isHuman = String(entry.by).startsWith("human:") - const at = formatDate(entry.at) - const label = at ? `${entry.by} (${at})` : String(entry.by) - return h( - "span", - { - key: `okf-verified-${i}`, - class: `okf-badge status-verified${isHuman ? " status-human-reviewed" : ""}`, - }, - isHuman ? h("strong", null, "✓ ") : null, - label, - ) - }) - .filter(Boolean) -} - -const OkfMetaConstructor = (_userOpts) => { -const OkfMeta = (props) => { - const fm = props?.fileData?.frontmatter ?? {} - const status = fm.status - const type = fm.type - const generated = fm.generated ?? {} - const verified = fm.verified - const staleAfter = fm.stale_after - const sources = fm.sources - const stale = isPastDue(staleAfter) - - const hasAnyMeta = - status || - type || - generated.by || - generated.at || - (Array.isArray(verified) && verified.length) || - stale || - (sources && sources.length) - - if (!hasAnyMeta) return null - - const badges = [ - Badge("type", type), - Badge("status", status, status ? `status-${status}` : ""), - stale ? Badge("stale after", formatDate(staleAfter), "status-stale") : null, - ...verifiedBadges(verified), - generated.by ? Badge("generated by", generated.by) : null, - generated.at ? Badge("generated at", formatDate(generated.at)) : null, - ].filter(Boolean) - - const sourcesList = - Array.isArray(sources) && sources.length - ? h( - "div", - { class: "okf-sources" }, - h("strong", null, "Sources"), - h( - "ul", - null, - sources.map((s) => { - const metaParts = [] - if (s.author) metaParts.push(s.author) - if (s.last_modified) metaParts.push(`updated ${formatDate(s.last_modified)}`) - return h( - "li", - { key: s.id ?? s.resource }, - h( - "a", - { href: s.resource, target: "_blank", rel: "noopener noreferrer" }, - s.title ?? s.resource, - ), - metaParts.length ? h("span", { class: "okf-source-meta" }, ` — ${metaParts.join(", ")}`) : null, - ) - }), - ), - ) - : null - - return h( - "div", - { class: "okf-meta-block" }, - badges.length ? h("div", { class: "okf-meta" }, ...badges) : null, - sourcesList, - ) -} - -OkfMeta.css = css -return OkfMeta -} - -export { OkfMetaConstructor as OkfMeta } diff --git a/site/okf-meta-plugin/package.json b/site/okf-meta-plugin/package.json deleted file mode 100644 index 761fa21..0000000 --- a/site/okf-meta-plugin/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "okf-meta-plugin", - "version": "0.2.0", - "description": "OkfMeta component for Quartz - renders OKF 0.2 frontmatter (type, status, generated, verified, stale_after, sources) as a badge row and source list", - "type": "module", - "license": "MIT", - "main": "./dist/index.js", - "exports": { - ".": { - "import": "./dist/index.js" - }, - "./components": { - "import": "./dist/components/index.js" - } - }, - "peerDependencies": { - "preact": ">=10.0.0" - }, - "quartz": { - "name": "okf-meta-plugin", - "displayName": "OKF Meta", - "category": "component", - "version": "0.2.0", - "quartzVersion": ">=5.0.0", - "defaultOrder": 50, - "defaultEnabled": true, - "defaultOptions": {}, - "components": { - "OkfMeta": { - "displayName": "OKF Meta", - "defaultPosition": "beforeBody", - "defaultPriority": 15 - } - } - } -} diff --git a/site/plugins/lightbox/dist/components/index.js b/site/plugins/lightbox/dist/components/index.js new file mode 100644 index 0000000..6389936 --- /dev/null +++ b/site/plugins/lightbox/dist/components/index.js @@ -0,0 +1,465 @@ +// Lightbox: click-to-zoom image viewer for article diagrams. +// +// This is a component-only Quartz plugin: the component itself renders +// nothing (Quartz still mounts it so its .css and .beforeDOMLoaded get +// collected into every page - see componentResources.ts in Quartz core). +// All behavior lives in the beforeDOMLoaded script below. +// +// This is beforeDOMLoaded, not afterDOMLoaded, deliberately: in a production +// build Quartz code-splits every component's afterDOMLoaded script into its +// own hashed file, all loaded in parallel via dynamic import() and only +// awaited as a group before the SPA router itself initializes (see +// componentResources.ts). That leaves a real window, right after page load +// under real network latency, where a user can click a diagram before our +// chunk has loaded and registered its click interceptor - the click then +// falls through to Quartz's SPA router uncontested, which tries to fetch the +// .svg as an html page and does a background window.location.assign(), +// silently navigating away. beforeDOMLoaded scripts are always bundled into +// one single, blocking prescript.js loaded synchronously in before +// the body is even parsed, so registering the interceptor there guarantees +// it exists before anything on the page is clickable at all. The overlay +// DOM itself is still built lazily, on first real open() call - safe, since +// a real click implies the body has already rendered. + +const css = ` +.site-lightbox { + position: fixed; + inset: 0; + z-index: 10000; + display: flex; + flex-direction: column; + /* Always a dark scrim, independent of site theme: --light/--dark are + foreground/background ROLE tokens that swap literal colors between + light and dark mode (in dark mode --dark is a near-white text color), + so using either for a dimming backdrop would invert in dark mode. */ + background: rgba(10, 10, 12, 0.92); + backdrop-filter: blur(2px); +} +.site-lightbox[hidden] { + display: none; +} +.site-lightbox-stage { + flex: 1 1 auto; + position: relative; + overflow: hidden; + cursor: grab; + touch-action: none; +} +.site-lightbox-stage:active { + cursor: grabbing; +} +.site-lightbox-img { + position: absolute; + left: 0; + top: 0; + margin: 0; + max-width: none; + max-height: none; + width: auto; + height: auto; + transform-origin: 0 0; + will-change: transform; + background: var(--light); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4); + touch-action: none; + user-select: none; + -webkit-user-drag: none; +} +.site-lightbox-toolbar { + position: absolute; + top: 0; + right: 0; + z-index: 1; + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + padding: 0.75rem; +} +.site-lightbox-btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2.25rem; + height: 2.25rem; + padding: 0 0.75rem; + border-radius: 6px; + border: 1px solid var(--lightgray); + background: var(--light); + color: var(--dark); + font-family: inherit; + font-size: 0.95rem; + line-height: 1; + text-decoration: none; + cursor: pointer; +} +.site-lightbox-btn:hover { + background: var(--lightgray); +} +.site-lightbox-btn:focus-visible { + outline: 2px solid var(--secondary); + outline-offset: 2px; +} +.site-lightbox-close { + font-size: 1.3rem; +} +.site-lightbox-hint { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 0.5rem 0.75rem; + text-align: center; + font-size: 0.75rem; + color: var(--gray); + pointer-events: none; +} +body.site-lightbox-lock { + overflow: hidden; +} +` + +const script = ` +function siteLightboxBuild() { + var overlay = document.createElement("div") + overlay.className = "site-lightbox" + overlay.setAttribute("role", "dialog") + overlay.setAttribute("aria-modal", "true") + overlay.setAttribute("aria-label", "Image viewer") + overlay.hidden = true + + var stage = document.createElement("div") + stage.className = "site-lightbox-stage" + + var img = document.createElement("img") + img.className = "site-lightbox-img" + img.alt = "" + img.draggable = false + stage.appendChild(img) + + var toolbar = document.createElement("div") + toolbar.className = "site-lightbox-toolbar" + + function mkButton(label, ariaLabel, extraClass) { + var b = document.createElement("button") + b.type = "button" + b.className = "site-lightbox-btn" + (extraClass ? " " + extraClass : "") + b.textContent = label + b.setAttribute("aria-label", ariaLabel) + return b + } + + var openFull = document.createElement("a") + openFull.className = "site-lightbox-btn" + openFull.textContent = "Open full size" + openFull.target = "_blank" + openFull.rel = "noopener noreferrer" + + var zoomOutBtn = mkButton("−", "Zoom out") + var resetBtn = mkButton("Reset", "Reset zoom") + var zoomInBtn = mkButton("+", "Zoom in") + var closeBtn = mkButton("×", "Close image viewer", "site-lightbox-close") + + toolbar.appendChild(openFull) + toolbar.appendChild(zoomOutBtn) + toolbar.appendChild(resetBtn) + toolbar.appendChild(zoomInBtn) + toolbar.appendChild(closeBtn) + + var hint = document.createElement("div") + hint.className = "site-lightbox-hint" + hint.textContent = "Scroll to zoom · drag to pan · double-click to reset · Esc to close" + + overlay.appendChild(stage) + overlay.appendChild(toolbar) + overlay.appendChild(hint) + document.body.appendChild(overlay) + + var scale = 1, tx = 0, ty = 0 + var naturalW = 0, naturalH = 0 + var triggerEl = null + var inlineWidthPx = 0 + var MIN_SCALE = 0.1, MAX_SCALE = 20 + var TOP_MARGIN = 16 + + function applyTransform() { + img.style.transform = "translate(" + tx + "px," + ty + "px) scale(" + scale + ")" + } + + // Fits the image to the viewport WIDTH only, not literal 1:1 - height is + // deliberately not part of this calculation. These diagrams are portrait + // (taller than wide) while browser viewports are landscape, so a fit that + // also constrains height is almost always height-bound and ends up + // *narrower* than the already-squeezed inline rendering, defeating the + // point of clicking in. Height overflow is normal here: the stage is + // pannable (drag, or pinch/wheel-zoom recentering), and opens scrolled to + // the top of the diagram rather than vertically centered, so the reader + // lands where the diagram starts. Floored at the inline rendered scale so + // this can never make the image look smaller than what was just clicked - + // that floor is measured once, when the click that opened this image + // happened, and reused for Reset/double-click within this same open(). + // The true 1:1 resolution is reachable via wheel/pinch zoom or the "Open + // full size" button, which bypasses the overlay entirely. + function fitAndCenter() { + var rect = stage.getBoundingClientRect() + var widthFit = naturalW > 0 && rect.width > 0 ? (0.92 * rect.width) / naturalW : 1 + var inlineFit = inlineWidthPx > 0 && naturalW > 0 ? inlineWidthPx / naturalW : 0 + scale = Math.max(widthFit, inlineFit) + tx = (rect.width - naturalW * scale) / 2 + ty = TOP_MARGIN + applyTransform() + } + + function zoomAt(clientX, clientY, factor) { + var rect = stage.getBoundingClientRect() + var mx = clientX - rect.left + var my = clientY - rect.top + var imgX = (mx - tx) / scale + var imgY = (my - ty) / scale + var next = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale * factor)) + tx = mx - imgX * next + ty = my - imgY * next + scale = next + applyTransform() + } + + function focusables() { + return [openFull, zoomOutBtn, resetBtn, zoomInBtn, closeBtn] + } + + // An pointing at an SVG that has a viewBox but no width/height + // attribute (the case for every diagram in this bundle) reports a + // naturalWidth/Height from the CSS default-object-size fallback (~300px + // wide), not the SVG's real authored resolution - so "full size" would + // actually be smaller than the squeezed inline rendering. Fetch and parse + // the SVG's own width/height or viewBox to get the true intrinsic size; + // fall back to the 's reported natural size for anything else. + function resolveSize(src, cb) { + var isSvg = /\.svg(?:[?#]|$)/i.test(src) + if (!isSvg) { + cb(img.naturalWidth || img.width, img.naturalHeight || img.height) + return + } + fetch(src) + .then(function (res) { + return res.text() + }) + .then(function (text) { + var doc = new DOMParser().parseFromString(text, "image/svg+xml") + var root = doc.documentElement + var w = root && root.getAttribute("width") + var h = root && root.getAttribute("height") + if (w && h && !/%/.test(w) && !/%/.test(h)) { + cb(parseFloat(w), parseFloat(h)) + return + } + var vb = root && root.viewBox && root.viewBox.baseVal + if (vb && vb.width && vb.height) { + cb(vb.width, vb.height) + return + } + cb(img.naturalWidth || img.width, img.naturalHeight || img.height) + }) + .catch(function () { + cb(img.naturalWidth || img.width, img.naturalHeight || img.height) + }) + } + + function open(src, alt, trigger, inlineWidth) { + triggerEl = trigger || null + inlineWidthPx = inlineWidth || 0 + img.src = src + img.alt = alt || "" + overlay.setAttribute("aria-label", alt ? "Image viewer: " + alt : "Image viewer") + openFull.href = src + overlay.hidden = false + document.body.classList.add("site-lightbox-lock") + + resolveSize(src, function (w, h) { + naturalW = w + naturalH = h + img.style.width = naturalW + "px" + img.style.height = naturalH + "px" + fitAndCenter() + }) + + closeBtn.focus() + } + + function close() { + if (overlay.hidden) return + overlay.hidden = true + document.body.classList.remove("site-lightbox-lock") + img.removeAttribute("src") + if (triggerEl && typeof triggerEl.focus === "function") triggerEl.focus() + triggerEl = null + } + + overlay.addEventListener("click", function (e) { + if (e.target === overlay || e.target === stage || e.target === hint) close() + }) + closeBtn.addEventListener("click", close) + resetBtn.addEventListener("click", fitAndCenter) + openFull.addEventListener("click", function (e) { + e.stopPropagation() + }) + zoomInBtn.addEventListener("click", function () { + var r = stage.getBoundingClientRect() + zoomAt(r.left + r.width / 2, r.top + r.height / 2, 1.4) + }) + zoomOutBtn.addEventListener("click", function () { + var r = stage.getBoundingClientRect() + zoomAt(r.left + r.width / 2, r.top + r.height / 2, 1 / 1.4) + }) + + stage.addEventListener( + "wheel", + function (e) { + if (overlay.hidden) return + e.preventDefault() + var factor = e.deltaY < 0 ? 1.15 : 1 / 1.15 + zoomAt(e.clientX, e.clientY, factor) + }, + { passive: false }, + ) + + img.addEventListener("dblclick", function (e) { + e.preventDefault() + fitAndCenter() + }) + + // Pointer-based pan (mouse/touch/pen) with two-finger pinch-to-zoom. + var pointers = new Map() + var dragStart = null + var pinchStart = null + + function dist(a, b) { + return Math.hypot(a.x - b.x, a.y - b.y) + } + function midpoint(a, b) { + return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 } + } + + img.addEventListener("pointerdown", function (e) { + img.setPointerCapture(e.pointerId) + pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }) + if (pointers.size === 1) { + dragStart = { x: e.clientX, y: e.clientY, tx: tx, ty: ty } + pinchStart = null + } else if (pointers.size === 2) { + dragStart = null + var pts = Array.from(pointers.values()) + pinchStart = { dist: dist(pts[0], pts[1]), mid: midpoint(pts[0], pts[1]), scale: scale, tx: tx, ty: ty } + } + }) + + img.addEventListener("pointermove", function (e) { + if (!pointers.has(e.pointerId)) return + pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }) + if (pointers.size === 1 && dragStart) { + tx = dragStart.tx + (e.clientX - dragStart.x) + ty = dragStart.ty + (e.clientY - dragStart.y) + applyTransform() + } else if (pointers.size === 2 && pinchStart) { + var pts = Array.from(pointers.values()) + var d = dist(pts[0], pts[1]) + var factor = d / pinchStart.dist + var rect = stage.getBoundingClientRect() + var mx = pinchStart.mid.x - rect.left + var my = pinchStart.mid.y - rect.top + var imgX = (mx - pinchStart.tx) / pinchStart.scale + var imgY = (my - pinchStart.ty) / pinchStart.scale + var next = Math.min(MAX_SCALE, Math.max(MIN_SCALE, pinchStart.scale * factor)) + tx = mx - imgX * next + ty = my - imgY * next + scale = next + applyTransform() + } + }) + + function endPointer(e) { + pointers.delete(e.pointerId) + if (pointers.size === 0) { + dragStart = null + pinchStart = null + } else if (pointers.size === 1) { + var p = Array.from(pointers.values())[0] + dragStart = { x: p.x, y: p.y, tx: tx, ty: ty } + pinchStart = null + } + } + img.addEventListener("pointerup", endPointer) + img.addEventListener("pointercancel", endPointer) + + document.addEventListener("keydown", function (e) { + if (overlay.hidden) return + if (e.key === "Escape") { + close() + return + } + if (e.key === "Tab") { + var items = focusables() + var first = items[0] + var last = items[items.length - 1] + if (e.shiftKey && document.activeElement === first) { + e.preventDefault() + last.focus() + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault() + first.focus() + } + } + }) + + return { overlay: overlay, open: open, close: close } +} + +// The overlay is built lazily, on the first real open() call, not eagerly +// here - this whole script runs once, synchronously, before document.body +// exists (see the comment above), so nothing here can touch document.body. +var lightbox = null + +// Open on click of any article image (or its wrapping link). Registered on +// document in the capture phase, in beforeDOMLoaded, so it is guaranteed to +// exist before Quartz's SPA router's bubble-phase click listener on window +// can ever see a click - see the file-level comment for why that ordering +// matters. stopPropagation keeps a matched click from ever reaching that +// listener; modifier-clicks (new tab / new window) and middle-click are left +// alone so the plain link still works as a fallback. +document.addEventListener( + "click", + function (e) { + if (e.button !== 0 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return + var targetImg = e.target && e.target.closest && e.target.closest("article img") + if (!targetImg) return + var link = targetImg.closest("a") + var src = link ? link.href : targetImg.currentSrc || targetImg.src + if (!src) return + var inlineWidth = targetImg.getBoundingClientRect().width || 0 + e.preventDefault() + e.stopPropagation() + if (!lightbox) lightbox = siteLightboxBuild() + lightbox.open(src, targetImg.alt, link || targetImg, inlineWidth) + }, + true, +) + +// Quartz's SPA router (micromorph) diffs document.body against the freshly +// fetched page and strips elements - like our dynamically-appended overlay - +// that aren't part of it, so re-append (and defensively close) on every nav. +document.addEventListener("nav", function () { + if (!lightbox) return + if (!document.body.contains(lightbox.overlay)) { + document.body.appendChild(lightbox.overlay) + } + lightbox.close() +}) +` + +const LightboxConstructor = (_userOpts) => { + const Lightbox = () => null + Lightbox.css = css + Lightbox.beforeDOMLoaded = script + return Lightbox +} + +export { LightboxConstructor as Lightbox } diff --git a/site/okf-meta-plugin/dist/index.js b/site/plugins/lightbox/dist/index.js similarity index 77% rename from site/okf-meta-plugin/dist/index.js rename to site/plugins/lightbox/dist/index.js index 5684fd5..9faafe9 100644 --- a/site/okf-meta-plugin/dist/index.js +++ b/site/plugins/lightbox/dist/index.js @@ -1,5 +1,5 @@ // Component-only plugin main entry. // Quartz imports this module for its side effects when a plugin is declared // as category "component". We don't need any YAML options or side effects -// for OkfMeta, so this is intentionally a no-op module. +// for Lightbox, so this is intentionally a no-op module. export {} diff --git a/site/plugins/lightbox/package.json b/site/plugins/lightbox/package.json new file mode 100644 index 0000000..a0ce16f --- /dev/null +++ b/site/plugins/lightbox/package.json @@ -0,0 +1,33 @@ +{ + "name": "site-lightbox", + "version": "0.1.0", + "description": "Click-to-zoom image lightbox for Quartz - opens article diagrams in a pan/zoom overlay", + "type": "module", + "license": "MIT", + "main": "./dist/index.js", + "exports": { + ".": { + "import": "./dist/index.js" + }, + "./components": { + "import": "./dist/components/index.js" + } + }, + "quartz": { + "name": "site-lightbox", + "displayName": "Site Lightbox", + "category": "component", + "version": "0.1.0", + "quartzVersion": ">=5.0.0", + "defaultOrder": 50, + "defaultEnabled": true, + "defaultOptions": {}, + "components": { + "Lightbox": { + "displayName": "Site Lightbox", + "defaultPosition": "afterBody", + "defaultPriority": 50 + } + } + } +} diff --git a/site/plugins/site-chrome/dist/components/index.js b/site/plugins/site-chrome/dist/components/index.js new file mode 100644 index 0000000..e2b3931 --- /dev/null +++ b/site/plugins/site-chrome/dist/components/index.js @@ -0,0 +1,102 @@ +import { h } from "preact" +import { readFileSync } from "node:fs" +import { fileURLToPath } from "node:url" +import { parse } from "yaml" +import { pathToRoot, resolveRelative, simplifySlug, slugifyFilePath } from "@quartz-community/utils/path" + +const css = readFileSync(new URL("./style.css", import.meta.url), "utf8") +const navigationFile = process.env.SITE_NAVIGATION_FILE || fileURLToPath(new URL("../../../../navigation.yaml", import.meta.url)) +// Read once at build time. Optional labels override parsed page titles. +const navigation = parse(readFileSync(navigationFile, "utf8")) +const slugFor = (page) => slugifyFilePath(page) +const samePage = (left, right) => simplifySlug(left) === simplifySlug(right) + +export const ReviewBanner = () => { + const Component = () => process.env.SITE_TARGET === "production" ? null : + h("div", { class: "review-banner", role: "note", "aria-label": "Review status" }, "DRAFT / Awaiting Review") + Component.css = css + return Component +} + +export const SiteLogo = () => { + const Component = ({ fileData }) => { + const root = pathToRoot(fileData.slug) + return h("div", { class: "site-logo" }, + h("a", { href: root, class: "internal", "aria-label": "Open Imaging Data Model: Knowledge home" }, + h("img", { class: "site-logo-light", src: `${root}/static/brand/logo-color.svg`, alt: "Open Imaging Data Model", width: 208 }), + h("img", { class: "site-logo-dark", src: `${root}/static/brand/logo-white.svg`, alt: "Open Imaging Data Model", width: 208 }), + h("span", { class: "site-logo-caption" }, "Knowledge"))) + } + Component.css = css + return Component +} + +const chevron = () => h("svg", { class: "site-nav-chevron", width: 16, height: 16, viewBox: "0 0 16 16", "aria-hidden": "true" }, + h("path", { d: "m6 4 4 4-4 4", fill: "none", stroke: "currentColor", "stroke-width": 1.5, "stroke-linecap": "round", "stroke-linejoin": "round" })) + +export const SiteNav = () => { + const Component = ({ fileData, allFiles }) => { + const pages = new Map(allFiles.map((file) => [file.slug, file])) + const pageFor = (item) => typeof item === "string" ? item : item.page + const labelFor = (item) => (typeof item === "string" ? undefined : item.label) ?? pages.get(slugFor(pageFor(item)))?.frontmatter?.title + const link = (item, child = false) => { + const page = pageFor(item) + const slug = slugFor(page) + const title = pages.get(slug)?.frontmatter?.title + if (!title) throw new Error(`navigation.yaml: no parsed title for ${page}`) + const active = samePage(fileData.slug, slug) + return h("a", { + href: resolveRelative(fileData.slug, slug), + class: `internal site-nav-link${child ? " site-nav-child" : ""}`, + "aria-current": active ? "page" : undefined, + "data-nav-slug": simplifySlug(slug), + }, labelFor(item)) + } + const entry = (item) => { + const page = pageFor(item) + const children = typeof item === "string" ? [] : item.pages || [] + if (!children.length) return h("li", { class: "site-nav-pillar", key: page }, link(item)) + const current = [page, ...children].some((child) => samePage(fileData.slug, slugFor(typeof child === "string" ? child : child.page))) + return h("li", { class: "site-nav-pillar", key: page }, + h("details", { class: "site-nav-section", open: current, "data-nav-pillar": simplifySlug(slugFor(page)) }, + h("summary", { "aria-label": `Toggle ${labelFor(item)} pages` }, link(item), chevron()), + h("ul", { class: "site-nav-children" }, children.map((child) => h("li", { key: pageFor(child) }, link(child, true)))))) + } + return h("nav", { class: "site-nav", "aria-label": "Site contents" }, + // Separate desktop and phone shells keep the phone panel closed with + // native details even when JavaScript is disabled. CSS shows one shell. + h("div", { class: "site-nav-desktop" }, h("ul", { class: "site-nav-list" }, navigation.map(entry))), + h("details", { class: "site-nav-mobile" }, + h("summary", null, h("span", null, "Contents"), chevron()), + h("ul", { class: "site-nav-list" }, navigation.map(entry)))) + } + Component.css = css + // micromorph preserves the user's details.open property. Reconcile only + // on real page navigation so the current pillar follows the new page. + Component.afterDOMLoaded = ` +document.addEventListener("nav", function(event) { + var slug = (event.detail.url || "").replace(/\\/index$/, "/"); + if (slug === "index" || slug === "") slug = "/"; + document.querySelectorAll(".site-nav").forEach(function(nav) { + nav.querySelectorAll("a[data-nav-slug]").forEach(function(link) { + if (link.dataset.navSlug === slug) link.setAttribute("aria-current", "page"); + else link.removeAttribute("aria-current"); + }); + nav.querySelectorAll(".site-nav-section").forEach(function(section) { + section.open = !!section.querySelector('[aria-current="page"]'); + }); + nav.querySelectorAll(".site-nav-mobile").forEach(function(panel) { panel.open = false; }); + }); +}); +` + return Component +} + +// The pinned v5 layout loader chooses one export per source entry. The +// adapter lets three YAML entries use their own options and positions. +export const SiteChrome = (options) => { + const constructors = { ReviewBanner, SiteLogo, SiteNav } + const constructor = constructors[options?.component] + if (!constructor) return () => null + return constructor() +} diff --git a/site/plugins/site-chrome/dist/components/style.css b/site/plugins/site-chrome/dist/components/style.css new file mode 100644 index 0000000..76137eb --- /dev/null +++ b/site/plugins/site-chrome/dist/components/style.css @@ -0,0 +1,95 @@ +/* Hide the automatic folder list; indexes carry their own agreed links. */ +.page-listing { display: none !important; } + +.review-banner { + --review-bg: #fff1d5; + --review-ink: #73440d; + --review-accent: #b77924; + box-sizing: border-box; + width: 100%; + margin: 0 0 1.5rem; + padding: .75rem 1rem; + background: var(--review-bg); + color: var(--review-ink); + border-left: 3px solid var(--review-accent); + border-bottom: 1px solid var(--review-accent); + font-family: var(--bodyFont); + font-size: .875rem; + font-weight: 600; + letter-spacing: .035em; + line-height: 1.5; +} +:root[saved-theme="dark"] .review-banner { + --review-bg: #302619; + --review-ink: #f3cd8a; + --review-accent: #c79445; +} +.site-logo { --site-logo-width: 208px; flex-shrink: 0; } +.site-logo a { display: block; width: var(--site-logo-width); max-width: 100%; background: none; line-height: 0; } +.site-logo img { display: block; width: 100%; height: auto; margin: 0; } +.site-logo .site-logo-dark { display: none; } +:root[saved-theme="dark"] .site-logo .site-logo-light { display: none; } +:root[saved-theme="dark"] .site-logo .site-logo-dark { display: block; } +/* The supplied SVG's wordmark begins at x≈250.5 in its 800-unit viewBox. + Align the site-name suffix there, beside the bottom of the square mark. */ +.site-logo-caption { display: block; margin-top: -.5rem; margin-left: 31.32%; color: var(--secondary); font-family: var(--bodyFont); font-size: 1rem; font-weight: 600; letter-spacing: normal; line-height: 1.2; } +.site-logo a:focus-visible, .site-nav a:focus-visible, .site-nav summary:focus-visible { + outline: 2px solid var(--secondary); + outline-offset: 3px; + border-radius: 2px; +} +.site-nav { font-family: var(--bodyFont); min-height: 0; overflow-y: auto; padding: .2rem 4px .5rem 0; scrollbar-color: var(--gray) var(--light); scrollbar-width: thin; } +.site-nav ul { padding: 0; margin: 0; list-style: none; } +.site-nav .site-nav-pillar { margin: 0 0 .65rem; } +.site-nav .site-nav-link { + display: block; + position: relative; + box-sizing: border-box; + padding: .6rem .75rem; + border-radius: 3px; + color: var(--darkgray); + background: none; + font-size: 1rem; + font-weight: 600; + line-height: 1.35; + text-decoration: none; + overflow-wrap: anywhere; +} +.site-nav .site-nav-link:hover { color: var(--secondary); background: var(--highlight); } +.site-nav .site-nav-link[aria-current="page"] { color: var(--secondary); background: var(--highlight); } +.site-nav .site-nav-link[aria-current="page"]::before { + content: ""; + position: absolute; + width: 3px; + top: .55rem; + bottom: .55rem; + left: 0; + background: var(--secondary); + border-radius: 2px; +} +.site-nav summary { display: flex; align-items: center; justify-content: space-between; list-style: none; cursor: pointer; color: var(--darkgray); } +.site-nav summary::-webkit-details-marker { display: none; } +.site-nav summary > a { flex: 1; min-width: 0; } +.site-nav-chevron { flex: 0 0 16px; margin: 0 .55rem; color: var(--secondary); } +.site-nav summary:hover > .site-nav-chevron { color: var(--tertiary); } +.site-nav details[open] > summary > .site-nav-chevron { transform: rotate(90deg); } +.site-nav .site-nav-children { border-left: 1px solid var(--lightgray); margin: .3rem 0 1rem .75rem; padding-left: .75rem; } +.site-nav .site-nav-children li { margin: .15rem 0; } +.site-nav .site-nav-child { font-size: .93rem; font-weight: 400; padding: .55rem .65rem; } +.site-nav .site-nav-child[aria-current="page"]::before { left: calc(-.75rem - 2px); } +.site-nav-mobile { display: none; } +@media (max-width: 800px) { + .page > #quartz-body > .sidebar.left { flex-wrap: wrap; gap: 1rem; padding-top: 1.5rem; padding-bottom: .5rem; } + .site-logo { --site-logo-width: 180px; flex: 1 1 150px; } + .site-logo-caption { margin-top: -.45rem; font-size: .9rem; } + .sidebar.left > .mobile-only { display: none; } + .sidebar.left > .flex-component { flex: 0 0 auto; } + .site-nav { flex: 1 0 100%; overflow: visible; padding: 0; } + .site-nav-desktop { display: none; } + .site-nav-mobile { display: block; border-top: 1px solid var(--lightgray); border-bottom: 1px solid var(--lightgray); } + .site-nav-mobile > summary { min-height: 48px; font-size: .95rem; font-weight: 600; padding: 0 .25rem; } + .site-nav-mobile > .site-nav-list { padding: .75rem 0 .25rem; } + .site-nav .site-nav-link { padding-top: .75rem; padding-bottom: .75rem; } + .site-nav .site-nav-pillar { margin-bottom: .25rem; } + .review-banner { margin: .75rem 0 1.25rem; padding: .65rem .8rem; font-size: .8rem; } +} diff --git a/site/plugins/site-chrome/dist/index.js b/site/plugins/site-chrome/dist/index.js new file mode 100644 index 0000000..38fca65 --- /dev/null +++ b/site/plugins/site-chrome/dist/index.js @@ -0,0 +1,2 @@ +// Component-only plugin; the layout adapter is exported from ./components. +export {} diff --git a/site/plugins/site-chrome/package.json b/site/plugins/site-chrome/package.json new file mode 100644 index 0000000..b0d9953 --- /dev/null +++ b/site/plugins/site-chrome/package.json @@ -0,0 +1,32 @@ +{ + "name": "site-chrome", + "version": "0.1.0", + "description": "Public site review banner, OIDM logo, and ordered navigation", + "type": "module", + "license": "MIT", + "main": "./dist/index.js", + "exports": { + ".": { "import": "./dist/index.js" }, + "./components": { "import": "./dist/components/index.js" } + }, + "peerDependencies": { + "preact": "^10.0.0", + "@quartz-community/utils": "^1.0.0", + "yaml": "^2.0.0" + }, + "quartz": { + "name": "site-chrome", + "displayName": "Site chrome", + "category": "component", + "version": "0.1.0", + "quartzVersion": ">=5.0.0", + "defaultEnabled": true, + "defaultOptions": {}, + "components": { + "SiteChrome": { "displayName": "Site chrome layout adapter" }, + "ReviewBanner": { "displayName": "Review banner", "defaultPosition": "beforeBody", "defaultPriority": 1 }, + "SiteLogo": { "displayName": "OIDM logo", "defaultPosition": "left", "defaultPriority": 10 }, + "SiteNav": { "displayName": "Site navigation", "defaultPosition": "left", "defaultPriority": 50 } + } + } +} diff --git a/site/quartz.config.yaml b/site/quartz.config.yaml index 20e3866..9bc9056 100644 --- a/site/quartz.config.yaml +++ b/site/quartz.config.yaml @@ -3,14 +3,15 @@ # Quartz config for the OIDM knowledge site. This file is copied into the # pinned Quartz checkout by site/build.sh; see site/README.md for the build. configuration: - pageTitle: OIDM knowledge + pageTitle: "Open Imaging Data Model: Knowledge" pageTitleSuffix: "" enableSPA: true enablePopovers: true analytics: provider: false locale: en-US - baseUrl: openimagingdata.github.io/oidm-knowledge + # site/build.sh replaces this with the --target's hostname (or SITE_BASE_URL). + baseUrl: oidm-knowledge-dev.talkasab.workers.dev ignorePatterns: - private - templates @@ -66,7 +67,7 @@ plugins: options: enableInHtmlEmbed: false enableCheckbox: true - mermaid: true + mermaid: false order: 30 - source: "@quartz-community/github-flavored-markdown" enabled: true @@ -80,13 +81,13 @@ plugins: - source: "@quartz-community/crawl-links" enabled: true options: - markdownLinkResolution: absolute + markdownLinkResolution: relative order: 60 - source: "@quartz-community/description" enabled: true order: 70 - source: "@quartz-community/latex" - enabled: true + enabled: false options: renderEngine: katex order: 80 @@ -127,15 +128,15 @@ plugins: - source: "@quartz-community/cname" enabled: true - source: "@quartz-community/canvas-page" - enabled: true + enabled: false - source: "@quartz-community/content-page" enabled: true - source: "@quartz-community/folder-page" enabled: true - source: "@quartz-community/tag-page" - enabled: true + enabled: false - source: "@quartz-community/explorer" - enabled: true + enabled: false layout: position: left priority: 50 @@ -168,12 +169,12 @@ plugins: position: beforeBody priority: 20 - source: "@quartz-community/tag-list" - enabled: true + enabled: false layout: position: beforeBody priority: 30 - source: "@quartz-community/page-title" - enabled: true + enabled: false layout: position: left priority: 10 @@ -223,14 +224,11 @@ plugins: priority: 25 display: mobile-only - source: "@quartz-community/bases-page" - enabled: true + enabled: false options: {} order: 50 - # This plugin's transformer is what parses YAML frontmatter into - # fileData.frontmatter in this Quartz fork (frontmatter parsing was moved - # out of core) - the okf-meta plugin below depends on it. We keep it - # enabled but hide its own properties panel so it doesn't duplicate the - # OKF badge row. + # Parse YAML frontmatter for page titles, descriptions, and navigation. + # Suppress the properties panel; the pages present their own content. - source: "@quartz-community/note-properties" enabled: true options: @@ -262,16 +260,35 @@ plugins: position: afterBody priority: 50 display: all - # OKF frontmatter badge row (type/status/verified/stale/sources). Source - # path is relative to the Quartz checkout directory (site/.quartz-src), - # so this resolves to site/okf-meta-plugin — see site/build.sh. - - source: ../okf-meta-plugin + # Hand-written local components, relative to site/.quartz-src. + - source: ../plugins/lightbox enabled: true - options: {} - order: 50 + layout: + position: afterBody + priority: 50 + # Quartz v5 selects one component per plugin entry. SiteChrome dispatches + # to the named component so this one package can occupy three positions. + - source: ../plugins/site-chrome + enabled: true + options: + component: ReviewBanner layout: position: beforeBody - priority: 15 + priority: 1 + - source: ../plugins/site-chrome + enabled: true + options: + component: SiteLogo + layout: + position: left + priority: 10 + - source: ../plugins/site-chrome + enabled: true + options: + component: SiteNav + layout: + position: left + priority: 50 layout: groups: toolbar: @@ -280,19 +297,25 @@ layout: gap: 0.5rem byPageType: "404": + template: default + exclude: + - "@quartz-community/article-title" + - "@quartz-community/content-meta" + - "@quartz-community/reader-mode" positions: - beforeBody: [] - left: [] right: [] - content: {} + content: + exclude: + - "@quartz-community/article-title" folder: exclude: - - reader-mode + - "@quartz-community/article-title" + - "@quartz-community/reader-mode" positions: right: [] tag: exclude: - - reader-mode + - "@quartz-community/reader-mode" positions: right: [] canvas: {} diff --git a/site/tools/check_site.py b/site/tools/check_site.py new file mode 100644 index 0000000..f42383c --- /dev/null +++ b/site/tools/check_site.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = ["pyyaml"] +# /// +"""Check public pages, local Markdown targets, navigation, and review stamps.""" +from __future__ import annotations + +import argparse +from collections import Counter +from pathlib import Path +import re +import sys +from urllib.parse import unquote, urlsplit + +import yaml + +SITE = Path(__file__).resolve().parents[1] +FRONTMATTER = re.compile(r"\A---\r?\n(.*?)\r?\n---(?:\r?\n|$)", re.S) + + +def read_page(path: Path) -> tuple[dict, str]: + text = path.read_text(encoding="utf-8") + match = FRONTMATTER.match(text) + if not match: + raise ValueError("missing frontmatter") + data = yaml.safe_load(match[1]) + if not isinstance(data, dict): + raise ValueError("frontmatter must be a mapping") + return data, text[match.end():] + + +def is_verified(data: dict) -> bool: + stamps = data.get("verified") + if isinstance(stamps, dict): + stamps = [stamps] + return isinstance(stamps, list) and any( + isinstance(stamp, dict) and stamp.get("by") and stamp.get("at") + for stamp in stamps + ) + + +def prose_lines(body: str): + """Ignore fenced code, preserving line numbers for useful diagnostics.""" + fence = None + for number, line in enumerate(body.splitlines(), 1): + marker = re.match(r"^\s{0,3}(`{3,}|~{3,})(.*)$", line) + if fence: + if marker and marker[1][0] == fence[0] and len(marker[1]) >= len(fence) and not marker[2].strip(): + fence = None + continue + if marker: + fence = marker[1] + if marker[2].strip().split(maxsplit=1)[:1] == ["mermaid"]: + yield number, None + continue + # Inline code is not a Markdown link either. + yield number, re.sub(r"(`+).*?\1", "", line) + + +def destination(text: str) -> str | None: + """Read a Markdown destination, including angle brackets and balanced ().""" + text = text.lstrip() + if text.startswith("<"): + end = text.find(">") + return text[1:end] if end >= 0 else None + depth = 0 + out = [] + escaped = False + for char in text: + if escaped: + out.append(char) + escaped = False + elif char == "\\": + escaped = True + elif char == "(": + depth += 1 + out.append(char) + elif char == ")": + if depth == 0: + break + depth -= 1 + out.append(char) + elif char.isspace() and depth == 0: + break + else: + out.append(char) + return "".join(out) + + +def link_targets(lines): + # Keep line boundaries so destinations wrapped onto the next line work. + text = "\n".join(line or "" for _, line in lines) + for number, line in lines: + if line is None: + continue + definition = re.match(r"^\s{0,3}\[([^\]^]+)\]:\s*(.*)$", line) + if definition: + # Check the shared destination once, at its definition. + yield number, destination(definition[2]) + for match in re.finditer(r"(? list[str]: + content = (site / "content").resolve() + pages = sorted(content.rglob("*.md")) + errors = [] + if not pages: + errors.append("content/: no Markdown pages found") + for page in pages: + name = "content/" + page.relative_to(content).as_posix() + try: + data, body = read_page(page) + except (ValueError, yaml.YAMLError, OSError) as error: + errors.append(f"{name}: {str(error).splitlines()[0]}") + continue + for field in ("title", "description"): + if not isinstance(data.get(field), str) or not data[field].strip(): + errors.append(f"{name}: missing non-empty {field}") + if target == "production" and not is_verified(data): + errors.append(f"{name}: missing verified entry for production") + lines = list(prose_lines(body)) + for number, line in lines: + if line is None: + errors.append(f"{name}: Mermaid fence at body line {number}") + for number, link in link_targets(lines): + if not link or link.startswith("#"): + continue + url = urlsplit(link) + if url.scheme or url.netloc: + continue + path = unquote(url.path) + resolved = (content / path.lstrip("/") if path.startswith("/") else page.parent / path).resolve() + if not resolved.is_relative_to(content) or not resolved.is_file(): + errors.append(f"{name}: broken local target {link!r} at body line {number}") + try: + navigation = yaml.safe_load((site / "navigation.yaml").read_text(encoding="utf-8")) + listed = Counter(navigation_pages(navigation, errors)) + expected = {page.relative_to(content).as_posix() for page in pages} + for path, count in sorted(listed.items()): + if path not in expected: + errors.append(f"navigation.yaml: {path}: page does not exist under content/") + if count != 1: + errors.append(f"navigation.yaml: {path}: listed {count} times") + for path in sorted(expected - listed.keys()): + errors.append(f"navigation.yaml: {path}: page is not listed") + except (yaml.YAMLError, OSError) as error: + errors.append(f"navigation.yaml: {str(error).splitlines()[0]}") + return errors + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--target", choices=("dev", "staging", "production"), default="dev") + args = parser.parse_args() + errors = check_site(SITE, args.target) + if errors: + print("\n".join(errors), file=sys.stderr) + return 1 + print(f"Site check passed ({args.target}, {len(list((SITE / 'content').rglob('*.md')))} pages)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/site/tools/verify.py b/site/tools/verify.py new file mode 100644 index 0000000..6b8fdec --- /dev/null +++ b/site/tools/verify.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = ["pyyaml"] +# /// +"""Write review stamps for site/content/ pages without rewriting other fields.""" +from __future__ import annotations + +import argparse +import datetime as dt +from pathlib import Path +import sys + +import yaml + +from check_site import FRONTMATTER, is_verified, read_page + +CONTENT = Path(__file__).resolve().parents[1] / "content" +REPO = CONTENT.parents[1] + + +def scoped_path(value: str, *, directory: bool = False) -> Path: + path = Path(value) + if not path.is_absolute(): + path = REPO / path + path = path.resolve() + if not path.is_relative_to(CONTENT.resolve()): + raise ValueError(f"{value}: must be under site/content/") + if directory: + if not path.is_dir(): + raise ValueError(f"{value}: not a directory") + elif path.suffix != ".md" or not path.is_file(): + raise ValueError(f"{value}: not a Markdown page") + return path + + +def field_span(frontmatter: str, key, value) -> tuple[int, int]: + start = frontmatter.rfind("\n", 0, key.start_mark.index) + 1 + end = value.end_mark.index + if value.end_mark.column: + newline = frontmatter.find("\n", end) + end = len(frontmatter) if newline < 0 else newline + 1 + # YAML block nodes can end at the next key, after intervening comments. + # Keep those comments and blank lines outside the edited field. + for line in reversed(frontmatter[start:end].splitlines(keepends=True)): + if line.strip() and not line.lstrip().startswith("#"): + break + end -= len(line) + return start, end + + +def marked_text(path: Path, by: str, now: str) -> str: + data, _ = read_page(path) + with path.open(encoding="utf-8", newline="") as source: + text = source.read() + match = FRONTMATTER.match(text) + assert match + newline = "\r\n" if text.startswith("---\r\n") else "\n" + fm = match[1] + newline + root = yaml.compose(fm) + fields = {key.value: (key, value) for key, value in root.value} + stamps = data.get("verified") or [] + if isinstance(stamps, dict): + stamps = [stamps] + if not isinstance(stamps, list) or any(not isinstance(stamp, dict) for stamp in stamps): + raise ValueError(f"{path}: verified must be a mapping or a list of mappings") + replacement = yaml.safe_dump( + {"verified": [*stamps, {"by": by, "at": now}]}, + sort_keys=False, allow_unicode=True, + ).replace("\n", newline) + if "verified" in fields: + start, end = field_span(fm, *fields["verified"]) + fm = fm[:start] + replacement + fm[end:] + elif "description" in fields: + _, end = field_span(fm, *fields["description"]) + fm = fm[:end] + replacement + fm[end:] + else: + raise ValueError(f"{path}: missing description") + return text[:match.start(1)] + fm.removesuffix(newline) + text[match.end(1):] + + +def report_status() -> None: + rows = {} + for path in sorted(CONTENT.rglob("*.md")): + data, _ = read_page(path) + relative = path.relative_to(CONTENT) + directory = relative.parts[0] if len(relative.parts) > 1 else "." + row = rows.setdefault(directory, [0, 0]) + row[0] += int(is_verified(data)) + row[1] += 1 + print(f"{'directory':28} verified / total") + for directory, (verified, total) in sorted(rows.items()): + print(f"{directory:28} {verified:3} / {total}") + print(f"{'all':28} {sum(row[0] for row in rows.values()):3} / {sum(row[1] for row in rows.values())}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("paths", nargs="*") + parser.add_argument("--by", default="human:talkasab") + parser.add_argument("--status", action="store_true") + parser.add_argument("--list", metavar="DIR") + args = parser.parse_args() + try: + if args.status: + report_status() + elif args.list: + for path in sorted(scoped_path(args.list, directory=True).rglob("*.md")): + data, _ = read_page(scoped_path(str(path))) + if not is_verified(data): + print(path.relative_to(REPO)) + elif args.paths: + if not args.by.strip(): + raise ValueError("--by must be non-empty") + now = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + # Validate all paths and frontmatter before writing any page. + changes = [(path, marked_text(path, args.by, now)) for path in dict.fromkeys(scoped_path(value) for value in args.paths)] + for path, text in changes: + path.write_text(text, encoding="utf-8", newline="") + print(f"verified {path.relative_to(REPO)} by {args.by} at {now}") + else: + parser.print_help() + return 2 + except (ValueError, OSError, yaml.YAMLError) as error: + print(str(error), file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/site/wrangler.jsonc b/site/wrangler.jsonc new file mode 100644 index 0000000..0cc6833 --- /dev/null +++ b/site/wrangler.jsonc @@ -0,0 +1,30 @@ +// Cloudflare Worker serving the built Quartz site as static assets. +// +// Three targets, all assets-only Workers on the same account: +// task site:deploy -> oidm-knowledge-dev (default; day-to-day) +// task site:deploy:staging -> oidm-knowledge-staging (also the push-to-main workflow) +// task site:deploy:prod -> oidm-knowledge (the public site) +// +// html_handling "auto-trailing-slash" is what Quartz expects: +// /foundation-context/relationships serves foundation-context/relationships.html +// and /foundation-context/ serves foundation-context/index.html. Worker logic +// (comments, endpoints) can be added later without changing the site build. +{ + "name": "oidm-knowledge", + "compatibility_date": "2026-09-21", + "assets": { + "directory": "../public", + "html_handling": "auto-trailing-slash", + "not_found_handling": "404-page" + }, + "env": { + "dev": { + "name": "oidm-knowledge-dev", + "assets": { "directory": "../public", "html_handling": "auto-trailing-slash", "not_found_handling": "404-page" } + }, + "staging": { + "name": "oidm-knowledge-staging", + "assets": { "directory": "../public", "html_handling": "auto-trailing-slash", "not_found_handling": "404-page" } + } + } +} diff --git a/tests/test_sources.py b/tests/test_sources.py new file mode 100644 index 0000000..b6baf17 --- /dev/null +++ b/tests/test_sources.py @@ -0,0 +1,110 @@ +import copy +import hashlib +import json +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + +from tools.sources import SourceError, load_manifest, recover, target_path + + +class SourceRecoveryTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.payload = b'original source bytes\n' + self.item = { + 'id': 'deck-original', 'source_id': 'deck', 'path': 'nested/deck.txt', + 'bucket_key': 'remote/deck.txt', 'size': len(self.payload), + 'sha256': hashlib.sha256(self.payload).hexdigest(), 'role': 'original', + 'description': 'Original deck', 'added_at': '2026-10-02T00:00:00Z', + 'derived_from': [], + } + self.data = { + 'schema_version': 1, 'bucket': 'test-bucket', + 'sources': [{'id': 'deck', 'title': 'Deck', 'sensitivity': 'internal', 'handling': 'Paraphrase'}], + 'files': [self.item], + } + + def load(self, data): + path = self.root / 'manifest.json' + path.write_text(json.dumps(data)) + return load_manifest(path) + + def test_paths_and_cli_globs_rejected_before_download(self): + for value in ['../escape', '/absolute', 'a/../../escape', 'a//b', 'a/./b', 'a\\b', '*.pdf', 'a?[0]', '-r', 'a\nb']: + for field in ['path', 'bucket_key']: + with self.subTest(value=value, field=field): + data = copy.deepcopy(self.data) + data['files'][0][field] = value + with self.assertRaises(SourceError): + self.load(data) + + def test_unknown_source_duplicate_and_derivative_cycle_rejected(self): + data = copy.deepcopy(self.data) + data['files'][0]['source_id'] = 'missing' + with self.assertRaises(SourceError): self.load(data) + data = copy.deepcopy(self.data) + data['files'].append(copy.deepcopy(self.item)) + with self.assertRaises(SourceError): self.load(data) + data = copy.deepcopy(self.data) + second = {**self.item, 'id': 'derived', 'path': 'b.txt', 'bucket_key': 'b.txt', 'role': 'extracted-text', 'derived_from': ['deck-original']} + data['files'][0]['derived_from'] = ['derived'] + data['files'].append(second) + with self.assertRaisesRegex(SourceError, 'cycle'): self.load(data) + + def test_verified_download_is_published_and_repeat_skips_network(self): + def fetch(bucket, item, destination): destination.write_bytes(self.payload) + with patch('tools.sources.download', side_effect=fetch) as mock: + self.assertEqual(recover(self.root, 'test-bucket', self.item), 'downloaded') + self.assertEqual(recover(self.root, 'test-bucket', self.item), 'present') + mock.assert_called_once() + self.assertEqual((self.root / self.item['path']).read_bytes(), self.payload) + + def test_corrupt_download_never_published_and_temp_removed(self): + with patch('tools.sources.download', side_effect=lambda b, i, p: p.write_bytes(b'corrupt')): + with self.assertRaisesRegex(SourceError, 'checksum mismatch'): + recover(self.root, 'test-bucket', self.item) + self.assertFalse((self.root / self.item['path']).exists()) + self.assertEqual(list(self.root.rglob('.source-download-*')), []) + + def test_existing_conflict_is_preserved_without_download(self): + target = self.root / self.item['path'] + target.parent.mkdir(); target.write_bytes(b'local edits') + with patch('tools.sources.download') as mock: + with self.assertRaisesRegex(SourceError, 'refusing to overwrite'): + recover(self.root, 'test-bucket', self.item) + mock.assert_not_called() + self.assertEqual(target.read_bytes(), b'local edits') + + def test_failed_transfer_cleans_partial_file(self): + def fail(bucket, item, destination): + destination.write_bytes(b'partial') + raise SourceError('connection failed') + with patch('tools.sources.download', side_effect=fail): + with self.assertRaises(SourceError): recover(self.root, 'test-bucket', self.item) + self.assertFalse((self.root / self.item['path']).exists()) + self.assertEqual(list(self.root.rglob('.source-download-*')), []) + + def test_symlink_parent_and_file_rejected(self): + other = self.root / 'outside'; other.mkdir() + (self.root / 'nested').symlink_to(other, target_is_directory=True) + with self.assertRaisesRegex(SourceError, 'symlink'): target_path(self.root, self.item) + (self.root / 'nested').unlink(); (self.root / 'nested').mkdir() + (self.root / self.item['path']).symlink_to(other / 'absent') + with self.assertRaisesRegex(SourceError, 'symlink'): target_path(self.root, self.item) + + def test_concurrent_destination_is_not_overwritten(self): + def fetch(bucket, item, destination): + destination.write_bytes(self.payload) + (self.root / item['path']).write_bytes(b'concurrent edit') + with patch('tools.sources.download', side_effect=fetch): + with self.assertRaisesRegex(SourceError, 'appeared during download'): + recover(self.root, 'test-bucket', self.item) + self.assertEqual((self.root / self.item['path']).read_bytes(), b'concurrent edit') + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/diagrams/README.md b/tools/diagrams/README.md new file mode 100644 index 0000000..0aae6ab --- /dev/null +++ b/tools/diagrams/README.md @@ -0,0 +1,97 @@ +# Diagrams + +Diagrams are Excalidraw files rendered to SVG. Mermaid is not used. + +## Output paths: `figures.toml` + +Where each figure is written is configuration, not code. [`figures.toml`](figures.toml) has one table per figure, keyed by figure name: + +- `builder`: the builder script +- `args`: extra builder arguments, if the figure needs them (the pillars seam) +- `excalidraw`: where the builder writes the `.excalidraw` +- `svg`: where the rendered SVG goes (omitted if the figure has no published SVG) + +Paths are relative to the repository root. The top-level `review_png_dir` holds every review PNG as `.png`. Builders call `excalib.figure("")` and save to its `excalidraw` path; no builder contains an output path. To move a figure, or to promote a candidate, change its entry and move the files. `build_pillars.py` and the `variants/` builders still accept `--out` as a one-off override. + +## Source-of-truth rule + +Per diagram, one of two things is the source: + +- **Builder-generated.** `build_.py` in this directory writes the `.excalidraw` file. Edit the builder, not the file. Iterate by rendering and looking at the PNG. +- **Hand-edited.** Once anyone edits a `.excalidraw` file directly (in Excalidraw or as JSON), that file becomes the source. Retire its builder in the same change: delete `build_.py` and add the diagram to the list below. + +Hand-edited diagrams (builders retired): none yet. + +`build_pillars.py` takes a required `--seam a|b|c` choosing how the Data +Structures and Foundation Context bands meet (named attachments, dovetail, +shared membrane); each seam is its own `pillars-` entry in `figures.toml`. The +v3 seam it replaced was rejected. The later v8 B3 candidate is pending owner +acceptance; see the [figure review state](../../site/ROADMAP.md#figures). +Do not promote any candidate over the accepted content asset without that review. + +## Rendering + +Build a figure from the repository root with `uv run tools/diagrams/` (plus its `args` from `figures.toml`, e.g. `--seam a`). + +Render from the excalidraw-diagram skill's references directory (it holds the Playwright environment): + +```bash +cd ~/.claude/skills/excalidraw-diagram/references +R=/home/talkasab/oidm-knowledge/tools/diagrams/render_excalidraw.py +uv run python $R --figure two-planes # one figure (repeat --figure for more) +uv run python $R --all # every figure in figures.toml +uv run python $R --figure two-planes --build # run the builder first +``` + +By name, the SVG goes to the figure's `svg` path, already made responsive (fixed `width`/`height` dropped from the root ``, `style="max-width:100%;height:auto"` added), and the PNG to `review_png_dir`. Explicit paths still work for one-off iterations: `uv run python $R .excalidraw -o --svg `; keep those previews under the ignored `scratch/review/` (exposed through `.peruseshow`), not in `sources/` or the plans directory. + +Then view the PNG, fix, re-render, until nothing crosses a box or a label and every label is readable. Embed the SVG with an image line plus an italic source line (see `knowledge/overview/architecture.md`). + +`render_template.html` loads the Excalidraw library from esm.sh pinned to 0.18.0; the unpinned build had a broken dependency. + +## Measuring text instead of estimating it + +`excalib.text()` estimates a string's width at `0.58 * fontSize` per character. That is fine for a label floating in open space, but it is wrong by 20-40% for real strings, so a layout packed to the pixel (a narrow canvas, a label that must not touch a lane) cannot be built on it. Measure instead: the render font is `Helvetica, Segoe UI Emoji` as resolved by headless Chromium, so a few lines of Playwright give exact widths. + +```python +# from the excalidraw-diagram skill's references directory, under `uv run` +from playwright.sync_api import sync_playwright +with sync_playwright() as p: + pg = p.chromium.launch(headless=True).new_page(); pg.goto("about:blank") + print(pg.evaluate("""(ss) => { const c = document.createElement('canvas').getContext('2d'); + c.font = '13px Helvetica, Segoe UI Emoji'; + return Object.fromEntries(ss.map(s => [s, c.measureText(s).width])); }""", + ["CT Chest WO contrast", "upper abdomen"])) +``` + +Size a box as `measured width + 20` (`box()` pads 10px each side) and pass the measured width to any helper that draws a backing box behind a label (`build_foundation_network.py` has `label_w` / `w` parameters for this). A label backing box that sits inside a tinted band should use the band's fill, not white. + +## Inspecting a render closely + +Viewing the whole PNG hides label-on-edge collisions. `render_template.html` can be driven directly to screenshot clipped regions at 2x: render as usual, then take the `#root svg` element's bounding box, divide its width by the `viewBox` width to get the scale, and screenshot `clip` rectangles expressed in diagram coordinates times that scale (the export adds 10px of padding on each side). Note that `exportToSvg` sizes the element by the browser's device pixel ratio while the `viewBox` stays in diagram units, so the scale must be read, not assumed. + +`excalib.py` holds the shared palette and element helpers (boxes, bound text, straight and elbowed arrows, lines, and image elements for embedded icons -- see below). + +## Icons + +Project logos and square marks are available in `assets/brand/`. +Use the original SVGs with `excalib.image()` and omit its `color` argument. +The [brand notes](../../docs/brand/README.md) give paths, proportions, and +embedding examples. Use these marks to identify the projects in a diagram. + +`excalib.image(id_, x, y, w, h, svg_path, color=None)` embeds an SVG file as an Excalidraw `image` element: it reads the file, optionally replaces every `currentColor` in the SVG source with a hex string (so one licensed icon file can be recolored per diagram without editing the file on disk), base64-encodes it into the document's top-level `files` map, and adds an element referencing that `fileId`. `render_template.html` already passed `files` through to `exportToSvg` before this was added, so no renderer change was needed to make icons show up in the rendered PNG/SVG -- this was checked, not assumed. + +Icon source files live in `tools/diagrams/icons/`. Their artwork is unchanged; +recoloring happens at build time. Lucide SVGs carry the full upstream license +notices in nonvisual metadata, which `image()` preserves in embedded copies. +[`icons/LICENSES.md`](icons/LICENSES.md) records their origins and links the +complete license text. Keep the metadata when copying or optimizing SVGs. +Share rendered PNGs with their generated `.LICENSE.txt` files. + +When adding an icon, prefer Health Icons or Lucide for consistent styling. +Record its origin and full required notices in `icons/LICENSES.md`, preserve +those notices in embedded copies, and identify its use in the builder docstring. + +## Node shape and color conventions (2026-09-30) + +Node kinds share one palette across figures (see excalib): FindingClass and Diagnosis green (Diagnosis darker), Grouping pale green with dashed stroke, AssessmentScheme purple OVAL (never a diamond, which reads as a flowchart decision), AnatomicLocation blue, exam types yellow, Modality grey, DataElement light rose, Measurement light violet, Subspecialty and other metadata Concept nodes grey-violet. Arrowheads are small solid triangles (excalib.tri_head). Edge labels use the schema's committed relationship names in 13px on boxed labels. diff --git a/tools/diagrams/build_architecture.py b/tools/diagrams/build_architecture.py new file mode 100644 index 0000000..4153886 --- /dev/null +++ b/tools/diagrams/build_architecture.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Build the OIDM architecture diagram (three layers joined by identifiers). +Three adjacent stacked bands (applications, data structures, semantic +foundation); Imaging Persona and CDEs-at-RadElement are compact second rows +within their own band (persona under Imaging Problem List at column 3, CDEs +under Finding models at column 1) rather than a separate trailing section, +with captions beside them instead of incoming arrows -- since neither sits at +column 1/2, they never block the foundation->data join arrows, which stay in +columns 1-2. Exam types -> Exam Finding List is also a caption, not an arrow, +to remove the one join that had no clean column-aligned path. Title/subtitle +and the "Also:" applications note are left for the page text. +Output: the "architecture" entry in figures.toml; render with render_excalidraw.py.""" +from excalib import PRIMARY, SECONDARY, TERTIARY, EXTERNAL, PLANNED, TITLE, SUBTITLE, BODY, LINE, els, base, text, box, find, arrow, hline, save, figure + +# ---------------------------------------------------------------- layout +# Box labels are hand-wrapped (Excalidraw does not auto-wrap bound text) so +# no line exceeds ~21 chars; that lets boxes stay narrow at 900px width. +W, GAP = 190, 26 # box width, horizontal gap between boxes in a row +C1 = 80 # left gutter (80px) -- wide clearance for the Forge -> Finding models line and the band labels, which both start at C1 +C2 = C1 + W + GAP # 296 +C3 = C2 + W + GAP # 512 +FORGE_X = 0 # off the C1 grid so its arrow can drop straight down the gutter, clear of Observation +CONTENT_W = C3 + W # 702 + +H_APP, H, H_EXT = 100, 80, 100 # row heights: 4-line boxes (app row, CDE row) need more height than 3-line boxes +ROW_GAP = 90 # band-to-band vertical gap +ROW_GAP2 = 60 # row1->row2 gap within a band (no arrow crosses it, so it can be tighter) +LABEL_BUF = 85 # extra clearance above the data-structures row so "per exam"/"per patient" have room above the boxes, not on them + +Y_APP = 40 +Y_DATA_HDR = Y_APP + H_APP + ROW_GAP # where the divider + "DATA STRUCTURES" label sit +Y_DATA = Y_DATA_HDR + LABEL_BUF # boxes start lower than usual, leaving that extra strip clear +Y_DATA2 = Y_DATA + H + ROW_GAP2 # Imaging Persona row (column 3 only) +Y_FOUND_HDR = Y_DATA2 + H + ROW_GAP +Y_FOUND = Y_FOUND_HDR + 24 +Y_FOUND2 = Y_FOUND + H + ROW_GAP2 # CDEs row (column 1 only) + +# ---------------------------------------------------------------- applications +els.append(text("band_app", C1, Y_APP - 24, "APPLICATIONS", size=14, color=SUBTITLE)) +box("forge", FORGE_X, Y_APP, W, H_APP, "Finding Model Forge\nauthoring wizard\nfmf.oidm.org", SECONDARY) +box("extract", C2, Y_APP, W, H_APP, "Report extraction\nplatform\nLLM extraction\nand coding (dev)", SECONDARY) +box("viewer", C3, Y_APP, W, H_APP, "Imaging Problem\nList viewer\nimaging-problem-\nlist.pages.dev", SECONDARY) + +# ---------------------------------------------------------------- data structures (adjacent to applications) +hline("div1", 0, Y_DATA_HDR - 6, CONTENT_W) +els.append(text("band_data", C1, Y_DATA_HDR, "DATA STRUCTURES", size=14, color=SUBTITLE)) +box("obs", C1, Y_DATA, W, H, "Observation\nfinding + location\n+ attributes", PRIMARY) +box("efl", C2, Y_DATA, W, H, "Exam Finding List\nall observations\nof one exam", PRIMARY) +box("ipl", C3, Y_DATA, W, H, "Imaging Problem List\none patient, grouped\nby finding", PRIMARY) +# labels sit in the LABEL_BUF strip above the boxes, clear of both the band +# label above and the box tops below (checked: label bottom edge stays ~25px +# above the box top, well clear of the rounded corners) +arrow("a_obs_efl", "obs", "right", "efl", "left", "per exam", label_dy=-88) +arrow("a_efl_ipl", "efl", "right", "ipl", "left", "per patient", label_dy=-88) + +# applications -> data structures: both adjacent rows, short vertical arrows. +# These lines got taller once LABEL_BUF pushed the data-structures row down, +# so the labels need more upward offset to clear the div1 divider below them. +arrow("a_extract_efl", "extract", "bottom", "efl", "top", "extracts from\nreports", label_dy=-50) +arrow("a_ipl_viewer", "ipl", "top", "viewer", "bottom", "renders", label_dy=-32) + +# Imaging Persona: second data-structures row, under Imaging Problem List +# (column 3) so it never sits over columns 1-2, where the foundation->data +# joins run -- caption to its left instead of an incoming arrow. +box("persona", C3, Y_DATA2, W, H, "Imaging Persona\nplus clinical\ncontext (concept)", PLANNED, dashed=True) +# caption BELOW persona, not beside: column 3 is clear all the way down, but +# columns 1-2 at this row height are the foundation->data join corridor +els.append(text("persona_note", C3, Y_DATA2 + H + 14, "goal: extends the Imaging\nProblem List with context", size=13, color=PLANNED[2])) + +# ---------------------------------------------------------------- semantic foundation (adjacent to data structures) +hline("div2", 0, Y_FOUND_HDR - 6, CONTENT_W) +els.append(text("band_found", C1, Y_FOUND_HDR, "SEMANTIC FOUNDATION", size=14, color=SUBTITLE)) +box("fm", C1, Y_FOUND, W, H, "Finding models\nOIFM_ / OIFMA_\ncodes", TERTIARY) +box("al", C2, Y_FOUND, W, H, "Anatomic locations\nRadLex RID codes", TERTIARY) +box("et", C3, Y_FOUND, W, H, "Exam types\nLOINC / Playbook\n(planned)", PLANNED, dashed=True) +els.append(text("et_note", C3, Y_FOUND + H + 14, "supplies the LOINC\nexam code on the\nExam Finding List", size=13, color=PLANNED[2])) + +# foundation -> data: both adjacent rows, so every join is a short, direct +# line -- straight vertical where columns line up, a short one-column +# diagonal where two foundation boxes both feed Observation. Labels sit to +# the RIGHT of their line (away from the left-gutter "authors" arrow). +# fm->obs binds at 0.92 across the box (not centered): centered, this line +# would run at column 1's midpoint, straight through the "SEMANTIC +# FOUNDATION" label text above it (the label is ~162px wide, wider than half +# the 190px box) -- shifted right, it clears the label with room to spare. +arrow("a_fm_obs", "fm", "top", "obs", "bottom", "OIFM and\nOIFMA codes", s_frac=0.92, d_frac=0.92, label_dx=-70, label_dy=-40) +arrow("a_al_obs", "al", "top", "obs", "bottom", "RID", d_frac=0.8, label_dx=44, label_dy=-40) + +# Forge (applications) authors finding models (semantic foundation), two +# rows down -- routed through the left gutter (x lung, +pleural effusion -> pleural space) are plumb-straight drops: their sources and +targets share an x-coordinate on purpose. Because of that, "thorax" is offset +left of "lung" (it doesn't need to share lung's column, just sit near it) so +it doesn't block the straight drop coming down through its own row. + + +--------------------------------------------------------+ + | Band 1 -- Sector 1: Finding / diagnosis definitions | + +--------------------------------------------------------+ + (gap, short "scoped to" verticals cross here) + +--------------------------------------------------------+ + | Band 2 -- Sector 2: Anatomic locations | + +--------------------------------------------------------+ + (gap, short "covers"/"included"/"edge (usually)" verticals) + +--------------------------------------------------------+ + | Band 3 -- Sector 3: Exam types | + +--------------------------------------------------------+ + +--------------------------------------------------------+ + | Legend | + +--------------------------------------------------------+ + +The only long edges are pulmonary nodule -> CT Chest ("seen on") and +pulmonary nodule -> CT Thoracic spine ("possibly seen on", dashed), which +run down the left and right margins respectively, outside all three bands, +each in its own lane, jogging in only at the very end. + +v8 change: the assessment-scheme node (Lung-RADS) is an oval, not a diamond -- +a diamond reads as a flowchart decision. Same fill, stroke and size envelope; +the legend swatch follows. Its incoming "assessed by" edge now aims at the +oval's leftmost point (d_frac 0.5), which is the one place on that side where +the bounding box and the ellipse's boundary coincide. + +v7 change: every arrowhead is a solid filled triangle drawn by +excalib.tri_head() at a fixed size, matching the two-planes figure -- grey +within-sector, red cross-sector, dashed edges included. Excalidraw's built-in +heads scale with strokeWidth, which made the thick cross-sector edges carry +heads half again too big. + +v6 changes: canvas narrowed from 1200px to 955px. Every x-coordinate below +was recomputed from measured text widths rather than estimated -- the +rendering font is `Helvetica, Segoe UI Emoji` as resolved by headless +Chromium, and the widths used here came from canvas `measureText` in that +same browser (see the note on Sector 3 below). Box widths are therefore +`measured text width + 20` (box() pads 10px each side), and the layout is +packed to a few pixels of slack in places, so changing a label's TEXT means +re-measuring, not just nudging a coordinate. + +Sector 3 and the "two lines per Playbook member" request: the five Playbook +members cannot all carry their name on one line inside a <=960px canvas. +Their names measure 131 / 137 / 188 / 243 / 187 px at 13px, so five one-line +names plus box padding and gaps need ~1030px of row; the row has ~830px +between the grey "further nodes" dots and the right margin lane. The three +"CT Chest ..." members do fit on one line and are set that way (name, then +code); "CTA Chest vessels WO and W contrast IV" and "CT Thoracic spine W +contrast IV" wrap their name over two lines and keep the code on the last +line. Shrinking text below 13px or abbreviating the Playbook names were the +only other ways to make all five one-line, and both were rejected. + +Output: the "foundation-network" entry in figures.toml; render with +render_excalidraw.py (see tools/diagrams/README.md). +""" +from __future__ import annotations + +from excalib import (TITLE, SUBTITLE, BODY, LINE, els, base, text, box, find, edge_point, + arrow, tri_head, save, figure) + +# ---------------------------------------------------------------- palette +FINDING = ("#d1fae5", "#059669", "#064e3b") # light green: finding +DIAGNOSIS = ("#6ee7b7", "#059669", "#064e3b") # darker green: diagnosis (subtly different fill) +ASSESS = ("#e9d5ff", "#7e22ce", "#4c1d95") # purple oval: assessment scheme +ANATOMY = ("#bfdbfe", "#1d4ed8", "#1e3a8a") # light blue: anatomic location +EXAM = ("#fde68a", "#b45309", "#78350f") # light amber: exam type +GREY = ("#e5e7eb", "#9ca3af", "#4b5563") # unlabeled / de-emphasized nodes +CROSS = "#be123c" # thick colored line: cross-sector relationship +BAND_BG = "#f8fafc" # sector band fill; label backings inside a band use it + +# Arrowheads are solid filled triangles drawn by excalib.tri_head(), matching +# the two-planes figure, not Excalidraw's built-in open heads: those are sized +# from the line's strokeWidth, so the thick cross-sector edges were forced to +# carry outsized heads. These are about 60% of that size and set independently +# of line weight. HEAD_X: cross-sector (red); HEAD_W: within-sector (grey). +HEAD_X, HEAD_W = 13.0, 10.0 + +# ---------------------------------------------------------------- helpers (local to this builder) +def region(id_: str, x: float, y: float, w: float, h: float, title: str) -> None: + """Faint background rectangle with a title, drawn first so nodes sit on top.""" + r = base("rectangle", id_, x, y, w, h, "#e2e8f0", BAND_BG, sw=1) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(id_ + "_title", x + 14, y + 10, title, size=15, color=TITLE)) + + +def edge(id_: str, *args, head: float = HEAD_W, **kwargs) -> None: + """excalib.arrow() with its open head replaced by a solid tri_head().""" + arrow(id_, *args, **kwargs) + a = find(id_) + a["endArrowhead"] = None + tip = (a["x"] + a["points"][-1][0], a["y"] + a["points"][-1][1]) + frm = (a["x"] + a["points"][-2][0], a["y"] + a["points"][-2][1]) + tri_head(id_ + "_h", tip, frm, a["strokeColor"], size=head) + + +def dot(id_: str, cx: float, cy: float, r: float, fill=GREY[0], stroke=GREY[1]) -> dict: + e = base("ellipse", id_, cx - r, cy - r, 2 * r, 2 * r, stroke, fill, sw=1) + els.append(e) + return e + + +def oval(id_: str, x: float, y: float, w: float, h: float, label: str, colors, size=13) -> dict: + """An ellipse node, inscribed in the given box. The bound text keeps clear + of the curve by sitting in the middle 60% of the width and 36% of the + height, where the ellipse is at or near its full extent.""" + fill, stroke, tcolor = colors + d = base("ellipse", id_, x, y, w, h, stroke, fill) + t = text(id_ + "_t", x + w * 0.2, y + h * 0.32, label, size=size, color=tcolor, align="center", w=w * 0.6, container=id_) + t["height"] = h * 0.36 + d["boundElements"] = [{"id": t["id"], "type": "text"}] + els.extend([d, t]) + return d + + +def faint(id_: str, x1: float, y1: float, x2: float, y2: float) -> None: + ln = base("arrow", id_, x1, y1, x2 - x1, y2 - y1, "#9ca3af", "transparent", sw=1) + ln.update({"points": [[0, 0], [x2 - x1, y2 - y1]], "startBinding": None, "endBinding": None, + "startArrowhead": None, "endArrowhead": None, "boundElements": None, "opacity": 40}) + els.append(ln) + + +def mpath(id_: str, pts: list[tuple[float, float]], color: str, dashed=False, sw=3, + label: str | None = None, label_pos: tuple[float, float] | None = None, label_color=BODY, + label_w: float | None = None, label_bg_color: str = "#ffffff", head: float = HEAD_X) -> None: + """Multi-point routed arrow (elbow arrow() only supports 3 points). + + label_w: MEASURED text width in px. The backing box is sized from it, so + passing a measured value (rather than a char-count estimate) is what keeps + a margin-lane label from pushing the canvas wider than the bands. + label_bg_color: BAND_BG for a label that lands inside a sector band, white + for one in a band gap -- a white patch on the tinted band is visible. + """ + x0, y0 = pts[0] + rel = [[px - x0, py - y0] for px, py in pts] + ar = base("arrow", id_, x0, y0, pts[-1][0] - x0, pts[-1][1] - y0, color, "transparent", dashed=dashed, sw=sw) + ar.update({"points": rel, "startBinding": None, "endBinding": None, + "startArrowhead": None, "endArrowhead": None, "boundElements": None}) + els.append(ar) + tri_head(id_ + "_h", pts[-1], pts[-2], color, size=head) + if label: + lw = label_w if label_w is not None else max(len(ln_) for ln_ in label.split("\n")) * 13 * 0.62 + lx, ly = label_pos + lh = len(label.split("\n")) * 13 * 1.25 + bg = base("rectangle", id_ + "_bg", lx - lw / 2 - 5, ly - 3, lw + 10, lh + 6, "transparent", label_bg_color, sw=0) + els.append(bg) + els.append(text(id_ + "_l", lx - lw / 2, ly, label, size=13, color=label_color, align="center", w=lw)) + + +def label_bg(id_: str, cx: float, cy: float, label: str, w: float, size=13, color=BODY, + bg_color: str = "#f8fafc") -> None: + """A standalone edge label with an opaque backing box so it never sits on + top of node text or an edge. w: MEASURED text width in px (see mpath). + bg_color defaults to the band fill, since every one of these sits inside a + band; pass white for a label placed in a band gap.""" + lines = label.split("\n") + bw = w + 10 + bh = len(lines) * size * 1.25 + 6 + bg = base("rectangle", id_ + "_bg", cx - bw / 2, cy - bh / 2, bw, bh, "transparent", bg_color, sw=0) + els.append(bg) + els.append(text(id_ + "_t", cx - bw / 2 + 5, cy - bh / 2 + 3, label, size=size, color=color, align="center", w=w)) + + +# ---------------------------------------------------------------- canvas / bands +CONTENT_W = 935 # + 2 x 10px export padding = 955px canvas +LANE_L = 8 # "seen on" lane, left margin +LANE_R = 912 # "possibly seen on" lane, just inside the right edge + +BAND1 = dict(x=0, y=70, w=CONTENT_W, h=300) # bottom 370 +GAP1 = 70 +BAND2 = dict(x=0, y=BAND1["y"] + BAND1["h"] + GAP1, w=CONTENT_W, h=370) # 440, bottom 810 +GAP2 = 70 +BAND3 = dict(x=0, y=BAND2["y"] + BAND2["h"] + GAP2, w=CONTENT_W, h=300) # 880, bottom 1180 +LEGEND = dict(x=0, y=BAND3["y"] + BAND3["h"] + 30, w=CONTENT_W, h=190) # 1210 + +els.append(text("maintitle", 0, 8, "Foundation Context: a mini-network", size=20, color=TITLE)) +els.append(text("subtitle", 0, 34, "three sectors of shared, curated knowledge — illustrative, not exhaustive", size=13, color=SUBTITLE)) + +region("rA", **BAND1, title="Sector 1 — Finding / diagnosis definitions") +region("rB", **BAND2, title="Sector 2 — Anatomic locations") +find("rB_title")["x"] += 40 # keep clear of the "seen on" lane running down x=8 +region("rC", **BAND3, title="Sector 3 — Exam types") +els.append(text("rC_tag", BAND3["x"] + 14, BAND3["y"] + 30, "(content to come)", size=13, color=BODY)) +region("rL", **LEGEND, title="Legend") + +# ================================================================== BAND 1: Finding / diagnosis definitions +# Row 1, left to right: lung cancer, pulmonary nodule, Lung-RADS, pleural +# effusion. Row 2, under pulmonary nodule: the three subtypes, spread wide for +# label room; solid component hangs below-right of part-solid, offset so it +# doesn't block pulmonary nodule's straight column down to lung. Grey "more" +# nodes sit at the far left of row 2. +COL_PN = 345 # pulmonary nodule / lung share this column (straight drop) +COL_PE = 708 # pleural effusion / pleural space share this one + +lung_cancer = box("lung_cancer", 20, 110, 140, 42, "lung cancer", DIAGNOSIS, size=14) +pulm_nodule = box("pulm_nodule", 272, 100, 146, 54, "pulmonary\nnodule", FINDING, size=14) # center 345 +lungrads = oval("lungrads", 492, 95, 125, 75, "Lung-RADS", ASSESS, size=13) +pleural_effusion = box("pleural_effusion", 637, 110, 142, 42, "pleural effusion", FINDING, size=13) # center 708 + +solid_nodule = box("solid_nodule", 120, 210, 105, 70, "solid\npulmonary\nnodule", FINDING, size=13) +partsolid_nodule = box("partsolid_nodule", 235, 210, 100, 70, "part-solid\npulmonary\nnodule", FINDING, size=13) +nonsolid_nodule = box("nonsolid_nodule", 530, 210, 100, 70, "non-solid\npulmonary\nnodule", FINDING, size=13) +solid_component = box("solid_component", 365, 295, 118, 38, "solid component", FINDING, size=13) + +dot("gA1", 50, 235, 15) +dot("gA2", 75, 270, 13) +dot("gA3", 55, 295, 12) +faint("fA_12", 50, 235, 75, 270) +faint("fA_13", 50, 235, 55, 295) +faint("fA_into", 75, 270, 120, 245) # gA2 -> solid pulmonary nodule (nearest labeled node) + +# The two left-hand "subtype of" labels sit BELOW the "seen on" lane's top +# horizontal (y=166) and to the LEFT of the red column at x=345; the right-hand +# one sits ABOVE the "possibly seen on" lane's top horizontal (y=190). That is +# what keeps the red pulmonary-nodule -> lung vertical off all three of them. +edge("e_solid_sub", "solid_nodule", "top", "pulm_nodule", "bottom", None, s_frac=0.5, d_frac=0.15) +edge("e_partsolid_sub", "partsolid_nodule", "top", "pulm_nodule", "bottom", None, s_frac=0.4, d_frac=0.5) +edge("e_nonsolid_sub", "nonsolid_nodule", "top", "pulm_nodule", "bottom", None, s_frac=0.2, d_frac=0.9) +# All three "subtype of" labels are white-backed and placed by hand: their own +# edges are diagonals that would otherwise run through the text, and the left +# two have to stay clear of the "seen on" lane above (y=166) and the red +# pulmonary-nodule column at x=345. +label_bg("l_solid_sub", 205, 189, "subtype of", 60.0) +label_bg("l_partsolid_sub", 290, 189, "subtype of", 60.0) +label_bg("l_nonsolid_sub", 464, 169, "subtype of", 60.0) # clear of the oval's lower-left curve +edge("e_partsolid_comp", "partsolid_nodule", "bottom", "solid_component", "top", "has component", s_frac=0.6, d_frac=0.3, + label_size=13, label_dx=60) +edge("e_manifest", "lung_cancer", "right", "pulm_nodule", "left", "may manifest as", s_frac=0.4, d_frac=0.35, + label_size=13, label_dy=-26) +edge("e_progress", "nonsolid_nodule", "left", "partsolid_nodule", "right", "may progress to", s_frac=0.5, d_frac=0.5, + dashed=True, label_size=13, label_dy=-24) +edge("e_assessed", "pulm_nodule", "right", "lungrads", "left", "assessed by", s_frac=0.25, d_frac=0.5, + label_size=13, label_dx=6, label_dy=-30) # -30: rides above the oval's upper-left curve +# pleural_effusion intentionally has no edge to pulm_nodule -- just a neighbor +# solid_component intentionally has no cross-sector "scoped to" edge -- only +# pulmonary nodule, lung cancer, and pleural effusion are scoped to anatomy + +# ================================================================== BAND 2: Anatomic locations +# thorax sits center-left, offset from lung's own column, on purpose: it leaves +# x=345 clear so pulmonary nodule's edge can drop straight into lung without +# detouring around thorax, AND leaves x=300 clear for lung cancer's arrival at +# lung's top-left. pleural space is pulled right, under pleural effusion, so +# ITS edge can also be a straight drop. +BY = BAND2["y"] +thorax = box("thorax", 160, BY + 40, 110, 42, "thorax", ANATOMY, size=14) # 160-270 +pleural_space = box("pleural_space", 654, BY + 40, 108, 44, "pleural\nspace", ANATOMY, size=13) # center 708 +upper_abdomen = box("upper_abdomen", 777, BY + 40, 115, 42, "upper abdomen", GREY, dashed=True, size=13) # ends 892, lane at 912 + +right_lung = box("right_lung", 150, BY + 110, 105, 40, "right lung", ANATOMY, size=13) +lung = box("lung", 280, BY + 110, 130, 42, "lung", ANATOMY, size=14) # center 345 +left_lung = box("left_lung", 435, BY + 110, 105, 40, "left lung", ANATOMY, size=13) + +# Row 3 is pushed down to BY+220 to open a clear horizontal corridor at BY+200 +# for the labels, and the gap between the two row-3 boxes (278..310) is the +# corridor the red "included" edge climbs to reach lung's bottom-left. +upper_lobe = box("upper_lobe", 140, BY + 220, 138, 42, "upper lobe of\nright lung", ANATOMY, size=13) +lung_parenchyma = box("lung_parenchyma", 310, BY + 220, 120, 44, "lung\nparenchyma", ANATOMY, size=13) + +dot("gB1", 50, BY + 250, 15) +dot("gB2", 75, BY + 285, 13) +dot("gB3", 55, BY + 310, 12) +faint("fB_12", 50, BY + 250, 75, BY + 285) +faint("fB_13", 50, BY + 250, 55, BY + 310) +faint("fB_into", 75, BY + 285, 140, BY + 245) # gB2 -> upper lobe of right lung (nearest labeled node) + +edge("e_rlung_lung", "right_lung", "right", "lung", "left", None, s_frac=0.5, d_frac=0.3) +edge("e_llung_lung", "left_lung", "left", "lung", "right", None, s_frac=0.5, d_frac=0.7) +edge("e_ulobe_rlung", "upper_lobe", "top", "right_lung", "bottom", None, s_frac=0.4, d_frac=0.4) +# lung parenchyma's edge runs into lung's BOTTOM-RIGHT (x=394), leaving the +# space under lung's center free for "laterality", which now sits between the +# red "included" arrowhead (x=294) and that edge -- on neither of them. +edge("e_parenchyma_lung", "lung_parenchyma", "top", "lung", "bottom", None, s_frac=0.7, d_frac=0.88) +edge("e_pspace_thorax", "pleural_space", "left", "thorax", "right", None, s_frac=0.5, d_frac=0.5) + +label_bg("l_ulobe_rlung", 197, BY + 172, "contained by", 73.7) +label_bg("l_laterality", 345, BY + 172, "laterality", 48.4) +label_bg("l_parenchyma_lung", 487, BY + 172, "part of", 36.9) +label_bg("l_pspace_thorax", 480, BY + 55, "contained by", 73.7) + +# ================================================================== BAND 3: Exam types +# Row 1: CT Chest (preferred, to come). Row 2, left to right: the four real +# Playbook entries with codes, then CT Thoracic spine (dashed) at the far +# right; grey "more" nodes at the far left. "Region imaged" hangs below +# "CT Chest W contrast IV". Box widths are measured-text + 20 and gaps are 8px +# -- see the module docstring on why only three of the five get a one-line name. +CY = BAND3["y"] +ct_chest = box("ct_chest", 285, CY + 40, 230, 60, "CT Chest\npreferred (to come)", EXAM, size=14) + +ct_wo = box("ct_wo", 90, CY + 130, 152, 70, "CT Chest WO contrast\n29252-4", EXAM, size=13) +ct_w = box("ct_w", 250, CY + 130, 158, 70, "CT Chest W contrast IV\n24628-0", EXAM, size=13) +ct_wo_w = box("ct_wo_w", 416, CY + 130, 209, 70, "CT Chest WO and W contrast IV\n30598-7", EXAM, size=13) +ct_cta = box("ct_cta", 633, CY + 130, 151, 70, "CTA Chest vessels\nWO and W contrast IV\n30804-9", EXAM, size=13) +ct_thoracic_spine = box("ct_thoracic_spine", 792, CY + 130, 126, 70, "CT Thoracic spine\nW contrast IV\n24979-7", GREY, dashed=True, size=13) + +region_imaged = box("region_imaged", 266, CY + 220, 126, 40, "Region imaged:\nThorax (RID1243)", GREY, size=13) + +dot("gC1", 42, CY + 165, 14) +dot("gC2", 64, CY + 198, 12) +faint("fC_12", 42, CY + 165, 64, CY + 198) +faint("fC_into", 42, CY + 165, 90, CY + 165) # gC1 -> CT Chest WO contrast (nearest labeled node) + +edge("e_wo_family", "ct_wo", "top", "ct_chest", "bottom", "member of family", s_frac=0.5, d_frac=0.1, + label_size=13, label_dx=-15, label_dy=-20) +edge("e_w_family", "ct_w", "top", "ct_chest", "bottom", None, s_frac=0.5, d_frac=0.35) +edge("e_wo_w_family", "ct_wo_w", "top", "ct_chest", "bottom", None, s_frac=0.5, d_frac=0.65) +edge("e_cta_family", "ct_cta", "top", "ct_chest", "bottom", None, s_frac=0.3, d_frac=0.9) +edge("e_w_regionimaged", "ct_w", "bottom", "region_imaged", "top", "Playbook part", s_frac=0.5, d_frac=0.5, + label_size=13, label_dx=107, label_dy=0) + +# ================================================================== cross-sector edges (thick, colored) +# Band 1 -> Band 2. +# "pulmonary nodule" and "lung" share x=345 -- a genuinely straight drop, clear +# of row 2's subtypes/solid component (both kept off that column), of all three +# "subtype of" labels, and of thorax (offset left of lung for exactly this +# reason). +mpath("cx_pn_lung", + [edge_point(find("pulm_nodule"), "bottom", 0.5), edge_point(find("lung"), "top", 0.5)], + color=CROSS, sw=3, label="scoped to", label_pos=(392, 396), label_w=56.4) + +# "lung cancer" drops from its own bottom, one right-angle jog at the midpoint +# of the band gap, then straight down into lung's top-left corner -- 45px left +# of pulmonary nodule's top-center arrival, so the two arrowheads land at +# separate points on "lung", not on top of each other. +mpath("cx_lc_lung", + [edge_point(find("lung_cancer"), "bottom", 0.5), (90, 405), (300, 405), + edge_point(find("lung"), "top", 0.15)], + color=CROSS, sw=3, label="scoped to", label_pos=(195, 393), label_w=56.4) + +# "pleural effusion" and "pleural space" also share an x (708) -- straight drop, +# since pleural space was pulled right specifically to sit under it. +mpath("cx_pe_pspace", + [edge_point(find("pleural_effusion"), "bottom", 0.5), edge_point(find("pleural_space"), "top", 0.5)], + color=CROSS, sw=3, label="scoped to", label_pos=(748, 286), label_w=56.4, label_bg_color=BAND_BG) + +# Band 3 -> Band 2 (pointing up): each uses its own left/right-margin column +# through band 2 so it clears the nodes directly in its way. "included" climbs +# the 32px corridor between the two row-3 boxes rather than cutting across the +# label row, so it crosses neither "contained by" nor the upper-lobe edge. +mpath("cx_ctchest_thorax", + [edge_point(find("ct_chest"), "top", 0.15), (319.5, 846), (100, 846), (100, BY + 61), + edge_point(find("thorax"), "left", 0.5)], + color=CROSS, sw=3, label="covers", label_pos=(66, 814), label_w=38.3) + +mpath("cx_ctchest_lung", + [edge_point(find("ct_chest"), "top", 0.3), (354, 868), (118, 868), (118, BY + 290), (294, BY + 290), + edge_point(find("lung"), "bottom", 0.11)], + color=CROSS, sw=3, label="included", label_pos=(160, 814), label_w=48.4) + +mpath("cx_ctchest_abdomen", + [edge_point(find("ct_chest"), "top", 0.85), (480.5, 846), (834.5, 846), + edge_point(find("upper_abdomen"), "bottom", 0.5)], + color=CROSS, dashed=True, sw=2, label="edge (usually)", label_pos=(834.5, 683), label_w=81.7, label_bg_color=BAND_BG) + +# The two long edges run down dedicated margin lanes outside all three bands, +# jogging in only once, right at the end. The right lane turns in above band 3's +# exam row, so CT Thoracic spine can use the full width up to the margin. +mpath("cx_pn_ctchest", + [edge_point(find("pulm_nodule"), "left", 1.0), (272, 166), (LANE_L, 166), (LANE_L, CY + 70), + edge_point(find("ct_chest"), "left", 0.5)], + color=CROSS, sw=3, label="seen on", label_pos=(48, 592), label_w=46.3, label_bg_color=BAND_BG) + +mpath("cx_pn_ctspine", + [edge_point(find("pulm_nodule"), "right", 1.0), (418, 190), (LANE_R, 190), (LANE_R, CY + 70), (855, CY + 70), + edge_point(find("ct_thoracic_spine"), "top", 0.5)], + color=CROSS, dashed=True, sw=2, label="possibly seen on", label_pos=(872, 392), label_w=96.8) + +# ================================================================== legend (full-width strip, 2 columns) +node_rows = [ + ("rect", FINDING, "finding"), + ("rect", DIAGNOSIS, "diagnosis"), + ("oval", ASSESS, "assessment scheme"), + ("rect", ANATOMY, "anatomic location"), + ("rect", EXAM, "exam type"), +] +edge_rows = [ + ("dot", GREY, "unlabeled grey = further nodes not shown"), + ("line", LINE, "solid edge = within-sector relationship"), + ("thick", CROSS, "colored thick edge = cross-sector relationship"), + ("dash", LINE, "dashed = possible or planned"), +] + +LX1, LX2 = LEGEND["x"] + 24, LEGEND["x"] + CONTENT_W // 2 + 10 +LY = LEGEND["y"] + 34 + +y = LY +for i, (kind, colors, label) in enumerate(node_rows): + fill, stroke, _tc = colors + if kind == "rect": + sw_el = base("rectangle", f"legA{i}", LX1, y, 22, 16, stroke, fill, sw=1) + sw_el["roundness"] = {"type": 3} + els.append(sw_el) + elif kind == "oval": + els.append(base("ellipse", f"legA{i}", LX1, y - 1, 24, 18, stroke, fill)) + els.append(text(f"legA{i}_l", LX1 + 32, y - 1, label, size=13, color=BODY, w=340)) + y += 28 + +y = LY +for i, (kind, color, label) in enumerate(edge_rows): + if kind == "dot": + dot(f"legB{i}", LX2 + 11, y + 8, 9, fill=color[0], stroke=color[1]) + elif kind == "line": + els.append(base("line", f"legB{i}", LX2, y + 8, 30, 0, color, "transparent", sw=2)) + find(f"legB{i}")["points"] = [[0, 0], [30, 0]] + find(f"legB{i}")["boundElements"] = None + elif kind == "thick": + els.append(base("line", f"legB{i}", LX2, y + 8, 30, 0, color, "transparent", sw=3)) + find(f"legB{i}")["points"] = [[0, 0], [30, 0]] + find(f"legB{i}")["boundElements"] = None + elif kind == "dash": + els.append(base("line", f"legB{i}", LX2, y + 8, 30, 0, color, "transparent", sw=2, dashed=True)) + find(f"legB{i}")["points"] = [[0, 0], [30, 0]] + find(f"legB{i}")["boundElements"] = None + els.append(text(f"legB{i}_l", LX2 + 40, y - 1, label, size=13, color=BODY, w=340)) + y += 28 + +save(figure("foundation-network").excalidraw) diff --git a/tools/diagrams/build_hierarchy.py b/tools/diagrams/build_hierarchy.py new file mode 100644 index 0000000..050ea6e --- /dev/null +++ b/tools/diagrams/build_hierarchy.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Build the data-structure ladder: Observation, Exam Finding List, Imaging +Problem List, Imaging Persona, with the fields each carries and its status. +Recomposed as a vertical ladder (Observation on top, Imaging Persona at the +bottom) with each box's field list set to its right, to fit a ~900px column. +Title/subtitle are left for the page text. +Output: the "hierarchy" entry in figures.toml""" +from excalib import PRIMARY, SECONDARY, TERTIARY, EXTERNAL, PLANNED, TITLE, SUBTITLE, BODY, LINE, els, base, text, box, find, arrow, hline, save, figure + +BOX_X = 0 +BOX_W, BOX_H = 250, 70 +TICK_W = 30 # short connector from box's right edge to its field list +FIELD_X = BOX_X + BOX_W + TICK_W +ARROW_GAP = 74 # vertical space between one box's bottom and the next box's top +FIELD_SIZE, STATUS_SIZE = 14, 13 +CONTENT_W = FIELD_X + 460 # keeps field-list lines within this width + +rows = [ + ("obs", "Observation\none finding, one exam", PRIMARY, False, + ["findingCode (OIFM)", "findingDescription", "anatomicLocation (RID)", "attributes[] with value codes", "reportText"], + "JSON in use; no published schema", SUBTITLE), + ("efl", "Exam Finding List\nevery observation of one exam", PRIMARY, False, + ["diagnosticReportId", "patientInfo", "examInfo with LOINC exam code", "findings[]: the observations"], + "JSON in use; no published schema", SUBTITLE), + ("ipl", "Imaging Problem List\none patient, keyed by finding", PRIMARY, False, + ["patient", "findings[] keyed by", "(findingCode, locationId)", "each with dated observations[]", "status derived, not stored"], + "JSON in use; grouping changed on dev", SUBTITLE), + ("persona", "Imaging Persona\nplus clinical context", PLANNED, True, + ["clinical context (orders, indications)", "medical baseline (problems, labs)", "specialized history (oncology)", "surgical history (operative, implants)"], + "concept only, no artifact", PLANNED[2]), +] +arrow_labels = ["per exam", "all exams", "with context"] + +y = 40 +prev_id = None +for i, (bid, label, colors, dashed, fields, status, status_color) in enumerate(rows): + box(bid, BOX_X, y, BOX_W, BOX_H, label, colors, dashed=dashed) + + field_h = len(fields) * FIELD_SIZE * 1.25 + fld_text = "\n".join(fields) + els.append(text(f"{bid}_fields", FIELD_X, y, fld_text, size=FIELD_SIZE, color="#374151")) + els.append(text(f"{bid}_status", FIELD_X, y + field_h + 8, status, size=STATUS_SIZE, color=status_color)) + tick = base("line", f"{bid}_tick", BOX_X + BOX_W, y + BOX_H / 2, TICK_W, 0, LINE, "transparent", sw=1) + tick.update({"points": [[0, 0], [TICK_W, 0]], "boundElements": None}) + els.append(tick) + + content_h = max(BOX_H, field_h + 8 + STATUS_SIZE * 1.25) + if prev_id is not None: + arrow(f"a_{prev_id}_{bid}", prev_id, "bottom", bid, "top", arrow_labels[i - 1], + label_dx=BOX_W / 2 + 14, label_dy=-12, dashed=dashed) + prev_id, y = bid, y + content_h + ARROW_GAP + +Y_NOTE = y - ARROW_GAP + 40 +els.append(text("note", 0, Y_NOTE, "Codes come from the semantic foundation: OIFM and OIFMA identifiers from finding\nmodels, RID from anatomic locations, LOINC for the exam.", size=13, color=BODY)) + +save(figure("hierarchy").excalidraw) diff --git a/tools/diagrams/build_nodule_neighborhood.py b/tools/diagrams/build_nodule_neighborhood.py new file mode 100644 index 0000000..9d4a5f9 --- /dev/null +++ b/tools/diagrams/build_nodule_neighborhood.py @@ -0,0 +1,411 @@ +#!/usr/bin/env python3 +"""Build the "pulmonary nodule neighborhood" diagram (v3). + +One FindingClass and everything that hangs off it, drawn as nodes and labeled +edges. The point of the figure is the SHAPE of the neighborhood -- which kinds +of thing attach to a finding definition, and by which named relationship -- +not the content of any one node, so every node carries only its name plus its +node kind on a small second line. + +v2 IS ILLUSTRATIVE, NOT A DUMP OF THE GRAPH +------------------------------------------- +v1 drew only edges committed to the CDE graph. v2 reflects updates the +project's clinical expert intends for that graph, so some nodes, names and +edges below do not exist at the pin. The figure says so on a caption line +under the legend, and the two lists in the next section say exactly which is +which. Nothing here was invented by the builder: every "intended" item came +from the project lead by way of the brief for this version. + +SOURCE OF THE COMMITTED DATA +---------------------------- +~/ACR-RSNA-CDEs, branch next-gen-2026, pinned commit +b541b74c22a33b64834309244569a60d06de79e4, file (read with `git show`, i.e. +the committed copy, not the working tree): + + docs/next-gen-schema/alpha/graph/definition-graph.json + +Centre node: FC-000005 "pulmonary nodule" (node: FindingClass). + +COMMITTED AT b541b74 -- 15 of the 22 edges drawn +------------------------------------------------ + SUBTYPE_OF 2 FC-000006 solid pulmonary nodule -> FC-000005 + FC-000007 part-solid pulmonary nodule -> FC-000005 + HAS_COMPONENT 1 FC-000007 -> FC-000009 solid component of part-solid + pulmonary nodule (the graph also carries the inverse + COMPONENT_OF, not drawn) + HAS_DATA_ELEMENT 3 FC-000005 -> DE-000001 presence, DE-000004 + calcification, DE-000015 interval change + HAS_MEASUREMENT 2 FC-000005 -> MS-000003 mean diameter, + MS-000001 long-axis diameter + ASSESSED_BY 1 FC-000005 -> AS-000001 Lung-RADS + SCOPED_TO 1 FC-000005 -> RID1301 lung + props: kind=region, strength=required + SEEN_ON 3 FC-000005 -> CT, XR, MR (the graph also has US and + PET on this finding; those two are not drawn) + IN_SUBSPECIALTY 1 FC-000005 -> CH + MAY_MANIFEST_AS 0 see below -- both drawn manifestation edges are + intended, neither is committed in the form drawn. + plus SUBTYPE_OF 2 drawn but NOT committed (see below), for 22 total. + +INTENDED, NOT AT THE PIN -- 7 edges and the nodes they need +------------------------------------------------------------ + Renamed data elements. "composition" replaces DE-000002 attenuation on this + finding, and "margin" replaces DE-000039 pulmonary margin. Both + HAS_DATA_ELEMENT edges are committed under the old names. A separate node + already named "composition" (DE-000006) exists in the graph for other + findings, so the rename points at a name the vocabulary already uses. + Dropped measurement. MS-000006 lesion count is a committed HAS_MEASUREMENT + target of FC-000005; removing it is the intended change. + Renamed subtype. "ground glass nodule" renames FC-000008 non-solid + pulmonary nodule. FC-000025 "ground-glass opacity" is a DIFFERENT node + that stays where it is. + Grouping node. "pulmonary parenchymal abnormality" and BOTH SUBTYPE_OF + edges into it (from pulmonary nodule and from pulmonary granuloma) are + intended. The alpha contains no Grouping nodes at all at this commit. + Diagnosis "pulmonary neoplasm" and its MAY_MANIFEST_AS -> pulmonary nodule. + No such node exists at the pin. The committed diagnoses that reach this + family are DX-000004 lung cancer, which manifests as all three SUBTYPES, + and DX-000009 metastatic disease, the only one with a direct edge into + FC-000005 (that was the diagnosis v1 drew). + Retargeted manifestation. DX-000002 pulmonary granuloma is committed, but + its MAY_MANIFEST_AS points at FC-000006 solid pulmonary nodule, not at + the parent. Pointing it at FC-000005 is the intended change. + AssessmentScheme "Fleischner criteria" and its ASSESSED_BY edge. Not at the + pin; the five committed schemes are Lung-RADS, ACR TI-RADS, LI-RADS, + Bosniak classification and BI-RADS assessment. + +Two labels are committed content shown in a reader-friendly form rather than +changed content. The Subspecialty node's `name` is the code "CH" and its +`definition` is "Chest Radiology"; the box shows the definition, since "CH" +alone says nothing. The Modality nodes carry RadLex definitions (Computed +Tomography, Projection Radiography, Magnetic Resonance Imaging) that do not +fit half-size boxes, so those three show the committed ids only. + +Not drawn at all, by instruction: values, time course, etiology. Also left +out: HAS_ANATOMIC_REFINEMENT_RULE -> ARR-000001, whose target is a node kind +outside the nine the legend names. + +RELATIONSHIP NAMES +------------------ +Every edge label is the committed `edge` string from definition-graph.json, +and all nine agree with the relationship table on +site/content/foundation-context/next-generation-schema.md. The older names that circulate +elsewhere in the same repository are NOT used: HAS_ELEMENT (for +HAS_DATA_ELEMENT) and MAY_HAVE_COMPONENT (for HAS_COMPONENT), both of which +survive in graph/core.jsonl and in the retired 2026-09-03 fc-neighborhood +diagram. The schema page flags that older pair itself. + +LAYOUT +------ +About 1050 x 820. v1 packed eight arrows out of the centre node's right edge +into a 180px corridor; v2 has fewer targets there (five data elements, two +measurements) AND a corridor half again as wide, so the fan opens at roughly +39px of label pitch instead of 26px. The centre node sits mid-canvas with the +Grouping directly above it, the two assessment schemes up and to the right, +the two diagnoses up and to the left, the subtype row below, and the +context nodes -- lung, three modalities, the subspecialty -- down the left +margin. + +Two routing facts hold the left margin together. The diagnosis that also +points UP at the Grouping (granuloma) sits ABOVE the one that only points at +the nodule (neoplasm); reversing them makes the two arrows cross. And the +SCOPED_TO arrow leaves the centre node BELOW the point where the neoplasm's +manifestation arrow arrives, which is what keeps those two apart. + +Every arrow is emitted before every label plate: the plates are opaque and +mask the lines they lie across, so an arrow drawn after a plate would cut +through the label's text. + +Text widths are measured, not estimated (see tools/diagrams/README.md): the +render font is `Helvetica, Segoe UI Emoji` as resolved by headless Chromium, +and every box width and plate width below comes from canvas measureText() at +the same size in that browser. + +Colours follow build_foundation_network.py so the figures name the same kinds +the same way: FINDING green, DIAGNOSIS darker green, ASSESS purple, +ANATOMY blue, GREY modality. Four kinds are local to this figure: DataElement +takes the lightest step of excalib's ROSE ramp, Measurement the lightest step +of VIOLET, Grouping a paler green than FINDING with a dashed stroke (it is a +container, not a finding you would report), and Subspecialty a desaturated +grey-violet that reads as metadata next to both. + +v3 change: the two AssessmentScheme nodes are OVALS, not diamonds. A diamond +reads as a flowchart decision, which is not what an assessment scheme is. +Same purple fill and stroke, same 190x92 envelope, labels still centred, and +the legend swatch is an oval to match. Both ASSESSED_BY arrows are drawn by +ray() so their heads land on the ellipse outline rather than on a +bounding-box extreme; see that helper for why excalib.arrow() cannot. + +Excalidraw has no italic font variant -- fontFamily is an integer code with +no style axis -- so the caption under the legend is set small and grey rather +than italic. + +Output: the "nodule-neighborhood" entry in figures.toml; render with +render_excalidraw.py (see tools/diagrams/README.md). +""" +from __future__ import annotations + +from excalib import (BODY, LINE, CAPTION, els, base, text, find, edge_point, arrow, + tri_head, save, figure) + +# ---------------------------------------------------------------- palette +# (fill, stroke, text, mid) -- "mid" is the tone for the small kind line. +FINDING = ("#d1fae5", "#059669", "#064e3b", "#047857") # FindingClass +GROUPING = ("#ecfdf5", "#10b981", "#065f46", "#059669") # Grouping (dashed) +DIAGNOSIS = ("#6ee7b7", "#059669", "#064e3b", "#047857") # Diagnosis +ASSESS = ("#e9d5ff", "#7e22ce", "#4c1d95", "#7e22ce") # AssessmentScheme (oval) +ANATOMY = ("#bfdbfe", "#1d4ed8", "#1e3a8a", "#1d4ed8") # AnatomicLocation +GREY = ("#e5e7eb", "#6b7280", "#374151", "#4b5563") # Modality +DATAEL = ("#ffe4e6", "#be123c", "#881337", "#be123c") # DataElement (ROSE, lightest) +MEAS = ("#ede9fe", "#6d28d9", "#4c1d95", "#6d28d9") # Measurement (VIOLET, lightest) +SUBSPEC = ("#e5e3ee", "#78748f", "#3c3a52", "#5d5a78") # Subspecialty (grey-violet) + +HEAD = 10.0 # solid tri_head length, independent of line weight +KIND_SIZE = 11 # the small second line inside every node + + +# ---------------------------------------------------------------- helpers +def kind_box(id_, x, y, w, h, name, kind, pal, size=14, lines=None, dashed=False, + kind_size=KIND_SIZE): + """A node: rounded rect, name centred, node kind on a small line under it. + `lines` overrides the name's line breaks (list of strings).""" + fill, stroke, tcol, mid = pal + r = base("rectangle", id_, x, y, w, h, stroke, fill, dashed=dashed) + r["roundness"] = {"type": 3} + els.append(r) + nl = lines or [name] + block = len(nl) * size * 1.25 + 3 + kind_size * 1.25 + ty = y + (h - block) / 2 + els.append(text(id_ + "_n", x + 8, ty, "\n".join(nl), size=size, color=tcol, + align="center", w=w - 16)) + els.append(text(id_ + "_k", x + 8, ty + len(nl) * size * 1.25 + 3, kind, + size=kind_size, color=mid, align="center", w=w - 16)) + return r + + +def oval_node(id_, x, y, w, h, name, kind, pal, size=14): + """Same two-line content as kind_box, in an ellipse. v3 change: these were + diamonds through v2, which read as flowchart decisions. Same fill, stroke + and size envelope; only the outline changed. + + The text column is the middle 72% of the width. An ellipse is widest at + its vertical centre and the two-line block reaches only +-17px from it, + where the half-width is still 93% of the semi-axis, so 72% clears the + curve with room to spare (a diamond only allowed 60%).""" + fill, stroke, tcol, mid = pal + d = base("ellipse", id_, x, y, w, h, stroke, fill) + els.append(d) + block = size * 1.25 + 3 + KIND_SIZE * 1.25 + ty = y + (h - block) / 2 + els.append(text(id_ + "_n", x + w * 0.14, ty, name, size=size, color=tcol, + align="center", w=w * 0.72)) + els.append(text(id_ + "_k", x + w * 0.14, ty + size * 1.25 + 3, kind, + size=KIND_SIZE, color=mid, align="center", w=w * 0.72)) + return d + + +def ray(id_, src, s_side, s_frac, dst, label, t): + """A straight arrow from a box edge to a point ON an ellipse's outline. + + excalib.arrow() lands on the destination's BOUNDING BOX, which for an + ellipse means the head stops at whichever of the four extreme points the + named side picks -- fine for a vertical or horizontal approach, wrong for + a diagonal one, which is what both ASSESSED_BY arrows are. This instead + solves for the boundary point along the line from the ellipse's centre to + the source, so each head meets the curve square on. `t` places the label + along the same line, so moving an endpoint moves its label with it.""" + a, b = find(src), find(dst) + x1, y1 = edge_point(a, s_side, s_frac) + cx, cy = b["x"] + b["width"] / 2, b["y"] + b["height"] / 2 + rx, ry = b["width"] / 2, b["height"] / 2 + dx, dy = x1 - cx, y1 - cy + k = 1.0 / ((dx / rx) ** 2 + (dy / ry) ** 2) ** 0.5 + x2, y2 = cx + dx * k, cy + dy * k + ln = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5 or 1.0 + ex = x2 - (x2 - x1) / ln * HEAD * 0.55 # stop short so the head's base sits on the line + ey = y2 - (y2 - y1) / ln * HEAD * 0.55 + ar = base("arrow", id_, x1, y1, ex - x1, ey - y1, LINE, "transparent", sw=2) + ar.update({"points": [[0, 0], [ex - x1, ey - y1]], "startBinding": None, + "endBinding": None, "startArrowhead": None, "endArrowhead": None, + "boundElements": None}) + els.append(ar) + tri_head(id_ + "_h", (x2, y2), (x1, y1), LINE, size=HEAD) + lab("l_" + id_, x1 + t * (x2 - x1), y1 + t * (y2 - y1), label) + + +def edge(id_, src, s_side, dst, d_side, s_frac=0.5, d_frac=0.5, elbow=None): + """excalib.arrow() with its open head replaced by a fixed-size solid one.""" + arrow(id_, src, s_side, dst, d_side, s_frac=s_frac, d_frac=d_frac, + color=LINE, sw=2, elbow=elbow) + a = find(id_) + a["endArrowhead"] = None + tip = (a["x"] + a["points"][-1][0], a["y"] + a["points"][-1][1]) + frm = (a["x"] + a["points"][-2][0], a["y"] + a["points"][-2][1]) + tri_head(id_ + "_h", tip, frm, LINE, size=HEAD) + + +def plate(id_, cx, cy, s, w, size=13, pad_x=6, pad_y=4): + """An edge label on an opaque white plate centred on (cx, cy), so it masks + the line it sits on. `w` is the MEASURED text width.""" + bw, bh = w + 2 * pad_x, size * 1.25 + 2 * pad_y + r = base("rectangle", id_, cx - bw / 2, cy - bh / 2, bw, bh, "transparent", "#ffffff", sw=0) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(id_ + "_t", cx - bw / 2 + pad_x, cy - bh / 2 + pad_y, s, + size=size, color=BODY, align="center", w=w)) + + +# Measured label widths at 13px Helvetica (headless Chromium). +LW = {"MAY_MANIFEST_AS": 124.7, "ASSESSED_BY": 94.7, "SUBTYPE_OF": 86.0, + "HAS_DATA_ELEMENT": 135.3, "HAS_MEASUREMENT": 135.1, "SCOPED_TO": 79.9, + "SEEN_ON": 62.1, "HAS_COMPONENT": 118.5, "IN_SUBSPECIALTY": 117.5} + +LABELS: list[tuple[str, float, float, str]] = [] + + +def lab(id_, cx, cy, name): + """Queue an edge label; all of them are flushed after every arrow is drawn.""" + LABELS.append((id_, cx, cy, name)) + + +# ================================================================== nodes +# centre node and the Grouping directly above it +kind_box("fc", 400, 270, 240, 80, "pulmonary nodule", "FindingClass", FINDING, size=17) +kind_box("gr", 320, 25, 250, 70, "pulmonary parenchymal abnormality", "Grouping", + GROUPING, dashed=True) + +# diagnoses, upper-left. granuloma is ABOVE neoplasm because granuloma also +# points UP at the Grouping; the other order makes those two arrows cross. +kind_box("dx_gran", 20, 30, 165, 62, "pulmonary granuloma", "Diagnosis", DIAGNOSIS) +kind_box("dx_neo", 20, 140, 165, 62, "pulmonary neoplasm", "Diagnosis", DIAGNOSIS) + +# assessment schemes, upper-right +oval_node("as_lr", 600, 14, 190, 92, "Lung-RADS", "AssessmentScheme", ASSESS) +oval_node("as_fl", 845, 14, 190, 92, "Fleischner criteria", "AssessmentScheme", ASSESS) + +# subtypes (row below the centre node; smaller boxes, 13px names) +kind_box("s1", 300, 500, 140, 80, "solid pulmonary nodule", "FindingClass", FINDING, + size=13, lines=["solid", "pulmonary nodule"]) +kind_box("s2", 450, 500, 140, 80, "part-solid pulmonary nodule", "FindingClass", FINDING, + size=13, lines=["part-solid", "pulmonary nodule"]) +kind_box("s3", 600, 500, 140, 80, "ground glass nodule", "FindingClass", FINDING, + size=13, lines=["ground glass", "nodule"]) + +# the component hanging off part-solid +kind_box("sc", 400, 630, 240, 70, "solid component", "FindingClass", FINDING, size=13, + lines=["solid component of", "part-solid pulmonary nodule"]) + +# context nodes down the left margin +kind_box("an", 60, 290, 140, 60, "lung", "AnatomicLocation", ANATOMY) +kind_box("sp", 40, 370, 170, 60, "chest radiology", "Subspecialty", SUBSPEC) +for i, (mid_, nm) in enumerate([("md_ct", "CT"), ("md_xr", "XR"), ("md_mr", "MR")]): + kind_box(mid_, 55, 480 + i * 48, 90, 38, nm, "Modality", GREY, size=13, kind_size=9) + +# data elements and measurements, right column +COL_X, COL_W, ROW_H = 845, 190, 48 +DE_NAMES = ["presence", "composition", "margin", "calcification", "interval change"] +for i, nm in enumerate(DE_NAMES): + kind_box(f"de{i}", COL_X, 150 + i * 66, COL_W, ROW_H, nm, "DataElement", DATAEL) +for i, nm in enumerate(["mean diameter", "long-axis diameter"]): + kind_box(f"ms{i}", COL_X, 510 + i * 66, COL_W, ROW_H, nm, "Measurement", MEAS) + +# ================================================================== edges +# SUBTYPE_OF into the Grouping: from the centre node (plumb vertical) and from +# the granuloma (near-horizontal, in the 135px gap that fits its plate). +edge("e_gr_fc", "fc", "top", "gr", "bottom", s_frac=0.50, d_frac=0.80) +edge("e_gr_gn", "dx_gran", "right", "gr", "left", s_frac=0.50, d_frac=0.50) +lab("l_gr_fc", 520, 180, "SUBTYPE_OF") +lab("l_gr_gn", 252, 60, "SUBTYPE_OF") + +# MAY_MANIFEST_AS x2, both into the centre node's left edge +edge("e_mma_gn", "dx_gran", "right", "fc", "left", s_frac=0.87, d_frac=0.15) +edge("e_mma_neo", "dx_neo", "right", "fc", "left", s_frac=0.50, d_frac=0.55) +lab("l_mma_gn", 292, 183, "MAY_MANIFEST_AS") +lab("l_mma_neo", 292, 242, "MAY_MANIFEST_AS") + +# ASSESSED_BY x2, drawn by ray() so each head meets the oval's curve square +# on rather than stopping at a bounding-box extreme. Both run up and to the +# right; the Fleischner one passes well below the Lung-RADS oval and above +# the first data element. +ray("e_asb_lr", "fc", "top", 0.62, "as_lr", "ASSESSED_BY", 0.34) +ray("e_asb_fl", "fc", "top", 0.80, "as_fl", "ASSESSED_BY", 0.66) + +# SUBTYPE_OF x3: each subtype -> the centre node (arrows point UP into it). +# The three plates are staggered along their arrows; level with each other +# they would collide, and the right one also has to stay clear of the +# measurement labels, so it rides high. +edge("e_st1", "s1", "top", "fc", "bottom", d_frac=0.35) +edge("e_st2", "s2", "top", "fc", "bottom", d_frac=0.50) +edge("e_st3", "s3", "top", "fc", "bottom", d_frac=0.72) +lab("l_st1", 410, 448, "SUBTYPE_OF") +lab("l_st2", 520, 425, "SUBTYPE_OF") +lab("l_st3", 592, 380, "SUBTYPE_OF") + +# HAS_COMPONENT: part-solid -> solid component. The drop is only 50px, so the +# plate sits BESIDE the arrow; centred on it, it would cover the whole shaft. +edge("e_hc", "s2", "bottom", "sc", "top") +lab("l_hc", 625, 605, "HAS_COMPONENT") + +# HAS_DATA_ELEMENT x5 and HAS_MEASUREMENT x2: one fan out of the centre node's +# right edge into the right column, across a 205px corridor. +for i in range(5): + edge(f"e_de{i}", "fc", "right", f"de{i}", "left", s_frac=0.10 + i * 0.15) + lab(f"l_de{i}", 742, 226 + i * 39, "HAS_DATA_ELEMENT") +edge("e_ms0", "fc", "right", "ms0", "left", s_frac=0.85) +edge("e_ms1", "fc", "right", "ms1", "left", s_frac=0.95) +lab("l_ms0", 727, 421, "HAS_MEASUREMENT") +lab("l_ms1", 732, 460, "HAS_MEASUREMENT") + +# SCOPED_TO -> lung. It leaves the centre node BELOW where the neoplasm's +# manifestation arrow arrives, so the two do not cross. +edge("e_sc", "fc", "left", "an", "right", s_frac=0.875, d_frac=0.50) +lab("l_sc", 300, 330, "SCOPED_TO") + +# IN_SUBSPECIALTY -> chest radiology +edge("e_sp", "fc", "bottom", "sp", "right", s_frac=0.05, d_frac=0.50) +lab("l_sp", 311, 375, "IN_SUBSPECIALTY") + +# SEEN_ON x3 -> the three modality boxes, a fan off the centre node's bottom +# edge that passes to the right of lung and of the subspecialty box. +for i, (mid_, t) in enumerate([("md_ct", 0.42), ("md_xr", 0.50), ("md_mr", 0.68)]): + edge(f"e_sn{i}", "fc", "bottom", mid_, "right", s_frac=0.10 + i * 0.08, d_frac=0.50) + a = find(f"e_sn{i}") + x1, y1 = a["x"], a["y"] + x2, y2 = x1 + a["points"][-1][0], y1 + a["points"][-1][1] + lab(f"l_sn{i}", x1 + t * (x2 - x1), y1 + t * (y2 - y1), "SEEN_ON") + +for _id, _cx, _cy, _nm in LABELS: + plate(_id, _cx, _cy, _nm, LW[_nm]) + +# ================================================================== legend +LEG_Y, LEG_X0, LEG_X1 = 735, 15, 1035 +lg = base("rectangle", "leg", LEG_X0, LEG_Y, LEG_X1 - LEG_X0, 52, "#e2e8f0", "#f8fafc", sw=1) +lg["roundness"] = {"type": 3} +els.append(lg) + +# widths measured at 11px; the nine entries need the smaller size to sit in one row +LEGEND = [("FindingClass", FINDING, 63.6, False), ("Grouping", GROUPING, 45.3, True), + ("Diagnosis", DIAGNOSIS, 48.3, False), ("AssessmentScheme", ASSESS, 100.3, False), + ("AnatomicLocation", ANATOMY, 87.4, False), ("Modality", GREY, 41.0, False), + ("DataElement", DATAEL, 63.6, False), ("Measurement", MEAS, 67.3, False), + ("Subspecialty", SUBSPEC, 62.4, False)] +lx = 32 +for i, (name, pal, wpx, dashed) in enumerate(LEGEND): + fill, stroke, _t, _m = pal + if name == "AssessmentScheme": + els.append(base("ellipse", f"lg{i}", lx - 1, LEG_Y + 17, 26, 18, stroke, fill, sw=1)) + sw_w = 26 + else: + s = base("rectangle", f"lg{i}", lx, LEG_Y + 18, 22, 16, stroke, fill, sw=1, dashed=dashed) + s["roundness"] = {"type": 3} + els.append(s) + sw_w = 22 + els.append(text(f"lg{i}_t", lx + sw_w + 8, LEG_Y + 19, name, size=11, color=BODY, w=wpx)) + lx += sw_w + 8 + wpx + 16 + +# caption under the legend (Excalidraw has no italic variant; small and grey instead) +els.append(text("cap", (LEG_X0 + LEG_X1) / 2 - 300, LEG_Y + 64, + "illustrative: reflects pending updates to the CDE graph", + size=11, color=CAPTION, align="center", w=600)) + +save(figure("nodule-neighborhood").excalidraw) diff --git a/tools/diagrams/build_pillars.py b/tools/diagrams/build_pillars.py new file mode 100644 index 0000000..60cd507 --- /dev/null +++ b/tools/diagrams/build_pillars.py @@ -0,0 +1,464 @@ +#!/usr/bin/env python3 +"""Build the OIDM "five pillars" technology-stack diagram. + +A layered stack, read bottom to top, each layer its own hue (no blue reused +from other diagrams in the bundle) so the picture reads without words: + + 1. Foundation Context (bottom) -- shared, curated knowledge: finding and + diagnosis definitions, anatomic locations, exam types. Short + double-headed arrows between the three blocks, with one "relationships" + label over the row, say the three are tied to each other. + 2. Data Structures -- this patient's data, sitting on the foundation. + 3. SDKs -- a thin band between the data layer and the top. + 4/5. Use Cases and Sample Applications -- two panels sharing the top band, + joined by a short horizontal connector labelled "illustrates". + +The seam -- how the Data Structures band and the Foundation Context band show +that the patient graph is woven into the foundation graph -- is pluggable, and +selected with the required `--seam` flag. v3's seam (an overlap crossed by +twelve alternating vertical threads) was rejected; the three candidates are: + + a Named attachments. The bands separate, and thin connectors run from the + data cards to named tags sitting on the Foundation band's top edge, each + tag over the foundation block it attaches to ("is a", "located at", ...). + b Dovetail. The bands are flush and the Data Structures band's lower edge + is cut into four teal tabs that drop into matching slots in the + Foundation band's top edge, one per attachment kind. No lines. + c Shared membrane. A narrow hatched band between the two holds four + attachment-point nodes, each ticking up into the data band and down into + the foundation band. + +A seam is a dict of four things: how much room the Data band leaves below its +cards, the gap between the two bands, how far below the Foundation band's top +edge its header starts, and a draw() that paints the seam itself once both +band rectangles are placed. + +Geometry rules: every band is CONTENT_W wide and starts at x=0; vertical gaps +between bands are all BAND_GAP, except the Data/Foundation seam, which each +seam sets for itself; band heights are computed from their contents, so no +band is taller than its own text needs. + +Icons: Health Icons (CC0/MIT, outline style) for anatomy/exam/person glyphs, +Lucide (ISC, with one Feather-derived MIT icon) for everything else -- see +icons/LICENSES.md for the full source list and the two slots where Health +Icons had no matching glyph and Lucide was used instead. Health Icons' +outline set is filled-path art on a 48x48 viewBox with built-in padding, so +it renders optically smaller than Lucide's 24x24 stroke art at the same box +size; ICON_FILLED compensates. + +Output: the "pillars-" entry in figures.toml (override with --out); +render with render_excalidraw.py. +""" +from __future__ import annotations + +import argparse +import math + +from excalib import els, base, text, image, save, figure, AMBER, TEAL, VIOLET, ROSE, GREEN, CAPTION + +# ---------------------------------------------------------------- layout constants +CONTENT_W = 880 +BAND_GAP = 24 # the vertical gap between bands above the seam +PAD_BAND = 20 # a band's own inner padding on all sides +GAP = 20 # gap between sibling blocks inside a band +GAP_REL = 64 # wider gap, so a double-headed arrow reads as an arrow and not a diamond +ICON = 28 # harmonized icon render size +ICON_FILLED = 32 # filled-path icon sets need a bigger box to match optically +CORNER_R = 32 # Excalidraw's adaptive corner radius for a band-sized rectangle + +_uid = [0] + + +def uid(prefix: str) -> str: + _uid[0] += 1 + return f"{prefix}{_uid[0]}" + + +# ---------------------------------------------------------------- primitives +def band_rect(id_: str, x: float, y: float, w: float, h: float, pal: dict, at_front=False) -> dict: + """Draw a band's background rectangle. Pass at_front=True when the band's + true height is only known after laying out its header/blocks (they were + already appended to els) -- this inserts the rect at index 0 so it still + paints behind its own contents instead of covering them. Note the order + that matters at the seam: whichever band rect is inserted at index 0 last + ends up furthest back, so Foundation Context must be built last.""" + r = base("rectangle", id_, x, y, w, h, pal["stroke"], pal["band"]) + r["roundness"] = {"type": 3} + if at_front: + els.insert(0, r) + else: + els.append(r) + return r + + +def band_header(x: float, y: float, icon_path: str, pal: dict, title: str, subtitle: str, + size=20, filled_icon=False) -> float: + """Bold band title with its icon to the left, subtitle below. Returns bottom y.""" + s = ICON_FILLED if filled_icon else ICON + image(uid("bicon"), x, y - (s - ICON) / 2 - 2, s, s, icon_path, color=pal["stroke"]) + els.append(text(uid("btitle"), x + ICON + 12, y, title, size=size, color=pal["text"])) + y2 = y + size * 1.3 + els.append(text(uid("bsub"), x + ICON + 12, y2, subtitle, size=13, color=pal["mid"])) + return y2 + 13 * 1.25 + + +def icon_block_h(lines: list[str], desc: str | None = None) -> float: + """Height an icon_block needs for this many label lines/descriptor.""" + n_title = len(lines) + return 12 + ICON + 8 + n_title * 15 * 1.25 + (13 * 1.25 + 2 if desc else 0) + 10 + + +def icon_block(x: float, y: float, w: float, h: float, icon_path: str, pal: dict, + lines: list[str], desc: str | None = None, filled_icon=False) -> dict: + """Icon centered on top, label line(s) centered below, optional lighter + descriptor line last. Background is the band's own deeper pastel so the + block reads as 'inside' the band, not a separate object.""" + r = base("rectangle", uid("blk"), x, y, w, h, pal["stroke"], pal["block"], sw=1) + r["roundness"] = {"type": 3} + els.append(r) + cx = x + w / 2 + content_h = icon_block_h(lines, desc) - 22 # minus this fn's fixed 12+10 top/bottom pad + top = y + (h - content_h) / 2 + s = ICON_FILLED if filled_icon else ICON + image(uid("blki"), cx - s / 2, top - (s - ICON) / 2, s, s, icon_path, color=pal["stroke"]) + ty = top + ICON + 6 + for ln in lines: + els.append(text(uid("blkt"), x + 6, ty, ln, size=15, color=pal["text"], align="center", w=w - 12)) + ty += 15 * 1.25 + if desc: + els.append(text(uid("blkd"), x + 6, ty + 2, desc, size=13, color=pal["mid"], align="center", w=w - 12)) + return r + + +def chip(cx: float, y_top: float, s: str, fill: str, stroke: str, color: str, + size=13, pad_x=9, pad_y=3) -> float: + """A short caption on its own opaque rounded plate, centered on cx, so it + reads as a label for what it sits over and never mixes with a line or a + band fill behind it. Returns bottom y.""" + w = len(s) * size * 0.58 + 2 * pad_x + h = size * 1.25 + 2 * pad_y + r = base("rectangle", uid("chip"), cx - w / 2, y_top, w, h, stroke, fill, sw=1) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(uid("chipt"), cx - w / 2 + pad_x, y_top + pad_y, s, size=size, color=color, + align="center", w=w - 2 * pad_x)) + return y_top + h + + +def hconn(x1: float, x2: float, y: float, color: str, sw=1, both=False) -> None: + """A plain horizontal connector; both=True gives it two arrowheads.""" + a = base("arrow" if both else "line", uid("conn"), x1, y, x2 - x1, 0, color, "transparent", sw=sw) + a.update({"points": [[0, 0], [x2 - x1, 0]], "boundElements": None}) + if both: + a.update({"startArrowhead": "arrow", "endArrowhead": "arrow"}) + els.append(a) + + +def fill_rect(x: float, y: float, w: float, h: float, fill: str, rounded=False) -> dict: + """An unstroked patch of colour -- used to close the notch two rounded + bands leave where they meet, and to let a tab sit across a band edge + without that edge's stroke running through it.""" + r = base("rectangle", uid("fill"), x, y, w, h, "transparent", fill, sw=1) + r["roundness"] = {"type": 3} if rounded else None + els.append(r) + return r + + +def stroke_line(pts: list[tuple[float, float]], color: str, sw=2, opacity=100) -> dict: + """A polyline through pts (absolute coordinates), sharp corners.""" + x0, y0 = pts[0] + xs = [p[0] for p in pts] + ys = [p[1] for p in pts] + ln = base("line", uid("ln"), x0, y0, max(xs) - min(xs), max(ys) - min(ys), color, "transparent", sw=sw) + ln.update({"points": [[p[0] - x0, p[1] - y0] for p in pts], "boundElements": None, "roundness": None}) + ln["opacity"] = opacity + els.append(ln) + return ln + + +def arc_pts(cx: float, cy: float, r: float, a0: float, a1: float, n=4) -> list[tuple[float, float]]: + """Points along a circular arc, for rounding a corner of a polyline.""" + return [(cx + r * math.cos(a0 + (a1 - a0) * i / n), cy + r * math.sin(a0 + (a1 - a0) * i / n)) + for i in range(n + 1)] + + +def plate(cx: float, y_top: float, s: str, size=13, color=CAPTION, fill="#ffffff", + stroke="transparent", pad_x=7, pad_y=3, sw=1) -> tuple[float, float]: + """An opaque label plate centered on cx with its top at y_top. Returns + (width, height).""" + w = len(s) * size * 0.58 + 2 * pad_x + h = size * 1.25 + 2 * pad_y + r = base("rectangle", uid("plate"), cx - w / 2, y_top, w, h, stroke, fill, sw=sw) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(uid("platet"), cx - w / 2 + pad_x, y_top + pad_y, s, size=size, color=color, + align="center", w=w - 2 * pad_x)) + return w, h + + +def corner_patch(y_top: float, y_bot: float, pal: dict, anchor: dict) -> None: + """Two rounded bands that meet edge to edge leave a white notch at each + end, where both corner radii curve away from the seam. Fill those notches + with the band's own colour and redraw its outer border straight, so the + stack keeps one silhouette through the seam. + + A notch is CORNER_R deep, which reaches back into the band far enough to + cover a card corner or a header icon, so the patch is restacked to sit + directly above `anchor` (its own band rectangle) instead of on top of + everything the seam is drawn after.""" + made = [ + fill_rect(-1, y_top, CORNER_R + 1, y_bot - y_top, pal["band"]), + fill_rect(CONTENT_W - CORNER_R, y_top, CORNER_R + 1, y_bot - y_top, pal["band"]), + stroke_line([(0, y_top), (0, y_bot)], pal["stroke"], sw=2), + stroke_line([(CONTENT_W, y_top), (CONTENT_W, y_bot)], pal["stroke"], sw=2), + ] + for e in reversed(made): + els.remove(e) + els.insert(els.index(anchor) + 1, e) + + +# ============================================================ seam a: named attachments +# Each link: (data card index, exit fraction along its bottom edge, +# foundation block index, which of that block's two tags, label). +# Exit fractions and tag order run left to right by target, so the six +# connectors cross each other only at clean X points and never a label. +A_LINKS = [ + (0, 0.22, 0, 0, "is a"), + (0, 0.50, 1, 0, "located at"), + (0, 0.78, 2, 0, "seen on"), + (1, 0.50, 2, 1, "of an exam type"), + (2, 0.35, 0, 1, "per definition"), + (2, 0.65, 1, 1, "per location"), +] + + +def draw_seam_a(g: dict) -> None: + size, pad_x, pad_y = 13, 7, 3 + tag_h = size * 1.25 + 2 * pad_y + y_tag_top = g["found_top"] - tag_h + y_card = g["blocks_bottom"] + + labels = {(dj, slot): lab for (_si, _sf, dj, slot, lab) in A_LINKS} + tag_cx: dict[tuple[int, int], float] = {} + for j in range(3): + w0 = len(labels[(j, 0)]) * size * 0.58 + 2 * pad_x + w1 = len(labels[(j, 1)]) * size * 0.58 + 2 * pad_x + total = w0 + w1 + 12 + cx = g["f_positions"][j] + g["W3"] / 2 + tag_cx[(j, 0)] = cx - total / 2 + w0 / 2 + tag_cx[(j, 1)] = cx + total / 2 - w1 / 2 + + # connectors first, so each tag paints over the end of its own line + for (si, sf, dj, slot, _lab) in A_LINKS: + x1 = g["d_positions"][si] + g["W4"] * sf + stroke_line([(x1, y_card), (tag_cx[(dj, slot)], y_tag_top)], TEAL["stroke"], sw=1, opacity=75) + for key, lab in labels.items(): + plate(tag_cx[key], y_tag_top, lab, size=size, color=AMBER["text"], + stroke=AMBER["stroke"], pad_x=pad_x, pad_y=pad_y) + + els.append(text(uid("seaml"), 0, g["found_top"] + 13, + "interconnected at every finding, diagnosis, location, and exam type", + size=13, color=CAPTION, align="center", w=CONTENT_W)) + + +# ============================================================ seam b: dovetail +B_WORDS = ["finding", "diagnosis", "location", "exam type"] +B_TAB_W = 120.0 +B_TAB_D = 34.0 +B_TAB_R = 11.0 +B_LEAD = "woven together at every:" +B_RIGHT_MARGIN = 60.0 # amber left clear to the right of the last tab + + +def draw_seam_b(g: dict) -> None: + seam = g["found_top"] # the two bands are flush here + corner_patch(seam - CORNER_R, seam, TEAL, g["teal_band"]) + corner_patch(seam, seam + CORNER_R, AMBER, g["amber_band"]) + stroke_line([(0, seam), (CONTENT_W, seam)], TEAL["stroke"], sw=2) + + lead_w = len(B_LEAD) * 13 * 0.58 + x0 = PAD_BAND + lead_w + 22 + span = CONTENT_W - B_RIGHT_MARGIN - x0 + step = (span - B_TAB_W) / (len(B_WORDS) - 1) + els.append(text(uid("bl"), PAD_BAND, seam + (B_TAB_D - 13 * 1.25) / 2, B_LEAD, + size=13, color=AMBER["mid"])) + + for i, word in enumerate(B_WORDS): + x = x0 + i * step + # the tab's fill runs up past the seam, so the band edge's stroke stops + # at the tab: what is left reads as a slot cut into the Foundation band. + fill_rect(x, seam - 12, B_TAB_W, B_TAB_D + 12, TEAL["band"], rounded=True) + pts = [(x, seam - 7)] + pts += arc_pts(x + B_TAB_R, seam + B_TAB_D - B_TAB_R, B_TAB_R, math.pi, math.pi / 2) + pts += arc_pts(x + B_TAB_W - B_TAB_R, seam + B_TAB_D - B_TAB_R, B_TAB_R, math.pi / 2, 0.0) + pts += [(x + B_TAB_W, seam - 7)] + stroke_line(pts, TEAL["stroke"], sw=2) + els.append(text(uid("bw"), x, seam + (B_TAB_D - 13 * 1.25) / 2, word, + size=13, color=TEAL["text"], align="center", w=B_TAB_W)) + + +# ============================================================ seam c: shared membrane +C_WORDS = ["finding", "diagnosis", "location", "exam type"] +C_H = 64.0 +C_TITLE = "attachment points" +C_PILL_H = 28.0 +C_TICK = 9.0 + + +def draw_seam_c(g: dict) -> None: + top = g["data_band_bottom"] + bot = g["found_top"] + corner_patch(top - CORNER_R, top, TEAL, g["teal_band"]) + corner_patch(bot, bot + CORNER_R, AMBER, g["amber_band"]) + + # membrane ground, then a fine two-colour hatch clipped to it + fill_rect(0, top, CONTENT_W, bot - top, "#ffffff") + step, i, c = 16.0, 0, top - CONTENT_W + while c < bot: + x1, x2 = max(0.0, top - c), min(CONTENT_W, bot - c) + if x2 - x1 > 1: + stroke_line([(x1, x1 + c), (x2, x2 + c)], + TEAL["stroke"] if i % 2 == 0 else AMBER["stroke"], sw=1, opacity=25) + c += step + i += 1 + stroke_line([(0, top), (CONTENT_W, top)], TEAL["stroke"], sw=2) + stroke_line([(0, bot), (CONTENT_W, bot)], AMBER["stroke"], sw=2) + stroke_line([(0, top), (0, bot)], CAPTION, sw=1, opacity=55) + stroke_line([(CONTENT_W, top), (CONTENT_W, bot)], CAPTION, sw=1, opacity=55) + + pill_top = top + (C_H - C_PILL_H) / 2 + tw, _ = plate(PAD_BAND + len(C_TITLE) * 13 * 0.58 / 2 + 7, pill_top + (C_PILL_H - (13 * 1.25 + 6)) / 2, + C_TITLE, size=13, color=CAPTION) + x0 = PAD_BAND + tw + 20 + widths = [len(w) * 13 * 0.58 + 28 for w in C_WORDS] + span = CONTENT_W - PAD_BAND - x0 + gap = (span - sum(widths)) / (len(C_WORDS) - 1) + x = x0 + for word, w in zip(C_WORDS, widths): + cx = x + w / 2 + stroke_line([(cx, top - C_TICK), (cx, pill_top)], TEAL["stroke"], sw=2) + stroke_line([(cx, pill_top + C_PILL_H), (cx, bot + C_TICK)], AMBER["stroke"], sw=2) + r = base("rectangle", uid("pill"), x, pill_top, w, C_PILL_H, CAPTION, "#ffffff", sw=1) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(uid("pillt"), x, pill_top + (C_PILL_H - 13 * 1.25) / 2, word, + size=13, color="#334155", align="center", w=w)) + x += w + gap + + +SEAMS = { + "a": {"pad_below_blocks": PAD_BAND, "band_gap": 60.0, "found_top_pad": 46.0, "draw": draw_seam_a}, + "b": {"pad_below_blocks": PAD_BAND, "band_gap": 0.0, "found_top_pad": B_TAB_D + 18, "draw": draw_seam_b}, + "c": {"pad_below_blocks": PAD_BAND, "band_gap": C_H, "found_top_pad": 20.0, "draw": draw_seam_c}, +} + + +# ============================================================ the stack +def build(seam_key: str, out_path: str) -> str: + seam = SEAMS[seam_key] + + # ---------------------------------------- 5/4. Use Cases | Sample Applications + Y_TOP = 0 + GAP_MID = 104 # wide enough for "illustrates" to sit clear of both panels + PANEL_PAD = 14 + PANEL_INSET = PAD_BAND - 2 # same left inset as the full-width band headers + PANEL_TITLE = 18 + PANEL_H = PANEL_PAD + PANEL_TITLE * 1.3 + 13 * 1.25 + PANEL_PAD + panel_w = (CONTENT_W - GAP_MID) / 2 + + band_rect(uid("panel"), 0, Y_TOP, panel_w, PANEL_H, ROSE) + band_rect(uid("panel"), panel_w + GAP_MID, Y_TOP, panel_w, PANEL_H, GREEN) + band_header(PANEL_INSET, Y_TOP + PANEL_PAD, "icons/lucide-lightbulb.svg", ROSE, + "Use Cases", "what the project wants built", size=PANEL_TITLE) + band_header(panel_w + GAP_MID + PANEL_INSET, Y_TOP + PANEL_PAD, "icons/lucide-app-window.svg", GREEN, + "Sample Applications", "what has been built", size=PANEL_TITLE) + conn_y = Y_TOP + PANEL_H / 2 + hconn(panel_w + 2, panel_w + GAP_MID - 2, conn_y, CAPTION) + chip(panel_w + GAP_MID / 2, conn_y - 7 - (13 * 1.25 + 6), "illustrates", + "#ffffff", "transparent", CAPTION) + + # ---------------------------------------- 3. SDKs (thin full-width band) + Y_SDK = Y_TOP + PANEL_H + BAND_GAP + sdk_hdr_bottom = band_header(PAD_BAND - 2, Y_SDK + 16, "icons/lucide-package.svg", VIOLET, + "SDKs", "wrap the structures · attach the shared knowledge · support authoring", + size=18) + SDK_BOTTOM = sdk_hdr_bottom + 16 + band_rect(uid("band"), 0, Y_SDK, CONTENT_W, SDK_BOTTOM - Y_SDK, VIOLET, at_front=True) + + # ---------------------------------------- 2. Data Structures + Y_DATA = SDK_BOTTOM + BAND_GAP + hdr_bottom = band_header(PAD_BAND - 2, Y_DATA + 18, "icons/lucide-layers.svg", TEAL, + "Data Structures", "this patient · Patient Context", size=20) + BLOCK_ROW_Y = hdr_bottom + 14 + W4 = (CONTENT_W - 2 * PAD_BAND - 3 * GAP) / 4 + d_positions = [PAD_BAND + i * (W4 + GAP) for i in range(4)] + d_specs = [ + ("icons/lucide-clipboard.svg", ["Observation"], False), + ("icons/lucide-clipboard-list.svg", ["Exam Finding", "List"], False), + ("icons/lucide-list.svg", ["Imaging Problem", "List"], False), + ("icons/healthicons-person.svg", ["Imaging Persona"], True), + ] + ROW_H_DATA = max(icon_block_h(lines) for _, lines, _ in d_specs) + for xpos, (ipath, lines, filled) in zip(d_positions, d_specs): + icon_block(xpos, BLOCK_ROW_Y, W4, ROW_H_DATA, ipath, TEAL, lines, filled_icon=filled) + DATA_BLOCKS_BOTTOM = BLOCK_ROW_Y + ROW_H_DATA + + DATA_BAND_BOTTOM = DATA_BLOCKS_BOTTOM + seam["pad_below_blocks"] + teal_band = band_rect(uid("band"), 0, Y_DATA, CONTENT_W, DATA_BAND_BOTTOM - Y_DATA, TEAL, at_front=True) + + # ---------------------------------------- 1. Foundation Context (bottom) + # Built last so its rect lands behind the Data Structures band at the seam. + FOUND_TOP = DATA_BAND_BOTTOM + seam["band_gap"] + hdr_bottom_f = band_header(PAD_BAND - 2, FOUND_TOP + seam["found_top_pad"], + "icons/lucide-book-open.svg", AMBER, + "Foundation Context", "shared, curated knowledge", size=20) + REL_Y = hdr_bottom_f + 12 + rel_bottom = chip(CONTENT_W / 2, REL_Y, "relationships", AMBER["band"], AMBER["stroke"], AMBER["text"]) + F_ROW_Y = rel_bottom + 10 + W3 = (CONTENT_W - 2 * PAD_BAND - 2 * GAP_REL) / 3 + f_positions = [PAD_BAND + i * (W3 + GAP_REL) for i in range(3)] + f_specs = [ + ("icons/lucide-tag.svg", ["Finding/diagnosis definitions"], "finding models · CDEs", False), + ("icons/healthicons-lungs.svg", ["Anatomic locations"], "anchored in RadLex", True), + ("icons/healthicons-xray.svg", ["Exam types"], "LOINC/RSNA Playbook", True), + ] + ROW_H_FOUND = max(icon_block_h(lines, desc) for _, lines, desc, _ in f_specs) + for xpos, (ipath, lines, desc, filled) in zip(f_positions, f_specs): + icon_block(xpos, F_ROW_Y, W3, ROW_H_FOUND, ipath, AMBER, lines, desc=desc, filled_icon=filled) + FOUND_BLOCKS_BOTTOM = F_ROW_Y + ROW_H_FOUND + + arrow_y = F_ROW_Y + ROW_H_FOUND / 2 + for i in range(2): + x1 = f_positions[i] + W3 + 6 + hconn(x1, x1 + GAP_REL - 12, arrow_y, AMBER["stroke"], sw=2, both=True) + + FOUND_BAND_BOTTOM = FOUND_BLOCKS_BOTTOM + PAD_BAND + amber_band = band_rect(uid("band"), 0, FOUND_TOP, CONTENT_W, FOUND_BAND_BOTTOM - FOUND_TOP, AMBER, at_front=True) + + # ---------------------------------------- the seam, drawn over both bands + seam["draw"]({ + "blocks_bottom": DATA_BLOCKS_BOTTOM, + "data_band_bottom": DATA_BAND_BOTTOM, + "found_top": FOUND_TOP, + "d_positions": d_positions, "W4": W4, + "f_positions": f_positions, "W3": W3, + "teal_band": teal_band, "amber_band": amber_band, + }) + + return save(out_path) + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + ap.add_argument("--seam", choices=sorted(SEAMS), required=True, + help="which Data/Foundation seam to draw: a=named attachments, " + "b=dovetail, c=shared membrane") + ap.add_argument("--out", default=None, + help="output path relative to the repo root " + "(default: the pillars- entry in figures.toml)") + args = ap.parse_args() + build(args.seam, args.out or figure(f"pillars-{args.seam}").excalidraw) + + +if __name__ == "__main__": + main() diff --git a/tools/diagrams/build_repository_map.py b/tools/diagrams/build_repository_map.py new file mode 100644 index 0000000..0cab1fb --- /dev/null +++ b/tools/diagrams/build_repository_map.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Build the repository data-flow map: one row per theme, each a short +left-to-right chain of at most three boxes, stacked top to bottom so every +cross-row flow is a short arrow to an ADJACENT row only -- rows are ordered +(CDE staging, finding models, applications, anatomy, terminology lookup) so +that CDE staging and applications both sit directly next to finding models, +and applications sits directly next to anatomy. Two edges that would have to +skip a row (CDEStaging -> RadElement, med-ontology-lookup -> findingmodel) +are dropped as captions instead -- both are already stated in the page prose. +Output: the "repository-map" entry in figures.toml""" +from excalib import PRIMARY, SECONDARY, TERTIARY, EXTERNAL, PLANNED, TITLE, SUBTITLE, BODY, LINE, els, base, text, box, find, arrow, save, figure + +W, H = 190, 80 +GAP = 115 # wide enough that the longest horizontal-arrow label ("CDE-derived") fits centered above its arrow with room on each side +C1, C2, C3 = 0, W + GAP, 2 * (W + GAP) # 0, 305, 610 +ROW_GAP = 90 +CONTENT_W = C3 + W # 800 + +row_names = ["cde_stage", "fm_pipeline", "apps", "anatomy", "terms"] +row_labels = ["CDE STAGING", "FINDING MODELS", "APPLICATIONS", "ANATOMY", "TERMINOLOGY LOOKUP"] +Y = {} +y = 40 +for name, lbl in zip(row_names, row_labels): + Y[name] = y + els.append(text(f"hdr_{name}", C1, y - 24, lbl, size=14, color=SUBTITLE)) + y += H + ROW_GAP + +# row 1: CDE staging (2 boxes; columns line up with row 2's RadElement/findingmodels) +box("cdesnap", C1, Y["cde_stage"], W, H, "common_data_elements\nRadElement snapshot", TERTIARY) +box("cdes", C2, Y["cde_stage"], W, H, "CDEStaging\n259 draft CDE\ndefinitions", TERTIARY) + +# row 2: finding model pipeline +box("radelement", C1, Y["fm_pipeline"], W, H, "RadElement (ACR/RSNA)\nRDES and RDE codes", EXTERNAL) +box("fms", C2, Y["fm_pipeline"], W, H, "findingmodels\n2,382 finding models", TERTIARY) +box("fm", C3, Y["fm_pipeline"], W, H, "findingmodel\nOIFM format, index,\nCLIs, MCP", SECONDARY) +arrow("f1", "radelement", "right", "fms", "left", "CDE-derived") +arrow("f2", "fms", "right", "fm", "left", "definitions") + +# row1 <-> row2: both adjacent, straight verticals (same columns). +# cdesnap/cdes sit ABOVE radelement/fms on the page, so the "ascending" arrow +# (radelement -> cdesnap) binds radelement's top to cdesnap's bottom, and the +# "descending" one (cdes -> fms) binds cdes's bottom to fms's top. +arrow("f3", "radelement", "top", "cdesnap", "bottom", "snapshot") +arrow("f4", "cdes", "bottom", "fms", "top", "content batches") + +# row 3: applications (fed from row 2 above and row 4 below -- both adjacent) +box("ipl", C1, Y["apps"], W, H, "imaging-problem-list\nuses models +\nlocations", PRIMARY) +box("site", C2, Y["apps"], W, H, "finding-models-site\ncorpus catalog", PRIMARY) +box("forge", C3, Y["apps"], W, H, "Finding Model Forge\nfmf.oidm.org", PRIMARY) +arrow("f5", "fms", "bottom", "site", "top", "git submodule") # row2->row3, same column, straight vertical +arrow("f6", "fm", "bottom", "forge", "top", "engine") # row2->row3, same column, straight vertical +# C1 has no arrow through this gap (imaging-problem-list is fed from row 4 +# below, not row 2 above), so it is clear for this caption +els.append(text("cdes_note", C1, Y["fm_pipeline"] + H + 12, "(CDEStaging also has an\ninformal path to RadElement)", size=13, color=BODY, w=W)) + +# row 4: anatomy +box("radlex", C1, Y["anatomy"], W, H, "RadLex (RSNA)\nRID codes", EXTERNAL) +box("alorg", C2, Y["anatomy"], W, H, "anatomiclocations.org\n+ BodyPartIndex", TERTIARY) +box("alpkg", C3, Y["anatomy"], W, H, "anatomic-locations pkg\ninside findingmodel", SECONDARY) +arrow("f7", "radlex", "right", "alorg", "left", "subset") +arrow("f8", "alorg", "right", "alpkg", "left", "lineage", dashed=True) + +# row3 <-> row4: adjacent rows -- one clean line, no bends, even though it +# spans the row's full width (alpkg is the rightmost box in row 4, ipl the +# leftmost in row 3, to keep each row's own left-to-right reading intact) +arrow("f9", "alpkg", "top", "ipl", "bottom", "used by", s_frac=0.5, d_frac=0.5) + +# row 5: terminology lookup +box("others", C1, Y["terms"], W, H, "SNOMED CT, FMA,\nLOINC, UMLS", EXTERNAL) +box("molu", C2, Y["terms"], W, H, "med-ontology-lookup\nmolu", SECONDARY) +box("mvp", C3, Y["terms"], W, H, "IPL-MVP-\nExtractionAndLabeling\n(superseded)", PLANNED, dashed=True) +arrow("f10", "others", "right", "molu", "left", "lookups") +els.append(text("molu_note", C2, Y["terms"] + H + 14, "molu also enriches findingmodel\nmetadata by ontology search (see text)", size=13, color=BODY)) + +# ---------------------------------------------------------------- legend / footnote +Y_END = Y["terms"] + H + 60 +els.append(text("legend", 0, Y_END, "Orange: external terminology. Light blue: content repository.\nMid blue: library. Dark blue: application. Dashed: lineage or superseded.", size=13, color=BODY)) + +save(figure("repository-map").excalidraw) diff --git a/tools/diagrams/build_three_axes.py b/tools/diagrams/build_three_axes.py new file mode 100644 index 0000000..ceebaf3 --- /dev/null +++ b/tools/diagrams/build_three_axes.py @@ -0,0 +1,396 @@ +#!/usr/bin/env python3 +"""Build the OIDM "three axes of Foundation Context" detail diagram. + +A smaller companion to build_pillars.py, zoomed into the Foundation +Context (AMBER) pillar of that stack: the three axes shared knowledge is +organized along -- findings/diagnoses, anatomic locations, exam types -- +each a curated layer over an existing external standard, and each with a +hierarchy of its own. Same AMBER palette and same icon files as the main +stack diagram (from excalib.py / icons/), so the two figures read as one +family. Icons: Lucide (tag), Health Icons (lungs, x-ray). + +Landscape, three independent columns -- nothing joins them in this +figure, by design. Each column carries: + - icon, title, a short "WHAT/WHERE/HOW" tagline + - stacked labelled sections: a full-width band naming the section, + then that section's example tree indented TREE_INDENT from the + column's inner edge + - a small darker block at the bottom: "layered over: " +The figure ends at the bottom of the columns; there is no caption. + +All three columns share the band grammar, so they read as one figure: +the findings column bands the next-generation CDE schema's node kinds +(Findings, Diagnoses, Assessments, Data Elements, Measurements), the +anatomy column bands body regions, and the exam column bands modalities. +Each band restarts the connector bookkeeping, so a section's tree never +links back into the section above it. + +Tree rows come in three kinds. A "band" is the section header. A "node" +is a concept in the hierarchy, drawn with real line connectors rather +than box-drawing characters so the indentation is exact in a +proportional font. An "ann" is a muted annotation hanging off the node +above it -- a value set, or the unit family a measurement kind implies. +It is half-indented and takes no connector, because it is not a child +concept. A measurement names the KIND of quantity; the units follow from +the kind, so "length" is annotated "mm, cm, ..." rather than one unit. + +The columns are the same height and their "layered over" blocks line up. +A column with fewer rows spreads them toward ROW_H_MAX so the leading +stays close across columns; whatever space is still left over falls +above that column's bottom block, because every column's first band must +start on the same line. + +Text widths below were measured in headless Chromium at the render font +(`Helvetica, Segoe UI Emoji`), per tools/diagrams/README.md. They place +the muted suffix after a root label and let the build assert that no row +overflows its column, neither of which can be estimated. CONTENT_W is +set by the widest single row (the `presence` value set) plus the margins. + +Exam type names are real LOINC/RSNA Playbook LongCommonName values, +checked against ~/exam-types/sources/LoincRsnaRadiologyPlaybook.xlsx -- +note the capital V in "XR Knee 2 Views", that the thyroid entry is "US +Thyroid gland" rather than "US Thyroid", and that the Playbook has no +"XR Chest Portable" (its portable names all begin "Portable XR ..."), so +the second XR Chest child here is "XR Chest PA and Lateral". A family +root such as "XR Chest" is a preferred-name family, not itself an entry. + +Output: the "three-axes" entry in figures.toml; render with +render_excalidraw.py at `--width 1010 --scale 1`. +""" +from excalib import els, base, text, image, save, figure, AMBER + +ICON = 36 +ROOT_SIZE = 14 +CHILD_SIZE = 13 +BAND_SIZE = 13 +BAND_FILL = "#fcd34d" # one step darker than the column's block fill + +W14 = { + "pulmonary nodule": 111.3, "pulmonary neoplasm": 130.0, "Lung-RADS": 74.7, + "Salter-Harris category": 136.2, "presence": 57.6, "severity": 48.2, + "length": 38.1, "CT density": 66.9, + "lung": 26.5, "pleural space": 83.3, "mediastinum": 79.4, "kidney": 40.5, + "liver": 25.7, "spleen": 41.3, "brain": 31.1, "orbit": 27.2, + "urinary bladder": 93.4, "prostate": 50.6, + "CT Chest": 59.1, "MR Knee": 58.4, "MR Brain": 58.3, "XR Knee": 56.0, + "XR Chest": 59.9, "US Abdomen": 83.3, "US Thyroid gland": 108.2, +} +W13 = { + "solid": 26.7, "part-solid": 53.5, "non-solid": 52.8, + "non-small cell lung cancer": 150.3, "adenocarcinoma of the lung": 160.4, + "present · absent · indeterminate · unknown": 247.9, + "minimal · mild · moderate · severe": 197.2, "mm, cm, ...": 64.3, "HU": 18.8, + "right lung": 53.5, "left lung": 45.5, "heart": 29.6, + "upper lobe of right lung": 133.0, "middle lobe of right lung": 138.0, + "lower lobe of right lung": 130.8, "left kidney": 58.5, "right kidney": 66.5, + "renal pelvis": 65.8, "cerebellum": 63.6, "frontal lobe": 64.3, + "CT Chest WO contrast": 130.7, "CT Chest W contrast IV": 136.5, + "CT Chest WO and W contrast IV": 187.8, "CT Lung parenchyma WO contrast": 200.9, + "CTA Chest vessels": 109.6, "MR Knee WO contrast": 130.0, + "MR Knee W contrast IV": 135.8, "MR Brain WO contrast": 130.0, + "MR Brain WO and W contrast IV": 187.1, "XR Knee 2 Views": 100.9, + "XR Knee 3 Views": 100.9, "XR Chest 2 Views": 104.5, + "XR Chest PA and Lateral": 144.3, "US Abdomen limited": 118.5, + "US Abdomen RUQ": 109.8, "(preferred family)": 99.0, + "each a LOINC/RSNA Playbook entry": 211.7, +} + +_uid = [0] + + +def uid(prefix: str) -> str: + _uid[0] += 1 + return f"{prefix}{_uid[0]}" + + +def rule(x: float, y: float, w: float) -> None: + ln = base("line", uid("rule"), x, y, w, 0, AMBER["stroke"], "transparent", sw=1) + ln.update({"points": [[0, 0], [w, 0]], "boundElements": None, "opacity": 35}) + els.append(ln) + + +def connector(x1: float, y1: float, x2: float, y2: float) -> None: + ln = base("line", uid("tc"), x1, y1, x2 - x1, y2 - y1, AMBER["stroke"], "transparent", sw=1) + ln.update({"points": [[0, 0], [x2 - x1, y2 - y1]], "boundElements": None, "opacity": 45}) + els.append(ln) + + +# ------------------------------------------------------------------ tree +INDENT = 18 +ANN_INDENT = 8 # half a step: an annotation is not a child concept +ROW_H = 19 +ROW_H_MAX = 23 # a shorter column spreads its rows to fill the region, + # but only this far: leading that differs much more than + # this between columns stops reading as one figure +BAND_H = 20 +BAND_PAD_X = 10 +BAND_GAP_ABOVE = 12 +BAND_GAP_BELOW = 6 + +Row = tuple[str, int, str, "str | None", float] + + +def row_width(kind: str, depth: int, label: str, suffix: str | None, _gap: float = 0) -> float: + """Rendered width of a row. A band spans the column, so it constrains nothing.""" + if kind == "band": + return 0.0 + if kind == "ann": + return depth * INDENT + ANN_INDENT + W13[label] + w = depth * INDENT + (W14[label] if depth == 0 else W13[label]) + return w + (7 + W13[suffix] if suffix else 0) + + +def tree_fixed_h(rows: list[Row]) -> float: + """Height of everything in a tree that does not stretch: bands and gaps.""" + return sum((BAND_GAP_ABOVE if i else 0) + BAND_H + BAND_GAP_BELOW if k == "band" else g + for i, (k, _d, _l, _s, g) in enumerate(rows)) + + +def n_rows(rows: list[Row]) -> int: + return sum(1 for r in rows if r[0] != "band") + + +def tree(x: float, y: float, rows: list[Row], row_h: float, band_x: float, band_w: float) -> float: + """Draw the banded sections of one column; returns the y it ends at. + + x is the tree's own left edge (indented from band_x); bands are drawn + full width from band_x. Connectors are lines, not characters: a + vertical dropping from just under the parent's label to the child + row's center at the parent's connector column, and a short horizontal + stub into the child label. + """ + centers: dict[int, float] = {} + cy = y + for i, (kind, depth, label, suffix, gap) in enumerate(rows): + if kind == "band": + if i: + cy += BAND_GAP_ABOVE + b = base("rectangle", uid("tb"), band_x, cy, band_w, BAND_H, + "transparent", BAND_FILL, sw=1) + b["roundness"] = {"type": 3} + els.append(b) + els.append(text(uid("tbt"), band_x + BAND_PAD_X, + cy + (BAND_H - BAND_SIZE * 1.25) / 2, label, + size=BAND_SIZE, color=AMBER["text"])) + cy += BAND_H + BAND_GAP_BELOW + centers.clear() # a new section starts its own hierarchy + continue + + cy += gap + row_h / 2 + if kind == "ann": + els.append(text(uid("ta"), x + depth * INDENT + ANN_INDENT, + cy - CHILD_SIZE * 1.25 / 2, label, + size=CHILD_SIZE, color=AMBER["mid"])) + cy += row_h / 2 + continue + + size = ROOT_SIZE if depth == 0 else CHILD_SIZE + color = AMBER["text"] if depth == 0 else AMBER["stroke"] + tx = x + depth * INDENT + if depth > 0: + col_x = x + (depth - 1) * INDENT + 7 + # start below the parent's text, not at its center, or the + # vertical strikes through the parent label's first glyph + connector(col_x, centers[depth - 1] + 10, col_x, cy) + connector(col_x, cy, tx - 4, cy) + els.append(text(uid("tn"), tx, cy - size * 1.25 / 2, label, size=size, color=color)) + if suffix: + els.append(text(uid("tns"), tx + W14[label] + 7, cy - CHILD_SIZE * 1.25 / 2, + suffix, size=CHILD_SIZE, color=AMBER["mid"])) + centers[depth] = cy + cy += row_h / 2 + return cy + + +# ---------------------------------------------------------------- column +MARGIN = 18 # column inner margin: nothing sits against the border +TREE_INDENT = 16 # trees hang this far inside the band's left edge +PAD_TOP = 16 +PAD_BOTTOM = 16 +TITLE_SIZE = 17 +TAG_SIZE = 14 +NOTE_SIZE = 13 +BLOCK_H = 8 + 2 * NOTE_SIZE * 1.25 + 8 +NOTE_GAP = 20 +TREE_BLOCK_GAP = 18 + +ICON_Y = PAD_TOP +TITLE_Y = ICON_Y + ICON + 9 +TAG_Y = TITLE_Y + TITLE_SIZE * 1.25 + 5 +RULE_Y = TAG_Y + TAG_SIZE * 1.25 + 11 +TREE_TOP = RULE_Y + 12 + + +def notes_h(notes: list[str]) -> float: + return NOTE_GAP + len(notes) * NOTE_SIZE * 1.25 if notes else 0.0 + + +def column_group_h(rows: list[Row], notes: list[str], row_h: float = ROW_H) -> float: + return tree_fixed_h(rows) + n_rows(rows) * row_h + notes_h(notes) + + +def fitted_row_h(rows: list[Row], notes: list[str], region_h: float) -> float: + """Spread a short column's rows to fill the shared region, up to a cap, + so a column with fewer rows does not sit in a hole.""" + spare = region_h - notes_h(notes) - tree_fixed_h(rows) + return max(ROW_H, min(ROW_H_MAX, spare / n_rows(rows))) + + +def axis_column(x: float, y: float, w: float, region_h: float, icon_path: str, title: str, + tagline: str, rows: list[Row], notes: list[str], desc_lines: list[str]) -> None: + r = base("rectangle", uid("col"), x, y, w, COL_H, AMBER["stroke"], AMBER["block"], sw=2) + r["roundness"] = {"type": 3} + els.append(r) + + cx = x + w / 2 + image(uid("coli"), cx - ICON / 2, y + ICON_Y, ICON, ICON, icon_path, color=AMBER["stroke"]) + els.append(text(uid("colt"), x + 8, y + TITLE_Y, title, size=TITLE_SIZE, + color=AMBER["text"], align="center", w=w - 16)) + els.append(text(uid("coltag"), x + 8, y + TAG_Y, tagline, size=TAG_SIZE, + color=AMBER["mid"], align="center", w=w - 16)) + rule(x + MARGIN, y + RULE_Y, w - 2 * MARGIN) # same span as the bands + + band_x, band_w = x + MARGIN, w - 2 * MARGIN + row_h = fitted_row_h(rows, notes, region_h) + # top-aligned, not centered: the first band of every column then sits + # on the same line just under the rule, which is what makes the three + # read as one figure. Leftover space falls above the bottom block. + top = y + TREE_TOP + tree_bottom = tree(band_x + TREE_INDENT, top, rows, row_h, band_x, band_w) + + ny = tree_bottom + NOTE_GAP + for ln in notes: + els.append(text(uid("coln"), band_x, ny, ln, size=NOTE_SIZE, color=AMBER["mid"])) + ny += NOTE_SIZE * 1.25 + + rb = base("rectangle", uid("colblk"), band_x, y + BLOCK_Y, band_w, BLOCK_H, + AMBER["text"], AMBER["stroke"], sw=1) + rb["roundness"] = {"type": 3} + els.append(rb) + by = y + BLOCK_Y + (BLOCK_H - len(desc_lines) * NOTE_SIZE * 1.25) / 2 + 2 + for ln in desc_lines: + els.append(text(uid("colblkt"), band_x + 6, by, ln, size=NOTE_SIZE, + color="#fef3c7", align="center", w=band_w - 12)) + by += NOTE_SIZE * 1.25 + + +# ---------------------------------------------------------------- content +FINDINGS: list[Row] = [ + ("band", 0, "Findings", None, 0), + ("node", 0, "pulmonary nodule", None, 0), + ("node", 1, "solid", None, 0), + ("node", 1, "part-solid", None, 0), + ("node", 1, "non-solid", None, 0), + ("band", 0, "Diagnoses", None, 0), + ("node", 0, "pulmonary neoplasm", None, 0), + ("node", 1, "non-small cell lung cancer", None, 0), + ("node", 2, "adenocarcinoma of the lung", None, 0), + ("band", 0, "Assessments", None, 0), + ("node", 0, "Lung-RADS", None, 0), + ("node", 0, "Salter-Harris category", None, 0), + ("band", 0, "Data Elements", None, 0), + ("node", 0, "presence", None, 0), + ("ann", 0, "present · absent · indeterminate · unknown", None, 0), + ("node", 0, "severity", None, 0), + ("ann", 0, "minimal · mild · moderate · severe", None, 0), + # a measurement names the KIND of quantity; the unit family follows + ("band", 0, "Measurements", None, 0), + ("node", 0, "length", None, 0), + ("ann", 0, "mm, cm, ...", None, 0), + ("node", 0, "CT density", None, 0), + ("ann", 0, "HU", None, 0), +] + +ANATOMY: list[Row] = [ + ("band", 0, "thorax", None, 0), + ("node", 0, "lung", None, 0), + ("node", 1, "right lung", None, 0), + ("node", 2, "upper lobe of right lung", None, 0), + ("node", 2, "middle lobe of right lung", None, 0), + ("node", 2, "lower lobe of right lung", None, 0), + ("node", 1, "left lung", None, 0), + ("node", 0, "pleural space", None, 0), + ("node", 0, "mediastinum", None, 0), + ("node", 1, "heart", None, 0), + ("band", 0, "abdomen", None, 0), + ("node", 0, "kidney", None, 0), + ("node", 1, "left kidney", None, 0), + ("node", 1, "right kidney", None, 0), + ("node", 2, "renal pelvis", None, 0), + ("node", 0, "liver", None, 0), + ("node", 0, "spleen", None, 0), + ("band", 0, "head", None, 0), + ("node", 0, "brain", None, 0), + ("node", 1, "cerebellum", None, 0), + ("node", 1, "frontal lobe", None, 0), + ("node", 0, "orbit", None, 0), + ("band", 0, "pelvis", None, 0), + ("node", 0, "urinary bladder", None, 0), + ("node", 0, "prostate", None, 0), +] + +EXAMS: list[Row] = [ + ("band", 0, "CT", None, 0), + ("node", 0, "CT Chest", "(preferred family)", 0), + ("node", 1, "CT Chest WO contrast", None, 0), + ("node", 1, "CT Chest W contrast IV", None, 0), + ("node", 1, "CT Chest WO and W contrast IV", None, 0), + ("node", 1, "CT Lung parenchyma WO contrast", None, 0), + ("node", 1, "CTA Chest vessels", None, 0), + ("band", 0, "MR", None, 0), + ("node", 0, "MR Knee", "(preferred family)", 0), + ("node", 1, "MR Knee WO contrast", None, 0), + ("node", 1, "MR Knee W contrast IV", None, 0), + ("node", 0, "MR Brain", "(preferred family)", 6), + ("node", 1, "MR Brain WO contrast", None, 0), + ("node", 1, "MR Brain WO and W contrast IV", None, 0), + ("band", 0, "XR", None, 0), + ("node", 0, "XR Knee", "(preferred family)", 0), + ("node", 1, "XR Knee 2 Views", None, 0), + ("node", 1, "XR Knee 3 Views", None, 0), + ("node", 0, "XR Chest", "(preferred family)", 6), + ("node", 1, "XR Chest 2 Views", None, 0), + ("node", 1, "XR Chest PA and Lateral", None, 0), + ("band", 0, "US", None, 0), + ("node", 0, "US Abdomen", "(preferred family)", 0), + ("node", 1, "US Abdomen limited", None, 0), + ("node", 1, "US Abdomen RUQ", None, 0), + ("node", 0, "US Thyroid gland", None, 6), +] + +specs = [ + ("icons/lucide-tag.svg", "Findings/Diagnoses", "WHAT was found", FINDINGS, [], + ["layered over: finding models", "· ACR/RSNA CDEs"]), + ("icons/healthicons-lungs.svg", "Anatomic Locations", "WHERE it is", ANATOMY, [], + ["layered over: RadLex"]), + ("icons/healthicons-xray.svg", "Exam Types", "HOW it was seen", EXAMS, + ["each a LOINC/RSNA Playbook entry"], ["layered over: LOINC/RSNA", "Playbook"]), +] + +# ---------------------------------------------------------------- layout +GAP_COL = 20 +CUSHION = 8 # clear space between the longest row and the band's right edge +COL_W = 316 # 2*MARGIN + TREE_INDENT + widest row (255.9) + CUSHION +CONTENT_W = 3 * COL_W + 2 * GAP_COL +Y0 = 0 + +# The widest row sets the column width. A row must end inside the band +# above it, not merely inside the column, or it reads as an overflow -- +# so assert that here rather than discovering it in the render. +_fits = COL_W - 2 * MARGIN - TREE_INDENT - CUSHION +_widest = max((row_width(*r), r[2]) for _i, _t, _g, rows, _n, _d in specs for r in rows) +assert _widest[0] <= _fits, f"row {_widest[1]!r} ({_widest[0]}) exceeds {_fits}" + +REGION_H = max(column_group_h(rows, notes) for _i, _t, _g, rows, notes, _d in specs) +COL_H = TREE_TOP + REGION_H + TREE_BLOCK_GAP + BLOCK_H + PAD_BOTTOM +BLOCK_Y = COL_H - PAD_BOTTOM - BLOCK_H + +for i, (ipath, title, tagline, rows, notes, desc_lines) in enumerate(specs): + axis_column(i * (COL_W + GAP_COL), Y0, COL_W, REGION_H, ipath, title, tagline, + rows, notes, desc_lines) + +save(figure("three-axes").excalidraw) +print(f"figure {CONTENT_W}x{COL_H} region {REGION_H} widest row {_widest[1]!r} {_widest[0]}") +for _i, t, _g, rows, notes, _d in specs: + print(f" {t}: {n_rows(rows)} rows, row_h {fitted_row_h(rows, notes, REGION_H):.1f}") diff --git a/tools/diagrams/build_two_collections.py b/tools/diagrams/build_two_collections.py new file mode 100644 index 0000000..c5207df --- /dev/null +++ b/tools/diagrams/build_two_collections.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +"""Build the "one kind of content, two collections" figure for the Finding +Models and Common Data Elements hub page. + +Finding/diagnosis definitions are one kind of content held in two collections +that differ only in how they are released. The figure says that three times +over: the two panels carry the same green as the finding/diagnosis definition +nodes in build_two_planes.py (excalib's FINDING palette, via FIND below), the +band between them reads as a beta-to-release gradient, and the amber band +underneath is the one schema both are moving onto. + + +--------------------------+ ~~~~~~~ +--------------------------+ GREEN + | (tag) Open Imaging | graduate | (tag) ACR/RSNA Common | + | Finding Models | ------> | Data Elements | + | inclusive . beta ... | [gradient]| well-reviewed . release | + | [hydronephrosis][renal] | <------ | [abdominal aortic an.] | + | [pulm nodule]---+-- index --+->[pulm nodule] | + +--------------------------+ codes +--------------------------+ + | moving onto | moving onto + +---------------------------------------------------------------+ AMBER + | the next-generation schema | + | one graph of interconnected concepts for both collections | + +---------------------------------------------------------------+ + +Constraints that drove the layout and are easy to break by nudging a number: + +1. The gap between the panels is 172px and it has to hold both band labels at + 13px, so every line of them is wrapped to <= 143px ("definition keeps its + OIFM" is the widest). Widening the panels to fit their own text on fewer + lines narrows this gap and breaks the labels instead. +2. The gap is a five-item vertical stack -- label, arrow, gradient bar, arrow, + label -- and the dotted index-codes link has to clear all of it. That is why + the two "pulmonary nodule" nodes are the BOTTOM row of each panel and the + other examples are the top row: the link then runs at y=220, below the band + stack, and crosses nothing. +3. Those two nodes face each other across the gap -- the left one is flush to + its panel's right padding, the right one flush to its panel's left padding -- + so the link is a short straight run rather than a line across two panels. +4. Arrowheads are drawn by excalib.tri_head(), not Excalidraw's built-ins, + which take their size from the line's strokeWidth; HEAD sets it directly so + the thin band arrows keep small solid heads. +5. The amber band's title is centred rather than carrying an icon in the left + corner like the panels do: the two "moving onto" arrows come down at the + panel centres (x=182 and x=718) and would crowd a left-aligned header. + +Text widths are measured, not estimated (see tools/diagrams/README.md): every +width below is `measureText` at `px Helvetica, Segoe UI Emoji` in +headless Chromium. + +Icons: Lucide (ISC) tag, the same glyph build_two_planes.py and +build_three_axes.py use for a finding/diagnosis definition. See icons/LICENSES.md. + +Output: the "two-collections" entry in figures.toml; render with render_excalidraw.py. +""" +from __future__ import annotations + +from excalib import els, base, text, image, save, figure, tri_head, AMBER, CAPTION + +# ---------------------------------------------------------------- palette +# The finding/diagnosis definition green, straight from build_two_planes.py's +# KIND_DEF / build_foundation_network.py's FINDING: the panels ARE that node +# kind, drawn large. +FIND = {"band": "#d1fae5", "block": "#a7f3d0", "stroke": "#059669", + "text": "#064e3b", "mid": "#047857"} +# Beta -> release, seven steps of one hue so the band reads as a single ramp. +RAMP = ["#ecfdf5", "#d1fae5", "#a7f3d0", "#6ee7b7", "#34d399", "#10b981", "#059669"] + +W = 900 # canvas width +ICON = 26 # Lucide stroke art +PAD = 18 # panel inner padding +HEAD = 9.0 # filled arrowhead length (band + "moving onto" arrows) + +PANEL_Y, PANEL_H = 0, 272 +LW = RW = 364 # panel width +RX = W - RW # right panel x (536) +GAP_L, GAP_R = LW, RX # 364 .. 536 +GAP_C = (GAP_L + GAP_R) / 2 # 450 + +ROW_A_Y, ROW_B_Y, ROW_H = 144, 200, 40 +AMBER_Y, AMBER_H = 320, 92 + +_uid = [0] + + +def uid(prefix: str) -> str: + _uid[0] += 1 + return f"{prefix}{_uid[0]}" + + +# ---------------------------------------------------------------- helpers +def panel(id_: str, x: float, w: float, pal: dict) -> dict: + r = base("rectangle", id_, x, PANEL_Y, w, PANEL_H, pal["stroke"], pal["band"], sw=2) + r["roundness"] = {"type": 3} + els.append(r) + return r + + +def header(x: float, title: str, title_w: float, blocks: list[list[tuple[str, float]]], + pal: dict) -> None: + """Tag icon at the panel's left padding, title beside it, description lines + aligned to the title (not to the icon). `blocks` groups those lines: the + channel descriptors and the maintainer are separate blocks, set 7px apart, + so a descriptor that wraps to two lines still reads as one statement rather + than running into the maintainer line below it.""" + image(uid("hicon"), x + PAD, PANEL_Y + 16, ICON, ICON, "icons/lucide-tag.svg", + color=pal["stroke"]) + tx = x + PAD + ICON + 10 + els.append(text(uid("ht"), tx, PANEL_Y + 18, title, size=17, color=pal["text"], w=title_w)) + y = PANEL_Y + 50 + for block in blocks: + for s, lw in block: + els.append(text(uid("hs"), tx, y, s, size=13, color=pal["mid"], w=lw)) + y += 13 * 1.25 + y += 7 + + +def node(id_: str, x: float, y: float, label: str, label_w: float, pal: dict) -> dict: + """A tiny example definition: one 13px line in a small rounded box.""" + w = label_w + 20 + r = base("rectangle", id_, x, y, w, ROW_H, pal["stroke"], pal["block"], sw=2) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(id_ + "_t", x + 8, y + (ROW_H - 13 * 1.25) / 2, label, size=13, + color=pal["text"], align="center", w=w - 16)) + return r + + +def line(id_: str, pts: list[tuple[float, float]], color: str, sw=2, dashed=False, + head: float | None = HEAD) -> None: + """A straight or routed run, finished with a solid triangle whose size is set + here rather than derived from strokeWidth. head=None draws no head.""" + x0, y0 = pts[0] + end_pts = list(pts) + if head: + tip, frm = pts[-1], pts[-2] + dx, dy = tip[0] - frm[0], tip[1] - frm[1] + length = (dx ** 2 + dy ** 2) ** 0.5 or 1.0 + end_pts = list(pts[:-1]) + [(tip[0] - dx / length * head * 0.55, + tip[1] - dy / length * head * 0.55)] + ex, ey = end_pts[-1] + ar = base("arrow", id_, x0, y0, ex - x0, ey - y0, color, "transparent", + dashed=dashed, sw=sw) + ar.update({"points": [[px - x0, py - y0] for px, py in end_pts], + "startBinding": None, "endBinding": None, "startArrowhead": None, + "endArrowhead": None, "boundElements": None}) + els.append(ar) + if head: + tri_head(id_ + "_h", pts[-1], pts[-2], color, size=head) + + +def caption(id_: str, cx: float, y_top: float, lines: list[tuple[str, float]], + size=13, color=CAPTION) -> None: + """A centred multi-line label, each line given its MEASURED width so the + block centres exactly on cx.""" + w = max(lw for _, lw in lines) + y = y_top + for s, lw in lines: + els.append(text(uid(id_), cx - w / 2, y, s, size=size, color=color, + align="center", w=w)) + y += size * 1.25 + + +def plate(id_: str, cx: float, cy: float, s: str, w: float, size=13, color=CAPTION, + fill="#ffffff", pad_x=6, pad_y=3) -> None: + """An edge label on an opaque plate, centred on (cx, cy), so it masks the + line it sits on. w is the MEASURED text width.""" + bw, bh = w + 2 * pad_x, size * 1.25 + 2 * pad_y + r = base("rectangle", id_, cx - bw / 2, cy - bh / 2, bw, bh, "transparent", fill, sw=0) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(id_ + "_t", cx - bw / 2 + pad_x, cy - bh / 2 + pad_y, s, size=size, + color=color, align="center", w=w)) + + +# ================================================================ the two panels +panel("panel_oifm", 0, LW, FIND) +panel("panel_cde", RX, RW, FIND) + +header(0, "Open Imaging Finding Models", 226.8, + [[("inclusive · beta channel ·", 143.8), + ("rapid additions and updates", 159.7)], + [("maintained by the OIDM project", 182.8)]], FIND) +header(RX, "ACR/RSNA Common Data Elements", 277.8, + [[("well-reviewed · release channel · stable", 228.3)], + [("American College of Radiology ·", 186.4), + ("Radiological Society of North America ·", 228.2), + ("radelement.org", 87.4)]], FIND) + +# Top row: the examples that live in only one collection. Bottom row: the one +# definition that lives in both, flush to the facing edges so the dotted link +# between them is a short straight run clear of the band stack above. +node("n_hydro", PAD, ROW_A_Y, "hydronephrosis", 88.9, FIND) +node("n_calc", PAD + 88.9 + 20 + 14, ROW_A_Y, "renal calculus", 79.5, FIND) +nod_l = node("n_nodule_l", LW - PAD - (103.3 + 20), ROW_B_Y, "pulmonary nodule", 103.3, FIND) +node("n_aaa", RX + PAD, ROW_A_Y, "abdominal aortic aneurysm", 156.1, FIND) +nod_r = node("n_nodule_r", RX + PAD, ROW_B_Y, "pulmonary nodule", 103.3, FIND) + +# ================================================================ the band between +# Five items stacked in the 172px gap: label, arrow, gradient bar, arrow, label. +caption("bl_grad", GAP_C, 28, + [("definitions proved out", 123.6), ("here graduate to review", 136.6)]) +line("b_grad", [(GAP_L + 4, 72), (GAP_R - 4, 72)], FIND["stroke"], sw=2) + +BAR_Y, BAR_H = 86, 16 +BAR_X, BAR_W = GAP_L + 4, (GAP_R - 4) - (GAP_L + 4) # the same span as the two arrows +step = BAR_W / len(RAMP) +for i, col in enumerate(RAMP): + els.append(base("rectangle", uid("ramp"), BAR_X + i * step, BAR_Y, step + 0.5, BAR_H, + "transparent", col, sw=0)) + +line("b_cross", [(GAP_R - 4, 114), (GAP_L + 4, 114)], FIND["stroke"], sw=2) +caption("bl_cross", GAP_C, 124, + [("cross-links: a migrated", 130.0), ("definition keeps its OIFM", 143.1), + ("entry and carries its", 113.4), ("CDE identity", 72.2)]) + +# ================================================================ the shared definition +LINK_Y = ROW_B_Y + ROW_H / 2 +line("l_index", [(nod_l["x"] + nod_l["width"], LINK_Y), (nod_r["x"], LINK_Y)], + FIND["stroke"], sw=2, dashed=True, head=None) +plate("l_index_l", GAP_C, LINK_Y, "index codes", 69.4, color=FIND["mid"]) + +# ================================================================ the next-generation schema +amber = base("rectangle", "amber", 0, AMBER_Y, W, AMBER_H, AMBER["stroke"], AMBER["band"], sw=2) +amber["roundness"] = {"type": 3} +els.append(amber) + +A_TOP = AMBER_Y + (AMBER_H - (17 * 1.25 + 6 + 13 * 1.25)) / 2 +els.append(text("a_title", (W - 210.7) / 2, A_TOP, "the next-generation schema", + size=17, color=AMBER["text"], align="center", w=210.7)) +els.append(text("a_sub", (W - 329.5) / 2, A_TOP + 17 * 1.25 + 6, + "one graph of interconnected concepts for both collections", + size=13, color=AMBER["mid"], align="center", w=329.5)) + +for i, cx in enumerate((LW / 2, RX + RW / 2)): + line(f"m_onto{i}", [(cx, PANEL_Y + PANEL_H + 2), (cx, AMBER_Y - 2)], + AMBER["stroke"], sw=2) + els.append(text(uid("m_l"), cx + 12, (PANEL_Y + PANEL_H + AMBER_Y) / 2 - 8, + "moving onto", size=13, color=AMBER["mid"], w=70.8)) + +save(figure("two-collections").excalidraw) diff --git a/tools/diagrams/build_two_planes.py b/tools/diagrams/build_two_planes.py new file mode 100644 index 0000000..abae47f --- /dev/null +++ b/tools/diagrams/build_two_planes.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 +"""Build the "two planes" overview figure for the knowledgebase root page. + +The one-picture version of the whole model: Patient Context and Foundation +Context as two tilted planes, one above the other, with every fact recorded +about this patient attached downward to the shared knowledge of what such a +thing is. + + +---------------------------------------------------------------+ TEAL + | Patient Context ,-------- found on --------------. | + | [CT chest WO] <-found on- [pulmonary nodule] [pleural eff] | + +---------------------------------------------------------------+ + | is a | is a | located at | is a | located at <- rose + +---------------------------------------------------------------+ AMBER + | Foundation Context | + | [CT Chest WO contrast] [pulm nodule def] [pleural eff def] | tier 1 + | |member of family |seen on |occurs at |occurs at | + | [CT Chest] -modality-> [CT] | | | tier 2 + | |covers | | | + | [thorax] -contains-> [lung] -> [upper lobe] [pleural space] | tier 3 + | `------------------ contains ------------------^ | + +---------------------------------------------------------------+ + +Geometry: both planes are the same parallelogram -- top edge offset SKEW px +to the right of the bottom edge -- so the pair reads as two isometric sheets +seen from slightly above. Because the top edge is inset, anything placed near +a plane's top must clear `left_edge(y)`, which is what `lx()` computes; every +x-coordinate below was checked against it rather than eyeballed. + +Constraints that drove the layout and are easy to break by nudging a number: + +1. The lower plane's header ("Foundation Context" + subtitle) occupies + x = 68..283 at y = 264..308, so the leftmost cross-plane edge has to come + down at x >= ~300 to miss it. Tier 1 therefore starts at x=120 and the + plane's left flank carries the grey "knowledge continues" nodes instead. +2. Five cross-plane edges, and none may cross another, so their x-order is the + same where they leave the upper plane (300 / 490 / 602 / 720 / 866) and + where they land (300 / 490 / 602 / 720 / 856). Four are plumb; the fifth + leans 10px to clear the effusion definition's right edge. Two of them run + past tier 1 into tier 3 and thread the gaps between tier 1's boxes -- x=602 + uses the 32px gap between the two definitions -- so those boxes cannot be + moved together, and neither definition can be widened. +3. With two findings in the upper plane, the second is too far right for a + straight "found on" edge back to the exam. Its edge runs ABOVE the row, in + the lane at y=72: every cross-plane edge leaves a node's BOTTOM, so the + band between the header and the row is the one place nothing crosses. +4. The foundation plane is three tiers and the vertical axis is generality: + what the patient's data attaches to sits in tier 1, and each tier below is + the more general thing. The exam axis (Playbook entry -> family -> modality) + and the anatomy axis (upper lobe <- lung <- thorax) both read that way, + which is why the family, not the specific entry, covers the thorax. +5. The pleural space is the thorax's second anatomic branch, but it sits at the + far right, under the effusion's definition. Its "contains" edge is drawn as + a lane BELOW tier 3 rather than a long horizontal through it, which would + have to cross both lung and the upper lobe. +6. Arrowheads are drawn by excalib.tri_head(), not by Excalidraw's built-in + ones: the built-ins take their size from the line's strokeWidth, which + forces a thick rose edge to carry an outsized head. HEAD_R and HEAD_G set + the two sizes directly. + +Text widths are measured, not estimated (see tools/diagrams/README.md): every +box width below is `measured width + padding` from canvas `measureText` at +`px Helvetica, Segoe UI Emoji` in headless Chromium. + +Icons: Health Icons (CC0/MIT, outline, filled-path art -- rendered at 32px) +for the x-ray and lungs glyphs; Lucide (ISC/MIT, stroke art -- 28px) for +layers, book-open, clipboard-list, clipboard and tag. See icons/LICENSES.md. + +Output: the "two-planes" entry in figures.toml; render with render_excalidraw.py. +""" +from __future__ import annotations + +from excalib import (els, base, text, image, save, figure, tri_head, + AMBER, TEAL, ROSE, CAPTION) + +# ---------------------------------------------------------------- constants +SKEW = 55 # how far each plane's top edge sits right of its bottom edge +PW = 880 # plane width, measured along an edge +ICON = 28 # Lucide (stroke art) +ICON_FILLED = 32 # Health Icons (filled-path art, optically smaller at equal size) + +CROSS = ROSE["stroke"] # deep rose: the cross-plane attachments +FAINT = "#a8a29e" # warm grey: "the knowledge continues" edges +GREY_FILL, GREY_STROKE = "#e7e5e4", "#a8a29e" +REL = "#78716c" # grey relationship edges inside the lower plane + +# Node kinds inside the Foundation Context plane. Fills and strokes are taken +# straight from build_foundation_network.py's FINDING / ANATOMY / EXAM / GREY +# so the two figures name the same kinds with the same colours. Each dict keeps +# excalib's band/block/stroke/text/mid shape; only "band" (the plane tint) is +# shared, since every one of these sits on the amber plane. The modality grey +# uses a darker stroke than the mini-network's #9ca3af, which is too faint to +# hold an edge against the amber ground. +KIND_DEF = {"band": AMBER["band"], "block": "#d1fae5", "stroke": "#059669", + "text": "#064e3b", "mid": "#047857"} # finding/diagnosis definition +KIND_ANAT = {"band": AMBER["band"], "block": "#bfdbfe", "stroke": "#1d4ed8", + "text": "#1e3a8a", "mid": "#1d4ed8"} # anatomic location +KIND_EXAM = AMBER # exam type (already the amber block) +KIND_MOD = {"band": AMBER["band"], "block": "#e5e7eb", "stroke": "#6b7280", + "text": "#374151", "mid": "#4b5563"} # modality + +P1_Y, P1_H = 0, 190 # upper plane (Patient Context) +P2_Y, P2_H = 250, 440 # lower plane (Foundation Context); 60px gap between +HEAD_R, HEAD_G = 13.0, 10.0 # filled arrowhead length: rose edges, grey/teal edges + +_uid = [0] + + +def uid(prefix: str) -> str: + _uid[0] += 1 + return f"{prefix}{_uid[0]}" + + +def lx(y: float, py: float, ph: float) -> float: + """Left edge of a plane at height y -- the top edge is SKEW px further right.""" + return SKEW * (1 - (y - py) / ph) + + +# ---------------------------------------------------------------- helpers +def plane(id_: str, py: float, ph: float, pal: dict) -> None: + """A tilted parallelogram: a closed Excalidraw line, filled with the band tint.""" + ln = base("line", id_, SKEW, py, PW + SKEW, ph, pal["stroke"], pal["band"], sw=2) + ln.update({"points": [[0, 0], [PW, 0], [PW - SKEW, ph], [-SKEW, ph], [0, 0]], + "roundness": None, "boundElements": None}) + els.append(ln) + + +def header(x: float, y: float, icon_path: str, pal: dict, title: str, subtitle: str, + filled_icon=False) -> None: + """Plane title with its icon to the left and a subtitle under it.""" + s = ICON_FILLED if filled_icon else ICON + image(uid("hicon"), x, y + 2 - (s - ICON) / 2, s, s, icon_path, color=pal["stroke"]) + els.append(text(uid("htitle"), x + ICON + 12, y, title, size=20, color=pal["text"])) + els.append(text(uid("hsub"), x + ICON + 12, y + 26, subtitle, size=13, color=pal["mid"])) + + +def node_rect(id_: str, x: float, y: float, w: float, h: float, pal: dict) -> dict: + r = base("rectangle", id_, x, y, w, h, pal["stroke"], pal["block"], sw=2) + r["roundness"] = {"type": 3} + els.append(r) + return r + + +def hnode(id_: str, x: float, y: float, w: float, h: float, icon_path: str, pal: dict, + title: str, sub: str, filled_icon=False) -> dict: + """Icon on the left at a fixed inset, title line, lighter second line -- + the style used for the two Patient Context nodes.""" + r = node_rect(id_, x, y, w, h, pal) + s = ICON_FILLED if filled_icon else ICON + image(uid("ni"), x + 12 - (s - ICON) / 2, y + (h - s) / 2, s, s, icon_path, color=pal["stroke"]) + tx = x + 12 + ICON + 10 + top = y + (h - (16 * 1.25 + 2 + 13 * 1.25)) / 2 + els.append(text(uid("nt"), tx, top, title, size=16, color=pal["text"], w=w - (tx - x) - 10)) + els.append(text(uid("ns"), tx, top + 16 * 1.25 + 2, sub, size=13, color=pal["mid"], + w=w - (tx - x) - 10)) + return r + + +def chip(id_: str, x: float, y: float, w: float, h: float, pal: dict, title: str, + desc: str | None = None, icon_path: str | None = None, filled_icon=False, + text_w: float | None = None) -> dict: + """The Foundation Context node: content vertically centred, and horizontally + either centred text (no icon) or an icon + text-column group centred as a + unit. text_w is the MEASURED width of the widest of title/desc, needed to + centre that group; with no icon it is not used. Every foundation node is + the same height, so the three tiers read as three rows of one grid.""" + r = node_rect(id_, x, y, w, h, pal) + lines_h = 15 * 1.25 + (2 + 13 * 1.25 if desc else 0) + ty = y + (h - lines_h) / 2 + if icon_path: + s_ = ICON_FILLED if filled_icon else ICON + gx = x + (w - (s_ + 10 + text_w)) / 2 + image(uid("ni"), gx, y + (h - s_) / 2, s_, s_, icon_path, color=pal["stroke"]) + els.append(text(uid("nt"), gx + s_ + 10, ty, title, size=15, color=pal["text"], w=text_w)) + if desc: + els.append(text(uid("nd"), gx + s_ + 10, ty + 15 * 1.25 + 2, desc, size=13, + color=pal["mid"], w=text_w)) + else: + els.append(text(uid("nt"), x + 8, ty, title, size=15, color=pal["text"], + align="center", w=w - 16)) + if desc: + els.append(text(uid("nd"), x + 8, ty + 15 * 1.25 + 2, desc, size=13, + color=pal["mid"], align="center", w=w - 16)) + return r + + +def dot(id_: str, cx: float, cy: float, r: float) -> None: + e = base("ellipse", id_, cx - r, cy - r, 2 * r, 2 * r, GREY_STROKE, GREY_FILL, sw=1) + e["opacity"] = 70 + els.append(e) + + +def faint(x1: float, y1: float, x2: float, y2: float) -> None: + ln = base("line", uid("f"), x1, y1, x2 - x1, y2 - y1, FAINT, "transparent", sw=1) + ln.update({"points": [[0, 0], [x2 - x1, y2 - y1]], "boundElements": None, "opacity": 60}) + els.append(ln) + + +def path(id_: str, pts: list[tuple[float, float]], color: str, sw=3, head=HEAD_G) -> None: + """Multi-point routed arrow (excalib.arrow()'s elbow modes only do 3 points), + finished with excalib.tri_head(): a solid filled triangle whose size is set + here rather than derived from the line's strokeWidth, so a thick rose edge + and a thin grey one can carry heads of deliberately chosen sizes.""" + x0, y0 = pts[0] + tip, frm = pts[-1], pts[-2] + dx, dy = tip[0] - frm[0], tip[1] - frm[1] + length = (dx ** 2 + dy ** 2) ** 0.5 or 1.0 + # stop the line a little short so the head's base sits on it, not past it + end = (tip[0] - dx / length * head * 0.55, tip[1] - dy / length * head * 0.55) + line_pts = list(pts[:-1]) + [end] + ar = base("arrow", id_, x0, y0, end[0] - x0, end[1] - y0, color, "transparent", sw=sw) + ar.update({"points": [[px - x0, py - y0] for px, py in line_pts], "startBinding": None, + "endBinding": None, "startArrowhead": None, "endArrowhead": None, + "boundElements": None}) + els.append(ar) + tri_head(id_ + "_h", tip, frm, color, size=head) + + +def plate(id_: str, cx: float, cy: float, s: str, w: float, size=13, color=CAPTION, + fill="#ffffff", pad_x=6, pad_y=3) -> None: + """An edge label on an opaque plate, centred on (cx, cy), so it masks the + line it sits on. w is the MEASURED text width. fill is the tint the label + lands on -- a white patch inside a tinted plane is visible, so pass the + plane's band colour for labels drawn inside a plane.""" + bw, bh = w + 2 * pad_x, size * 1.25 + 2 * pad_y + r = base("rectangle", id_, cx - bw / 2, cy - bh / 2, bw, bh, "transparent", fill, sw=0) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(id_ + "_t", cx - bw / 2 + pad_x, cy - bh / 2 + pad_y, s, size=size, + color=color, align="center", w=w)) + + +# ================================================================ planes +plane("plane_patient", P1_Y, P1_H, TEAL) +plane("plane_foundation", P2_Y, P2_H, AMBER) + +header(67, P1_Y + 12, "icons/lucide-layers.svg", TEAL, + "Patient Context", "this patient · this exam") +header(68, P2_Y + 14, "icons/lucide-book-open.svg", AMBER, + "Foundation Context", "shared, curated knowledge") + +# ================================================================ upper plane: one exam, two findings +# All three sit on one row, so each finding's "found on" edge is short and +# neither crosses the cross-plane drops that leave the row's underside. The +# second finding is too far right for a straight edge back to the exam, so its +# edge runs ABOVE the row, in the lane at y=72 between the header and the nodes +# -- every cross-plane drop leaves a node's BOTTOM, so nothing is up there to +# cross. +ROW1_Y, ROW1_H = 92, 66 +exam = hnode("exam", 140, ROW1_Y, 246, ROW1_H, "icons/lucide-clipboard-list.svg", TEAL, + "CT chest without contrast", "this patient \u00b7 2026-03-04") +nodule = hnode("nodule", 470, ROW1_Y, 196, ROW1_H, "icons/lucide-clipboard.svg", TEAL, + "pulmonary nodule", "present \u00b7 8 mm \u00b7 new") +effusion = hnode("effusion", 706, ROW1_Y, 176, ROW1_H, "icons/lucide-clipboard.svg", TEAL, + "pleural effusion", "absent") +ROW1_B = ROW1_Y + ROW1_H + +path("e_found1", [(470, 125), (392, 125)], TEAL["stroke"], sw=2) +plate("l_found1", 431, 125, "found on", 50.6, color=TEAL["mid"], fill=TEAL["band"]) +path("e_found2", [(790, ROW1_Y), (790, 72), (250, 72), (250, ROW1_Y - 3)], TEAL["stroke"], sw=2) +plate("l_found2", 560, 72, "found on", 50.6, color=TEAL["mid"], fill=TEAL["band"]) + +# ================================================================ lower plane, tier 1 +# What the patient's own data attaches to: the specific Playbook exam type and +# one definition per finding. Widest lines measure 150.9 / 152.5 / 152.5px; +# with the 32/28px icon, its 10px gutter and 24px of padding that gives 218/216. +T1_Y, T1_H = 322, 62 +exam_type = chip("exam_type", 120, T1_Y, 218, T1_H, KIND_EXAM, "CT Chest WO contrast", + desc="exam type \u00b7 29252-4", icon_path="icons/healthicons-xray.svg", + filled_icon=True, text_w=150.9) +nodule_def = chip("nodule_def", 370, T1_Y, 216, T1_H, KIND_DEF, "pulmonary nodule", + desc="finding/diagnosis definition", icon_path="icons/lucide-tag.svg", + text_w=152.5) +effusion_def = chip("effusion_def", 618, T1_Y, 216, T1_H, KIND_DEF, "pleural effusion", + desc="finding/diagnosis definition", icon_path="icons/lucide-tag.svg", + text_w=152.5) +T1_B = T1_Y + T1_H + +# ================================================================ lower plane, tier 2 +T2_Y, T2_H = 448, 56 +family = chip("family", 130, T2_Y, 168, T2_H, KIND_EXAM, "CT Chest", desc="exam family \u00b7 preferred") +modality = chip("modality", 382, T2_Y, 80, T2_H, KIND_MOD, "CT", desc="modality") +T2_B = T2_Y + T2_H +T2_MID = T2_Y + T2_H / 2 + +path("r_member", [(200, T1_B), (200, T2_Y - 3)], REL, sw=2) +plate("l_member", 200, T1_B + 32, "member of family", 99.7, color=REL, fill=AMBER["band"]) +path("r_modality", [(298, T2_MID), (379, T2_MID)], REL, sw=2) +plate("l_modality", 340, T2_MID, "modality", 48.4, color=REL, fill=AMBER["band"]) +# "CT" is a hub: the family reaches it along tier 2, the nodule definition +# straight down from tier 1. +path("r_seen", [(422, T1_B), (422, T2_Y - 3)], REL, sw=2) +plate("l_seen", 422, T1_B + 32, "seen on", 46.3, color=REL, fill=AMBER["band"]) + +# ================================================================ lower plane, tier 3: anatomy +# thorax contains lung contains upper lobe; thorax also contains the pleural +# space, which sits at the far right under the effusion's definition. That +# second branch is drawn as a lane BELOW the tier rather than a long horizontal +# through it, which would have to cross lung and upper lobe. +T3_Y, T3_H = 568, 62 +thorax = chip("thorax", 130, T3_Y, 130, T3_H, KIND_ANAT, "thorax", desc="anatomic location") +lung = chip("lung", 342, T3_Y, 110, T3_H, KIND_ANAT, "lung", + icon_path="icons/healthicons-lungs.svg", filled_icon=True, text_w=28.4) +upper_lobe = chip("upper_lobe", 554, T3_Y, 185, T3_H, KIND_ANAT, "upper lobe of right lung") +pleural_space = chip("pleural_space", 760, T3_Y, 120, T3_H, KIND_ANAT, "pleural space") +T3_B = T3_Y + T3_H +T3_MID = T3_Y + T3_H / 2 + +path("r_contains1", [(260, T3_MID), (339, T3_MID)], REL, sw=2) +plate("l_contains1", 301, T3_MID, "contains", 48.4, color=REL, fill=AMBER["band"]) +path("r_contains2", [(452, T3_MID), (551, T3_MID)], REL, sw=2) +plate("l_contains2", 503, T3_MID, "contains", 48.4, color=REL, fill=AMBER["band"]) +LANE_Y = T3_B + 26 +path("r_contains3", [(215, T3_B), (215, LANE_Y), (820, LANE_Y), (820, T3_B + 3)], REL, sw=2) +plate("l_contains3", 500, LANE_Y, "contains", 48.4, color=REL, fill=AMBER["band"]) + +# The family covers the whole region; each definition occurs at its own +# structure. "occurs at" for the nodule elbows around the modality node rather +# than cutting the corner off it. +path("r_covers", [(200, T2_B), (200, T3_Y - 3)], REL, sw=2) +plate("l_covers", 200, T2_B + 32, "covers", 38.3, color=REL, fill=AMBER["band"]) +path("r_occurs1", [(560, T1_B), (560, 550), (440, 550), (440, T3_Y - 3)], REL, sw=2) +plate("l_occurs1", 552, T1_B + 32, "occurs at", 52.7, color=REL, fill=AMBER["band"]) +path("r_occurs2", [(790, T1_B), (790, T3_Y - 3)], REL, sw=2) +plate("l_occurs2", 790, T1_B + 32, "occurs at", 52.7, color=REL, fill=AMBER["band"]) + +# ================================================================ the knowledge continues +# The left cluster sits BELOW tier 1, where the plane's slanted left edge has +# moved far enough left to give a dot clearance; higher up it would straddle +# the border. +for gid, gx, gy, gr in [("g1", 96, 412, 11), ("g2", 62, 448, 9), ("g3", 92, 486, 10), + ("g4", 60, 528, 8), ("g5", 676, 452, 12), ("g6", 712, 492, 9)]: + dot(gid, gx, gy, gr) +faint(96, 412, 62, 448) +faint(96, 412, 92, 486) +faint(92, 486, 60, 528) +faint(104, 404, 124, 388) +faint(66, 535, 130, 590) +faint(676, 452, 712, 492) +faint(676, 440, 676, T1_B) + +# ================================================================ cross-plane attachments +# Five thick rose edges. Their x-order is the same where they leave the upper +# plane (300 / 490 / 602 / 720 / 866) and where they land (300 / 490 / 602 / +# 720 / 856), so none crosses another. Four are plumb; the fifth leans 10px to +# clear the effusion definition's right edge. The two that reach tier 3 thread +# the gaps between tier 1's boxes: x=602 runs down the 32px gap between the two +# definitions, which is why those two boxes cannot be moved together. +CX_LABEL_Y = 212 +path("x_exam", [(300, ROW1_B), (300, T1_Y - 3)], CROSS, sw=3, head=HEAD_R) +plate("x_exam_l", 300, CX_LABEL_Y, "is a", 20.2, color=ROSE["text"]) +path("x_nodule_isa", [(490, ROW1_B), (490, T1_Y - 3)], CROSS, sw=3, head=HEAD_R) +plate("x_nodule_isa_l", 490, CX_LABEL_Y, "is a", 20.2, color=ROSE["text"]) +path("x_nodule_loc", [(602, ROW1_B), (602, T3_Y - 3)], CROSS, sw=3, head=HEAD_R) +plate("x_nodule_loc_l", 602, CX_LABEL_Y, "located at", 56.4, color=ROSE["text"]) +path("x_effusion_isa", [(720, ROW1_B), (720, T1_Y - 3)], CROSS, sw=3, head=HEAD_R) +plate("x_effusion_isa_l", 720, CX_LABEL_Y, "is a", 20.2, color=ROSE["text"]) +path("x_effusion_loc", [(866, ROW1_B), (856, T3_Y - 3)], CROSS, sw=3, head=HEAD_R) +plate("x_effusion_loc_l", 864, CX_LABEL_Y, "located at", 56.4, color=ROSE["text"]) + +# ================================================================ legend +# One line of swatches under the plane, naming the four node kinds the fills +# stand for. Measured label widths, so the row centres exactly on the canvas. +LEG_ITEMS = [(KIND_DEF, "finding/diagnosis definition", 152.5), + (KIND_ANAT, "anatomic location", 101.2), + (KIND_EXAM, "exam type", 60.0), + (KIND_MOD, "modality", 48.4)] +SW_W, SW_H, SW_GAP, ITEM_GAP = 18, 13, 8, 28 +leg_w = sum(SW_W + SW_GAP + w for _, _, w in LEG_ITEMS) + ITEM_GAP * (len(LEG_ITEMS) - 1) +leg_x = (PW + SKEW - leg_w) / 2 +LEG_Y = P2_Y + P2_H + 22 +for _pal, _label, _w in LEG_ITEMS: + _r = base("rectangle", uid("leg"), leg_x, LEG_Y + 1.6, SW_W, SW_H, + _pal["stroke"], _pal["block"], sw=1) + _r["roundness"] = {"type": 3} + els.append(_r) + els.append(text(uid("legt"), leg_x + SW_W + SW_GAP, LEG_Y, _label, size=13, color=CAPTION, w=_w)) + leg_x += SW_W + SW_GAP + _w + ITEM_GAP + +# ================================================================ caption +els.append(text("caption", 0, P2_Y + P2_H + 54, + "every fact about this patient is attached to the shared knowledge " + "of what such a thing is", + size=14, color=CAPTION, align="center", w=PW + SKEW)) + +save(figure("two-planes").excalidraw) diff --git a/tools/diagrams/excalib.py b/tools/diagrams/excalib.py new file mode 100644 index 0000000..fd4a12a --- /dev/null +++ b/tools/diagrams/excalib.py @@ -0,0 +1,253 @@ +"""Shared helpers for building Excalidraw diagrams as JSON (used by build_*.py).""" +from __future__ import annotations + +import base64 +import json +import os +import tomllib +from dataclasses import dataclass, field +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +FIGURES_TOML = Path(__file__).resolve().with_name("figures.toml") + +# Palette (tools/diagrams follows the excalidraw-diagram skill palette) +PRIMARY = ("#3b82f6", "#1e3a5f", "#ffffff") # data structures: fill, stroke, text +SECONDARY = ("#60a5fa", "#1e3a5f", "#1e3a5f") # applications +TERTIARY = ("#93c5fd", "#1e3a5f", "#1e3a5f") # semantic foundation +EXTERNAL = ("#fed7aa", "#c2410c", "#7c2d12") # allied external project +PLANNED = ("#ffffff", "#64748b", "#475569") # dashed, documented or planned only +TITLE, SUBTITLE, BODY, LINE = "#1e40af", "#3b82f6", "#64748b", "#64748b" + +# "Five pillars" palette -- one hue per pillar of the OIDM stack, shared by +# build_pillars.py and any diagram that needs to visually match it (e.g. +# build_three_axes.py, a detail view of the Foundation Context / AMBER +# pillar). Each dict: band fill (lightest), block fill (a pastel step +# deeper, for content sitting on the band), stroke (dark border/icon +# color), text (darkest, for bold titles), mid (a mid-tone for subtitles +# and descriptors, same as stroke here since these are already fairly dark). +# Matched lightness steps across hues; no hue repeats, no blue reused. +AMBER = {"band": "#fef3c7", "block": "#fde68a", "stroke": "#b45309", "text": "#78350f", "mid": "#b45309"} +TEAL = {"band": "#ccfbf1", "block": "#99f6e4", "stroke": "#0f766e", "text": "#134e4a", "mid": "#0f766e"} +VIOLET = {"band": "#ede9fe", "block": "#ddd6fe", "stroke": "#6d28d9", "text": "#4c1d95", "mid": "#6d28d9"} +ROSE = {"band": "#ffe4e6", "block": "#fecdd3", "stroke": "#be123c", "text": "#881337", "mid": "#be123c"} +GREEN = {"band": "#dcfce7", "block": "#bbf7d0", "stroke": "#15803d", "text": "#14532d", "mid": "#15803d"} +CAPTION = "#64748b" # neutral gray for connective captions that sit between/across pillars + +els: list[dict] = [] +files: dict = {} +_seed = 1000 + + +def reset() -> None: + global _seed + els.clear() + files.clear() + _seed = 1000 + + +def seed() -> int: + global _seed + _seed += 1 + return _seed + + +def base(kind: str, id_: str, x: float, y: float, w: float, h: float, stroke: str, fill: str, dashed=False, sw=2) -> dict: + return { + "type": kind, "id": id_, "x": x, "y": y, "width": w, "height": h, + "strokeColor": stroke, "backgroundColor": fill, "fillStyle": "solid", + "strokeWidth": sw, "strokeStyle": "dashed" if dashed else "solid", "roughness": 0, + "opacity": 100, "angle": 0, "seed": seed(), "version": 1, "versionNonce": seed(), + "isDeleted": False, "groupIds": [], "boundElements": [], "link": None, "locked": False, + } + + +def text(id_: str, x: float, y: float, s: str, size=16, color=BODY, align="left", w: float | None = None, container: str | None = None, font=2) -> dict: + lines = s.split("\n") + est_w = w if w is not None else max(len(l) for l in lines) * size * 0.58 + h = len(lines) * size * 1.25 + t = base("text", id_, x, y, est_w, h, color, "transparent", sw=1) + t.update({"text": s, "originalText": s, "fontSize": size, "fontFamily": font, "textAlign": align, + "verticalAlign": "middle" if container else "top", "containerId": container, "lineHeight": 1.25, + "boundElements": None}) + return t + + +def box(id_: str, x: float, y: float, w: float, h: float, label: str, colors, dashed=False, size=16) -> dict: + fill, stroke, tcolor = colors + r = base("rectangle", id_, x, y, w, h, stroke, fill, dashed=dashed) + r["roundness"] = {"type": 3} + t = text(id_ + "_t", x + 10, y + 10, label, size=size, color=tcolor, align="center", w=w - 20, container=id_) + t["height"] = h - 20 + r["boundElements"] = [{"id": t["id"], "type": "text"}] + els.extend([r, t]) + return r + + +def image(id_: str, x: float, y: float, w: float, h: float, svg_path: str, color: str | None = None) -> dict: + """Embed an SVG icon as an Excalidraw image element. + + svg_path: path to an SVG file, absolute or relative to this module's directory + (icons/ holds the ones this repo uses). + color: if given, replace every `currentColor` in the SVG source with this hex + string before embedding -- used to recolor a stroke- or fill-based icon set to a + band's dark palette color without touching the licensed original file on disk. + """ + path = svg_path if os.path.isabs(svg_path) else os.path.join(os.path.dirname(__file__), svg_path) + with open(path, "r") as fh: + svg = fh.read() + if color: + svg = svg.replace("currentColor", color) + b64 = base64.b64encode(svg.encode("utf-8")).decode("ascii") + file_id = f"img_{id_}" + files[file_id] = { + "mimeType": "image/svg+xml", + "id": file_id, + "dataURL": f"data:image/svg+xml;base64,{b64}", + "created": 1700000000000, + } + el = base("image", id_, x, y, w, h, "transparent", "transparent", sw=1) + el.update({"fileId": file_id, "status": "saved", "scale": [1, 1], "roundness": None, "crop": None}) + els.append(el) + return el + + +def find(id_: str) -> dict: + return next(e for e in els if e["id"] == id_) + + +def edge_point(b: dict, side: str, frac: float = 0.5) -> tuple[float, float]: + x, y, w, h = b["x"], b["y"], b["width"], b["height"] + return {"top": (x + w * frac, y), "bottom": (x + w * frac, y + h), + "left": (x, y + h * frac), "right": (x + w, y + h * frac)}[side] + + +def arrow(id_: str, src: str, s_side: str, dst: str, d_side: str, label: str | None = None, s_frac=0.5, d_frac=0.5, + color=LINE, dashed=False, label_dx=0.0, label_dy=-22.0, sw=2, both=False, elbow: str | None = None, + label_size=14, inset=0.0, head="arrow") -> None: + a, b = find(src), find(dst) + (x1, y1), (x2, y2) = edge_point(a, s_side, s_frac), edge_point(b, d_side, d_frac) + if inset: + dx, dy = x2 - x1, y2 - y1 + length = (dx ** 2 + dy ** 2) ** 0.5 + if length > 2 * inset: + ux, uy = dx / length, dy / length + x1, y1 = x1 + ux * inset, y1 + uy * inset + x2, y2 = x2 - ux * inset, y2 - uy * inset + ar = base("arrow", id_, x1, y1, x2 - x1, y2 - y1, color, "transparent", dashed=dashed, sw=sw) + pts = [[0, 0], [x2 - x1, y2 - y1]] + if elbow == "vertical-first": + pts = [[0, 0], [0, y2 - y1], [x2 - x1, y2 - y1]] + elif elbow == "horizontal-first": + pts = [[0, 0], [x2 - x1, 0], [x2 - x1, y2 - y1]] + ar.update({"points": pts, + "startBinding": {"elementId": src, "focus": 0, "gap": 2}, + "endBinding": {"elementId": dst, "focus": 0, "gap": 2}, + "startArrowhead": head if both else None, "endArrowhead": head, "boundElements": None}) + a["boundElements"].append({"id": id_, "type": "arrow"}) + b["boundElements"].append({"id": id_, "type": "arrow"}) + els.append(ar) + if label: + mx, my = (x1 + x2) / 2 + label_dx, (y1 + y2) / 2 + label_dy + lw = max(len(ln) for ln in label.split("\n")) * label_size * 0.58 + els.append(text(id_ + "_l", mx - lw / 2, my, label, size=label_size, color=BODY, align="center", w=lw)) + + +def tri_head(id_: str, tip: tuple[float, float], frm: tuple[float, float], color: str, + size: float = 11.0, width_ratio: float = 0.62) -> dict: + """A solid filled arrowhead, drawn as its own closed triangle. + + Excalidraw's built-in arrowheads ("arrow", "triangle", ...) are sized from + the arrow's `strokeWidth`, so a thick line is forced to carry a big head. + This draws the head separately instead: give the arrow `endArrowhead: None` + and call this at its last point, passing the previous point as `frm` so the + triangle aims along the final segment. `size` is the head's length in px, + independent of the line weight. + """ + dx, dy = tip[0] - frm[0], tip[1] - frm[1] + length = (dx ** 2 + dy ** 2) ** 0.5 or 1.0 + ux, uy = dx / length, dy / length + px, py = -uy, ux # unit perpendicular + hw = size * width_ratio # half-width of the head's base + bx, by = tip[0] - ux * size, tip[1] - uy * size + pts = [tip, (bx + px * hw, by + py * hw), (bx - px * hw, by - py * hw)] + x0, y0 = pts[0] + el = base("line", id_, x0, y0, 0, 0, color, color, sw=1) + el.update({"points": [[x - x0, y - y0] for x, y in pts] + [[0, 0]], + "roundness": None, "boundElements": None, "fillStyle": "solid"}) + els.append(el) + return el + + +def boxed_label(id_: str, cx: float, y_bottom: float, s: str, size=13, color=BODY, pad_x=5, pad_y=2) -> dict: + """A small text label on an opaque white box -- for a caption that would + otherwise sit on top of a line or arrow it crosses. cx: horizontal + center. y_bottom: the y the label's bottom edge should align to, so a + caller can anchor it a fixed clearance above (or below, with a negative + clearance folded into y_bottom) some other element.""" + lines = s.split("\n") + w = max(len(ln) for ln in lines) * size * 0.58 + 2 * pad_x + h = len(lines) * size * 1.25 + 2 * pad_y + y = y_bottom - h + x = cx - w / 2 + r = base("rectangle", id_, x, y, w, h, "transparent", "#ffffff", sw=0) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(id_ + "_t", x + pad_x, y + pad_y, s, size=size, color=color, align="center", w=w - 2 * pad_x)) + return r + + +def hline(id_: str, x: float, y: float, w: float, dashed=True) -> None: + ln = base("line", id_, x, y, w, 0, LINE, "transparent", dashed=dashed, sw=1) + ln.update({"points": [[0, 0], [w, 0]], "boundElements": None}) + els.append(ln) + + + + +@dataclass(frozen=True) +class Figure: + """A figure's configured paths (absolute), from figures.toml.""" + name: str + builder: Path + excalidraw: Path + svg: Path | None + png: Path + args: list[str] = field(default_factory=list) + + +def load_figures() -> dict[str, Figure]: + """Every figure in figures.toml, keyed by name. Paths there are repo-relative.""" + with open(FIGURES_TOML, "rb") as fh: + cfg = tomllib.load(fh) + png_dir = REPO_ROOT / cfg["review_png_dir"] + figs = {} + for name, t in cfg.items(): + if not isinstance(t, dict): + continue # top-level settings + figs[name] = Figure(name=name, builder=REPO_ROOT / t["builder"], + excalidraw=REPO_ROOT / t["excalidraw"], + svg=REPO_ROOT / t["svg"] if "svg" in t else None, + png=png_dir / f"{name}.png", args=list(t.get("args", []))) + return figs + + +def figure(name: str) -> Figure: + """One figure's configured paths, by name (see figures.toml).""" + figs = load_figures() + if name not in figs: + raise KeyError(f"figure {name!r} is not in {FIGURES_TOML}") + return figs[name] + + +def save(path: str | os.PathLike) -> str: + """Write the document. `path` is absolute or relative to the repo root; + builders pass figure().excalidraw rather than a literal path.""" + out = os.path.normpath(os.path.join(REPO_ROOT, path)) + os.makedirs(os.path.dirname(out), exist_ok=True) + doc = {"type": "excalidraw", "version": 2, "source": "oidm-knowledge/tools/diagrams", + "elements": els, "appState": {"viewBackgroundColor": "#ffffff", "gridSize": None}, "files": files} + with open(out, "w") as fh: + json.dump(doc, fh, indent=1) + print("wrote", out, len(els), "elements") + return out diff --git a/tools/diagrams/excalidraw/foundation-network.excalidraw b/tools/diagrams/excalidraw/foundation-network.excalidraw new file mode 100644 index 0000000..58a769b --- /dev/null +++ b/tools/diagrams/excalidraw/foundation-network.excalidraw @@ -0,0 +1,6256 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "text", + "id": "maintitle", + "x": 0, + "y": 8, + "width": 394.4, + "height": 25.0, + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Foundation Context: a mini-network", + "originalText": "Foundation Context: a mini-network", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "subtitle", + "x": 0, + "y": 34, + "width": 550.42, + "height": 16.25, + "strokeColor": "#3b82f6", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "three sectors of shared, curated knowledge \u2014 illustrative, not exhaustive", + "originalText": "three sectors of shared, curated knowledge \u2014 illustrative, not exhaustive", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "rA", + "x": 0, + "y": 70, + "width": 935, + "height": 300, + "strokeColor": "#e2e8f0", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "rA_title", + "x": 14, + "y": 80, + "width": 365.4, + "height": 18.75, + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Sector 1 \u2014 Finding / diagnosis definitions", + "originalText": "Sector 1 \u2014 Finding / diagnosis definitions", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "rB", + "x": 0, + "y": 440, + "width": 935, + "height": 370, + "strokeColor": "#e2e8f0", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "rB_title", + "x": 54, + "y": 450, + "width": 252.29999999999998, + "height": 18.75, + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Sector 2 \u2014 Anatomic locations", + "originalText": "Sector 2 \u2014 Anatomic locations", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "rC", + "x": 0, + "y": 880, + "width": 935, + "height": 300, + "strokeColor": "#e2e8f0", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "rC_title", + "x": 14, + "y": 890, + "width": 182.7, + "height": 18.75, + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Sector 3 \u2014 Exam types", + "originalText": "Sector 3 \u2014 Exam types", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "rC_tag", + "x": 14, + "y": 910, + "width": 128.17999999999998, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "(content to come)", + "originalText": "(content to come)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "rL", + "x": 0, + "y": 1210, + "width": 935, + "height": 190, + "strokeColor": "#e2e8f0", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "rL_title", + "x": 14, + "y": 1220, + "width": 52.199999999999996, + "height": 18.75, + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Legend", + "originalText": "Legend", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lung_cancer", + "x": 20, + "y": 110, + "width": 140, + "height": 42, + "strokeColor": "#059669", + "backgroundColor": "#6ee7b7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "lung_cancer_t", + "type": "text" + }, + { + "id": "e_manifest", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lung_cancer_t", + "x": 30, + "y": 120, + "width": 120, + "height": 22, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lung cancer", + "originalText": "lung cancer", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "lung_cancer", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "pulm_nodule", + "x": 272, + "y": 100, + "width": 146, + "height": 54, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "pulm_nodule_t", + "type": "text" + }, + { + "id": "e_solid_sub", + "type": "arrow" + }, + { + "id": "e_partsolid_sub", + "type": "arrow" + }, + { + "id": "e_nonsolid_sub", + "type": "arrow" + }, + { + "id": "e_manifest", + "type": "arrow" + }, + { + "id": "e_assessed", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "pulm_nodule_t", + "x": 282, + "y": 110, + "width": 126, + "height": 34, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary\nnodule", + "originalText": "pulmonary\nnodule", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "pulm_nodule", + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "lungrads", + "x": 492, + "y": 95, + "width": 125, + "height": 75, + "strokeColor": "#7e22ce", + "backgroundColor": "#e9d5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "lungrads_t", + "type": "text" + }, + { + "id": "e_assessed", + "type": "arrow" + } + ], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "lungrads_t", + "x": 517.0, + "y": 119.0, + "width": 75.0, + "height": 27.0, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Lung-RADS", + "originalText": "Lung-RADS", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "lungrads", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "pleural_effusion", + "x": 637, + "y": 110, + "width": 142, + "height": 42, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "pleural_effusion_t", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "pleural_effusion_t", + "x": 647, + "y": 120, + "width": 122, + "height": 22, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pleural effusion", + "originalText": "pleural effusion", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "pleural_effusion", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "solid_nodule", + "x": 120, + "y": 210, + "width": 105, + "height": 70, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "solid_nodule_t", + "type": "text" + }, + { + "id": "e_solid_sub", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "solid_nodule_t", + "x": 130, + "y": 220, + "width": 85, + "height": 50, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "solid\npulmonary\nnodule", + "originalText": "solid\npulmonary\nnodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "solid_nodule", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "partsolid_nodule", + "x": 235, + "y": 210, + "width": 100, + "height": 70, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "partsolid_nodule_t", + "type": "text" + }, + { + "id": "e_partsolid_sub", + "type": "arrow" + }, + { + "id": "e_partsolid_comp", + "type": "arrow" + }, + { + "id": "e_progress", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "partsolid_nodule_t", + "x": 245, + "y": 220, + "width": 80, + "height": 50, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "part-solid\npulmonary\nnodule", + "originalText": "part-solid\npulmonary\nnodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "partsolid_nodule", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "nonsolid_nodule", + "x": 530, + "y": 210, + "width": 100, + "height": 70, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "nonsolid_nodule_t", + "type": "text" + }, + { + "id": "e_nonsolid_sub", + "type": "arrow" + }, + { + "id": "e_progress", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "nonsolid_nodule_t", + "x": 540, + "y": 220, + "width": 80, + "height": 50, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "non-solid\npulmonary\nnodule", + "originalText": "non-solid\npulmonary\nnodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "nonsolid_nodule", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "solid_component", + "x": 365, + "y": 295, + "width": 118, + "height": 38, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "solid_component_t", + "type": "text" + }, + { + "id": "e_partsolid_comp", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "solid_component_t", + "x": 375, + "y": 305, + "width": 98, + "height": 18, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "solid component", + "originalText": "solid component", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "solid_component", + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "gA1", + "x": 35, + "y": 220, + "width": 30, + "height": 30, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1055, + "version": 1, + "versionNonce": 1056, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "gA2", + "x": 62, + "y": 257, + "width": 26, + "height": 26, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1057, + "version": 1, + "versionNonce": 1058, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "gA3", + "x": 43, + "y": 283, + "width": 24, + "height": 24, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1059, + "version": 1, + "versionNonce": 1060, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "arrow", + "id": "fA_12", + "x": 50, + "y": 235, + "width": 25, + "height": 35, + "strokeColor": "#9ca3af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "angle": 0, + "seed": 1061, + "version": 1, + "versionNonce": 1062, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 25, + 35 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "arrow", + "id": "fA_13", + "x": 50, + "y": 235, + "width": 5, + "height": 60, + "strokeColor": "#9ca3af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "angle": 0, + "seed": 1063, + "version": 1, + "versionNonce": 1064, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 5, + 60 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "arrow", + "id": "fA_into", + "x": 75, + "y": 270, + "width": 45, + "height": -25, + "strokeColor": "#9ca3af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "angle": 0, + "seed": 1065, + "version": 1, + "versionNonce": 1066, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 45, + -25 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "arrow", + "id": "e_solid_sub", + "x": 172.5, + "y": 210, + "width": 121.39999999999998, + "height": -56, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1067, + "version": 1, + "versionNonce": 1068, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 121.39999999999998, + -56 + ] + ], + "startBinding": { + "elementId": "solid_nodule", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "pulm_nodule", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_solid_sub_h", + "x": 293.9, + "y": 154, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1069, + "version": 1, + "versionNonce": 1070, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -6.48348502073145, + 9.818575354192205 + ], + [ + -11.677453639093187, + -1.4412066148991016 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_partsolid_sub", + "x": 275.0, + "y": 210, + "width": 70.0, + "height": -56, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1071, + "version": 1, + "versionNonce": 1072, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 70.0, + -56 + ] + ], + "startBinding": { + "elementId": "partsolid_nodule", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "pulm_nodule", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_partsolid_sub_h", + "x": 345.0, + "y": 154, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1073, + "version": 1, + "versionNonce": 1074, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -3.935578799592861, + 11.088337094091031 + ], + [ + -11.681797389267729, + 1.4055638569974747 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_nonsolid_sub", + "x": 550.0, + "y": 210, + "width": -146.60000000000002, + "height": -56, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1075, + "version": 1, + "versionNonce": 1076, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -146.60000000000002, + -56 + ] + ], + "startBinding": { + "elementId": "nonsolid_nodule", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "pulm_nodule", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_nonsolid_sub_h", + "x": 403.4, + "y": 154, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1077, + "version": 1, + "versionNonce": 1078, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 11.554070614725333, + -2.223387557296462 + ], + [ + 7.129215863531158, + 9.360250059222551 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_solid_sub_bg", + "x": 170.0, + "y": 177.875, + "width": 70.0, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1079, + "version": 1, + "versionNonce": 1080, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "l_solid_sub_t", + "x": 175.0, + "y": 180.875, + "width": 60.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1081, + "version": 1, + "versionNonce": 1082, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "subtype of", + "originalText": "subtype of", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_partsolid_sub_bg", + "x": 255.0, + "y": 177.875, + "width": 70.0, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1083, + "version": 1, + "versionNonce": 1084, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "l_partsolid_sub_t", + "x": 260.0, + "y": 180.875, + "width": 60.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1085, + "version": 1, + "versionNonce": 1086, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "subtype of", + "originalText": "subtype of", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_nonsolid_sub_bg", + "x": 429.0, + "y": 157.875, + "width": 70.0, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1087, + "version": 1, + "versionNonce": 1088, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "l_nonsolid_sub_t", + "x": 434.0, + "y": 160.875, + "width": 60.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1089, + "version": 1, + "versionNonce": 1090, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "subtype of", + "originalText": "subtype of", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "e_partsolid_comp", + "x": 295.0, + "y": 280, + "width": 105.39999999999998, + "height": 15, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1091, + "version": 1, + "versionNonce": 1092, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 105.39999999999998, + 15 + ] + ], + "startBinding": { + "elementId": "partsolid_nodule", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "solid_component", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "text", + "id": "e_partsolid_comp_l", + "x": 358.69, + "y": 265.5, + "width": 98.02, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1093, + "version": 1, + "versionNonce": 1094, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "has component", + "originalText": "has component", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "e_partsolid_comp_h", + "x": 400.4, + "y": 295, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1095, + "version": 1, + "versionNonce": 1096, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -10.773796008606894, + 4.729198617622842 + ], + [ + -9.026693953157121, + -7.547105158670888 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_manifest", + "x": 160, + "y": 126.8, + "width": 112, + "height": -7.8999999999999915, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1097, + "version": 1, + "versionNonce": 1098, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 112, + -7.8999999999999915 + ] + ], + "startBinding": { + "elementId": "lung_cancer", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "pulm_nodule", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "text", + "id": "e_manifest_l", + "x": 159.45, + "y": 96.85, + "width": 113.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1099, + "version": 1, + "versionNonce": 1100, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "may manifest as", + "originalText": "may manifest as", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "e_manifest_h", + "x": 272, + "y": 118.9, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1101, + "version": 1, + "versionNonce": 1102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -9.538978435942965, + 6.888242910831082 + ], + [ + -10.411453578876774, + -5.481024938357194 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_progress", + "x": 530, + "y": 245.0, + "width": -195, + "height": 0.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1103, + "version": 1, + "versionNonce": 1104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -195, + 0.0 + ] + ], + "startBinding": { + "elementId": "nonsolid_nodule", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "partsolid_nodule", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "text", + "id": "e_progress_l", + "x": 375.95, + "y": 221.0, + "width": 113.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1105, + "version": 1, + "versionNonce": 1106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "may progress to", + "originalText": "may progress to", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "e_progress_h", + "x": 335, + "y": 245.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1107, + "version": 1, + "versionNonce": 1108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 10.0, + -6.199999999999989 + ], + [ + 10.0, + 6.199999999999989 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_assessed", + "x": 418, + "y": 113.5, + "width": 74, + "height": 19.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1109, + "version": 1, + "versionNonce": 1110, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 74, + 19.0 + ] + ], + "startBinding": { + "elementId": "pulm_nodule", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "lungrads", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "text", + "id": "e_assessed_l", + "x": 419.53, + "y": 93.0, + "width": 82.94, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1111, + "version": 1, + "versionNonce": 1112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "assessed by", + "originalText": "assessed by", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "e_assessed_h", + "x": 492, + "y": 132.5, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1113, + "version": 1, + "versionNonce": 1114, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -11.227710220076972, + 3.518312552059541 + ], + [ + -8.143951152869988, + -8.492117499167563 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "thorax", + "x": 160, + "y": 480, + "width": 110, + "height": 42, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1115, + "version": 1, + "versionNonce": 1116, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "thorax_t", + "type": "text" + }, + { + "id": "e_pspace_thorax", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "thorax_t", + "x": 170, + "y": 490, + "width": 90, + "height": 22, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1117, + "version": 1, + "versionNonce": 1118, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "thorax", + "originalText": "thorax", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "thorax", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "pleural_space", + "x": 654, + "y": 480, + "width": 108, + "height": 44, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1119, + "version": 1, + "versionNonce": 1120, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "pleural_space_t", + "type": "text" + }, + { + "id": "e_pspace_thorax", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "pleural_space_t", + "x": 664, + "y": 490, + "width": 88, + "height": 24, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1121, + "version": 1, + "versionNonce": 1122, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pleural\nspace", + "originalText": "pleural\nspace", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "pleural_space", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "upper_abdomen", + "x": 777, + "y": 480, + "width": 115, + "height": 42, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1123, + "version": 1, + "versionNonce": 1124, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "upper_abdomen_t", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "upper_abdomen_t", + "x": 787, + "y": 490, + "width": 95, + "height": 22, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1125, + "version": 1, + "versionNonce": 1126, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "upper abdomen", + "originalText": "upper abdomen", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "upper_abdomen", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "right_lung", + "x": 150, + "y": 550, + "width": 105, + "height": 40, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1127, + "version": 1, + "versionNonce": 1128, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "right_lung_t", + "type": "text" + }, + { + "id": "e_rlung_lung", + "type": "arrow" + }, + { + "id": "e_ulobe_rlung", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "right_lung_t", + "x": 160, + "y": 560, + "width": 85, + "height": 20, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1129, + "version": 1, + "versionNonce": 1130, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "right lung", + "originalText": "right lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "right_lung", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lung", + "x": 280, + "y": 550, + "width": 130, + "height": 42, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1131, + "version": 1, + "versionNonce": 1132, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "lung_t", + "type": "text" + }, + { + "id": "e_rlung_lung", + "type": "arrow" + }, + { + "id": "e_llung_lung", + "type": "arrow" + }, + { + "id": "e_parenchyma_lung", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lung_t", + "x": 290, + "y": 560, + "width": 110, + "height": 22, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1133, + "version": 1, + "versionNonce": 1134, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lung", + "originalText": "lung", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "lung", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "left_lung", + "x": 435, + "y": 550, + "width": 105, + "height": 40, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1135, + "version": 1, + "versionNonce": 1136, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "left_lung_t", + "type": "text" + }, + { + "id": "e_llung_lung", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "left_lung_t", + "x": 445, + "y": 560, + "width": 85, + "height": 20, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1137, + "version": 1, + "versionNonce": 1138, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "left lung", + "originalText": "left lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "left_lung", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "upper_lobe", + "x": 140, + "y": 660, + "width": 138, + "height": 42, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1139, + "version": 1, + "versionNonce": 1140, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "upper_lobe_t", + "type": "text" + }, + { + "id": "e_ulobe_rlung", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "upper_lobe_t", + "x": 150, + "y": 670, + "width": 118, + "height": 22, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1141, + "version": 1, + "versionNonce": 1142, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "upper lobe of\nright lung", + "originalText": "upper lobe of\nright lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "upper_lobe", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lung_parenchyma", + "x": 310, + "y": 660, + "width": 120, + "height": 44, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1143, + "version": 1, + "versionNonce": 1144, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "lung_parenchyma_t", + "type": "text" + }, + { + "id": "e_parenchyma_lung", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lung_parenchyma_t", + "x": 320, + "y": 670, + "width": 100, + "height": 24, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1145, + "version": 1, + "versionNonce": 1146, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lung\nparenchyma", + "originalText": "lung\nparenchyma", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "lung_parenchyma", + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "gB1", + "x": 35, + "y": 675, + "width": 30, + "height": 30, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1147, + "version": 1, + "versionNonce": 1148, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "gB2", + "x": 62, + "y": 712, + "width": 26, + "height": 26, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1149, + "version": 1, + "versionNonce": 1150, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "gB3", + "x": 43, + "y": 738, + "width": 24, + "height": 24, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1151, + "version": 1, + "versionNonce": 1152, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "arrow", + "id": "fB_12", + "x": 50, + "y": 690, + "width": 25, + "height": 35, + "strokeColor": "#9ca3af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "angle": 0, + "seed": 1153, + "version": 1, + "versionNonce": 1154, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 25, + 35 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "arrow", + "id": "fB_13", + "x": 50, + "y": 690, + "width": 5, + "height": 60, + "strokeColor": "#9ca3af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "angle": 0, + "seed": 1155, + "version": 1, + "versionNonce": 1156, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 5, + 60 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "arrow", + "id": "fB_into", + "x": 75, + "y": 725, + "width": 65, + "height": -40, + "strokeColor": "#9ca3af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "angle": 0, + "seed": 1157, + "version": 1, + "versionNonce": 1158, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 65, + -40 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "arrow", + "id": "e_rlung_lung", + "x": 255, + "y": 570.0, + "width": 25, + "height": -7.399999999999977, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1159, + "version": 1, + "versionNonce": 1160, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 25, + -7.399999999999977 + ] + ], + "startBinding": { + "elementId": "right_lung", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "lung", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_rlung_lung_h", + "x": 280, + "y": 562.6, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1161, + "version": 1, + "versionNonce": 1162, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -7.82902639099558, + 8.783299253076507 + ], + [ + -11.34848289519772, + -3.1067565043632612 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_llung_lung", + "x": 435, + "y": 570.0, + "width": -25, + "height": 9.399999999999977, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1163, + "version": 1, + "versionNonce": 1164, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -25, + 9.399999999999977 + ] + ], + "startBinding": { + "elementId": "left_lung", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "lung", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_llung_lung_h", + "x": 410, + "y": 579.4, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1165, + "version": 1, + "versionNonce": 1166, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 7.178158496674996, + -9.322770006634983 + ], + [ + 11.54226320339535, + 2.2838914474086778 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_ulobe_rlung", + "x": 195.2, + "y": 660, + "width": -3.1999999999999886, + "height": -70, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1167, + "version": 1, + "versionNonce": 1168, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -3.1999999999999886, + -70 + ] + ], + "startBinding": { + "elementId": "upper_lobe", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "right_lung", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_ulobe_rlung_h", + "x": 192.0, + "y": 590, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1169, + "version": 1, + "versionNonce": 1170, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 6.650197705725532, + 9.706434488253763 + ], + [ + -5.736865831977809, + 10.272700249977333 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_parenchyma_lung", + "x": 394.0, + "y": 660, + "width": 0.39999999999997726, + "height": -68, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1171, + "version": 1, + "versionNonce": 1172, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.39999999999997726, + -68 + ] + ], + "startBinding": { + "elementId": "lung_parenchyma", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "lung", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_parenchyma_lung_h", + "x": 394.4, + "y": 592, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1173, + "version": 1, + "versionNonce": 1174, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 6.141070224617636, + 10.036296951381814 + ], + [ + -6.258715248077749, + 9.963357036836555 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_pspace_thorax", + "x": 654, + "y": 502.0, + "width": -384, + "height": -1.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1175, + "version": 1, + "versionNonce": 1176, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -384, + -1.0 + ] + ], + "startBinding": { + "elementId": "pleural_space", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "thorax", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_pspace_thorax_h", + "x": 270, + "y": 501.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1177, + "version": 1, + "versionNonce": 1178, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 10.016111870337966, + -6.173937398522469 + ], + [ + 9.983820313166632, + 6.226020555250386 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_ulobe_rlung_bg", + "x": 155.15, + "y": 600.875, + "width": 83.7, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1179, + "version": 1, + "versionNonce": 1180, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "l_ulobe_rlung_t", + "x": 160.15, + "y": 603.875, + "width": 73.7, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1181, + "version": 1, + "versionNonce": 1182, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "contained by", + "originalText": "contained by", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_laterality_bg", + "x": 315.8, + "y": 600.875, + "width": 58.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1183, + "version": 1, + "versionNonce": 1184, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "l_laterality_t", + "x": 320.8, + "y": 603.875, + "width": 48.4, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1185, + "version": 1, + "versionNonce": 1186, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "laterality", + "originalText": "laterality", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_parenchyma_lung_bg", + "x": 463.55, + "y": 600.875, + "width": 46.9, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1187, + "version": 1, + "versionNonce": 1188, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "l_parenchyma_lung_t", + "x": 468.55, + "y": 603.875, + "width": 36.9, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1189, + "version": 1, + "versionNonce": 1190, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "part of", + "originalText": "part of", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_pspace_thorax_bg", + "x": 438.15, + "y": 483.875, + "width": 83.7, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1191, + "version": 1, + "versionNonce": 1192, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "l_pspace_thorax_t", + "x": 443.15, + "y": 486.875, + "width": 73.7, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1193, + "version": 1, + "versionNonce": 1194, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "contained by", + "originalText": "contained by", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ct_chest", + "x": 285, + "y": 920, + "width": 230, + "height": 60, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1195, + "version": 1, + "versionNonce": 1196, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ct_chest_t", + "type": "text" + }, + { + "id": "e_wo_family", + "type": "arrow" + }, + { + "id": "e_w_family", + "type": "arrow" + }, + { + "id": "e_wo_w_family", + "type": "arrow" + }, + { + "id": "e_cta_family", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ct_chest_t", + "x": 295, + "y": 930, + "width": 210, + "height": 40, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1197, + "version": 1, + "versionNonce": 1198, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest\npreferred (to come)", + "originalText": "CT Chest\npreferred (to come)", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ct_chest", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ct_wo", + "x": 90, + "y": 1010, + "width": 152, + "height": 70, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1199, + "version": 1, + "versionNonce": 1200, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ct_wo_t", + "type": "text" + }, + { + "id": "e_wo_family", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ct_wo_t", + "x": 100, + "y": 1020, + "width": 132, + "height": 50, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1201, + "version": 1, + "versionNonce": 1202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest WO contrast\n29252-4", + "originalText": "CT Chest WO contrast\n29252-4", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ct_wo", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ct_w", + "x": 250, + "y": 1010, + "width": 158, + "height": 70, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1203, + "version": 1, + "versionNonce": 1204, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ct_w_t", + "type": "text" + }, + { + "id": "e_w_family", + "type": "arrow" + }, + { + "id": "e_w_regionimaged", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ct_w_t", + "x": 260, + "y": 1020, + "width": 138, + "height": 50, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1205, + "version": 1, + "versionNonce": 1206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest W contrast IV\n24628-0", + "originalText": "CT Chest W contrast IV\n24628-0", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ct_w", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ct_wo_w", + "x": 416, + "y": 1010, + "width": 209, + "height": 70, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1207, + "version": 1, + "versionNonce": 1208, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ct_wo_w_t", + "type": "text" + }, + { + "id": "e_wo_w_family", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ct_wo_w_t", + "x": 426, + "y": 1020, + "width": 189, + "height": 50, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1209, + "version": 1, + "versionNonce": 1210, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest WO and W contrast IV\n30598-7", + "originalText": "CT Chest WO and W contrast IV\n30598-7", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ct_wo_w", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ct_cta", + "x": 633, + "y": 1010, + "width": 151, + "height": 70, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1211, + "version": 1, + "versionNonce": 1212, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ct_cta_t", + "type": "text" + }, + { + "id": "e_cta_family", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ct_cta_t", + "x": 643, + "y": 1020, + "width": 131, + "height": 50, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1213, + "version": 1, + "versionNonce": 1214, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CTA Chest vessels\nWO and W contrast IV\n30804-9", + "originalText": "CTA Chest vessels\nWO and W contrast IV\n30804-9", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ct_cta", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ct_thoracic_spine", + "x": 792, + "y": 1010, + "width": 126, + "height": 70, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1215, + "version": 1, + "versionNonce": 1216, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "ct_thoracic_spine_t", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ct_thoracic_spine_t", + "x": 802, + "y": 1020, + "width": 106, + "height": 50, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1217, + "version": 1, + "versionNonce": 1218, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Thoracic spine\nW contrast IV\n24979-7", + "originalText": "CT Thoracic spine\nW contrast IV\n24979-7", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ct_thoracic_spine", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "region_imaged", + "x": 266, + "y": 1100, + "width": 126, + "height": 40, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1219, + "version": 1, + "versionNonce": 1220, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "region_imaged_t", + "type": "text" + }, + { + "id": "e_w_regionimaged", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "region_imaged_t", + "x": 276, + "y": 1110, + "width": 106, + "height": 20, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1221, + "version": 1, + "versionNonce": 1222, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Region imaged:\nThorax (RID1243)", + "originalText": "Region imaged:\nThorax (RID1243)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "region_imaged", + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "gC1", + "x": 28, + "y": 1031, + "width": 28, + "height": 28, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1223, + "version": 1, + "versionNonce": 1224, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "gC2", + "x": 52, + "y": 1066, + "width": 24, + "height": 24, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1225, + "version": 1, + "versionNonce": 1226, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "arrow", + "id": "fC_12", + "x": 42, + "y": 1045, + "width": 22, + "height": 33, + "strokeColor": "#9ca3af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "angle": 0, + "seed": 1227, + "version": 1, + "versionNonce": 1228, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 22, + 33 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "arrow", + "id": "fC_into", + "x": 42, + "y": 1045, + "width": 48, + "height": 0, + "strokeColor": "#9ca3af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "angle": 0, + "seed": 1229, + "version": 1, + "versionNonce": 1230, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 48, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "arrow", + "id": "e_wo_family", + "x": 166.0, + "y": 1010, + "width": 142.0, + "height": -30, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1231, + "version": 1, + "versionNonce": 1232, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 142.0, + -30 + ] + ], + "startBinding": { + "elementId": "ct_wo", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ct_chest", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "text", + "id": "e_wo_family_l", + "x": 161.68, + "y": 975.0, + "width": 120.63999999999999, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1233, + "version": 1, + "versionNonce": 1234, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "member of family", + "originalText": "member of family", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "e_wo_family_h", + "x": 308.0, + "y": 980, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1235, + "version": 1, + "versionNonce": 1236, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -8.502462724935015, + 8.133149919378639 + ], + [ + -11.06560383812456, + -3.999051349718229 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_w_family", + "x": 329.0, + "y": 1010, + "width": 36.5, + "height": -30, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1237, + "version": 1, + "versionNonce": 1238, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 36.5, + -30 + ] + ], + "startBinding": { + "elementId": "ct_w", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ct_chest", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_w_family_h", + "x": 365.5, + "y": 980, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1239, + "version": 1, + "versionNonce": 1240, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -3.7886249515367467, + 11.13940397761894 + ], + [ + -11.662191889925964, + 1.559897535912114 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_wo_w_family", + "x": 520.5, + "y": 1010, + "width": -86.0, + "height": -30, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1241, + "version": 1, + "versionNonce": 1242, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -86.0, + -30 + ] + ], + "startBinding": { + "elementId": "ct_wo_w", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ct_chest", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_wo_w_family_h", + "x": 434.5, + "y": 980, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1243, + "version": 1, + "versionNonce": 1244, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 11.484109183631176, + -2.5603195617809433 + ], + [ + 7.399894445284303, + 9.147762688146713 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_cta_family", + "x": 678.3, + "y": 1010, + "width": -186.29999999999995, + "height": -30, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1245, + "version": 1, + "versionNonce": 1246, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -186.29999999999995, + -30 + ] + ], + "startBinding": { + "elementId": "ct_cta", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ct_chest", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_cta_family_h", + "x": 492.0, + "y": 980, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1247, + "version": 1, + "versionNonce": 1248, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 10.85850556438794, + -4.531319554848892 + ], + [ + 8.887122416534169, + 7.710969793322761 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_w_regionimaged", + "x": 329.0, + "y": 1080, + "width": 0.0, + "height": 20, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1249, + "version": 1, + "versionNonce": 1250, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 20 + ] + ], + "startBinding": { + "elementId": "ct_w", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "region_imaged", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "text", + "id": "e_w_regionimaged_l", + "x": 386.99, + "y": 1090.0, + "width": 98.02, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1251, + "version": 1, + "versionNonce": 1252, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Playbook part", + "originalText": "Playbook part", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "e_w_regionimaged_h", + "x": 329.0, + "y": 1100, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1253, + "version": 1, + "versionNonce": 1254, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -6.199999999999989, + -10.0 + ], + [ + 6.199999999999989, + -10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "cx_pn_lung", + "x": 345.0, + "y": 154, + "width": 0.0, + "height": 396, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1255, + "version": 1, + "versionNonce": 1256, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 0.0, + 396 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "cx_pn_lung_h", + "x": 345.0, + "y": 550, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1257, + "version": 1, + "versionNonce": 1258, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -8.060000000000002, + -13.0 + ], + [ + 8.060000000000002, + -13.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "cx_pn_lung_bg", + "x": 358.8, + "y": 393, + "width": 66.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1259, + "version": 1, + "versionNonce": 1260, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "cx_pn_lung_l", + "x": 363.8, + "y": 396, + "width": 56.4, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1261, + "version": 1, + "versionNonce": 1262, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "scoped to", + "originalText": "scoped to", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "cx_lc_lung", + "x": 90.0, + "y": 152, + "width": 209.5, + "height": 398, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1263, + "version": 1, + "versionNonce": 1264, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 0.0, + 253 + ], + [ + 210.0, + 253 + ], + [ + 209.5, + 398 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "cx_lc_lung_h", + "x": 299.5, + "y": 550, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1265, + "version": 1, + "versionNonce": 1266, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -8.0151247615878, + -13.02771564995885 + ], + [ + 8.104779400979169, + -12.972129773536267 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "cx_lc_lung_bg", + "x": 161.8, + "y": 390, + "width": 66.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1267, + "version": 1, + "versionNonce": 1268, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "cx_lc_lung_l", + "x": 166.8, + "y": 393, + "width": 56.4, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1269, + "version": 1, + "versionNonce": 1270, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "scoped to", + "originalText": "scoped to", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "cx_pe_pspace", + "x": 708.0, + "y": 152, + "width": 0.0, + "height": 328, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1271, + "version": 1, + "versionNonce": 1272, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 0.0, + 328 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "cx_pe_pspace_h", + "x": 708.0, + "y": 480, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1273, + "version": 1, + "versionNonce": 1274, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -8.059999999999945, + -13.0 + ], + [ + 8.059999999999945, + -13.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "cx_pe_pspace_bg", + "x": 714.8, + "y": 283, + "width": 66.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1275, + "version": 1, + "versionNonce": 1276, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "cx_pe_pspace_l", + "x": 719.8, + "y": 286, + "width": 56.4, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1277, + "version": 1, + "versionNonce": 1278, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "scoped to", + "originalText": "scoped to", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "cx_ctchest_thorax", + "x": 319.5, + "y": 920, + "width": -159.5, + "height": -419.0, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1279, + "version": 1, + "versionNonce": 1280, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 0.0, + -74 + ], + [ + -219.5, + -74 + ], + [ + -219.5, + -419 + ], + [ + -159.5, + -419.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "cx_ctchest_thorax_h", + "x": 160, + "y": 501.0, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1281, + "version": 1, + "versionNonce": 1282, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -13.0, + 8.060000000000002 + ], + [ + -13.0, + -8.060000000000002 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "cx_ctchest_thorax_bg", + "x": 41.85, + "y": 811, + "width": 48.3, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1283, + "version": 1, + "versionNonce": 1284, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "cx_ctchest_thorax_l", + "x": 46.85, + "y": 814, + "width": 38.3, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1285, + "version": 1, + "versionNonce": 1286, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "covers", + "originalText": "covers", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "cx_ctchest_lung", + "x": 354.0, + "y": 920, + "width": -59.69999999999999, + "height": -328, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1287, + "version": 1, + "versionNonce": 1288, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 0.0, + -52 + ], + [ + -236.0, + -52 + ], + [ + -236.0, + -190 + ], + [ + -60.0, + -190 + ], + [ + -59.69999999999999, + -328 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "cx_ctchest_lung_h", + "x": 294.3, + "y": 592, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1289, + "version": 1, + "versionNonce": 1290, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 8.031720151912396, + 13.017490979500053 + ], + [ + -8.08824175748532, + 12.982447584044849 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "cx_ctchest_lung_bg", + "x": 130.8, + "y": 811, + "width": 58.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1291, + "version": 1, + "versionNonce": 1292, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "cx_ctchest_lung_l", + "x": 135.8, + "y": 814, + "width": 48.4, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1293, + "version": 1, + "versionNonce": 1294, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "included", + "originalText": "included", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "cx_ctchest_abdomen", + "x": 480.5, + "y": 920, + "width": 354.0, + "height": -398, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1295, + "version": 1, + "versionNonce": 1296, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 0.0, + -74 + ], + [ + 354.0, + -74 + ], + [ + 354.0, + -398 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "cx_ctchest_abdomen_h", + "x": 834.5, + "y": 522, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1297, + "version": 1, + "versionNonce": 1298, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 8.059999999999945, + 13.0 + ], + [ + -8.059999999999945, + 13.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "cx_ctchest_abdomen_bg", + "x": 788.65, + "y": 680, + "width": 91.7, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1299, + "version": 1, + "versionNonce": 1300, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "cx_ctchest_abdomen_l", + "x": 793.65, + "y": 683, + "width": 81.7, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1301, + "version": 1, + "versionNonce": 1302, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "edge (usually)", + "originalText": "edge (usually)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "cx_pn_ctchest", + "x": 272, + "y": 154.0, + "width": 13, + "height": 796.0, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1303, + "version": 1, + "versionNonce": 1304, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 0, + 12.0 + ], + [ + -264, + 12.0 + ], + [ + -264, + 796.0 + ], + [ + 13, + 796.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "cx_pn_ctchest_h", + "x": 285, + "y": 950.0, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1305, + "version": 1, + "versionNonce": 1306, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -13.0, + 8.059999999999945 + ], + [ + -13.0, + -8.059999999999945 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "cx_pn_ctchest_bg", + "x": 19.85, + "y": 589, + "width": 56.3, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1307, + "version": 1, + "versionNonce": 1308, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "cx_pn_ctchest_l", + "x": 24.85, + "y": 592, + "width": 46.3, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1309, + "version": 1, + "versionNonce": 1310, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "seen on", + "originalText": "seen on", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "cx_pn_ctspine", + "x": 418, + "y": 154.0, + "width": 437.0, + "height": 856.0, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1311, + "version": 1, + "versionNonce": 1312, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 0, + 36.0 + ], + [ + 494, + 36.0 + ], + [ + 494, + 796.0 + ], + [ + 437, + 796.0 + ], + [ + 437.0, + 856.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "cx_pn_ctspine_h", + "x": 855.0, + "y": 1010, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1313, + "version": 1, + "versionNonce": 1314, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -8.059999999999945, + -13.0 + ], + [ + 8.059999999999945, + -13.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "cx_pn_ctspine_bg", + "x": 818.6, + "y": 389, + "width": 106.8, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1315, + "version": 1, + "versionNonce": 1316, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "cx_pn_ctspine_l", + "x": 823.6, + "y": 392, + "width": 96.8, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1317, + "version": 1, + "versionNonce": 1318, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "possibly seen on", + "originalText": "possibly seen on", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "legA0", + "x": 24, + "y": 1244, + "width": 22, + "height": 16, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1319, + "version": 1, + "versionNonce": 1320, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "legA0_l", + "x": 56, + "y": 1243, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1321, + "version": 1, + "versionNonce": 1322, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "finding", + "originalText": "finding", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "legA1", + "x": 24, + "y": 1272, + "width": 22, + "height": 16, + "strokeColor": "#059669", + "backgroundColor": "#6ee7b7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1323, + "version": 1, + "versionNonce": 1324, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "legA1_l", + "x": 56, + "y": 1271, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1325, + "version": 1, + "versionNonce": 1326, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "diagnosis", + "originalText": "diagnosis", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "legA2", + "x": 24, + "y": 1299, + "width": 24, + "height": 18, + "strokeColor": "#7e22ce", + "backgroundColor": "#e9d5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1327, + "version": 1, + "versionNonce": 1328, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "legA2_l", + "x": 56, + "y": 1299, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1329, + "version": 1, + "versionNonce": 1330, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "assessment scheme", + "originalText": "assessment scheme", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "legA3", + "x": 24, + "y": 1328, + "width": 22, + "height": 16, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1331, + "version": 1, + "versionNonce": 1332, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "legA3_l", + "x": 56, + "y": 1327, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1333, + "version": 1, + "versionNonce": 1334, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "anatomic location", + "originalText": "anatomic location", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "legA4", + "x": 24, + "y": 1356, + "width": 22, + "height": 16, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1335, + "version": 1, + "versionNonce": 1336, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "legA4_l", + "x": 56, + "y": 1355, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1337, + "version": 1, + "versionNonce": 1338, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "exam type", + "originalText": "exam type", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "legB0", + "x": 479, + "y": 1243, + "width": 18, + "height": 18, + "strokeColor": "#9ca3af", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1339, + "version": 1, + "versionNonce": 1340, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "legB0_l", + "x": 517, + "y": 1243, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1341, + "version": 1, + "versionNonce": 1342, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "unlabeled grey = further nodes not shown", + "originalText": "unlabeled grey = further nodes not shown", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "legB1", + "x": 477, + "y": 1280, + "width": 30, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1343, + "version": 1, + "versionNonce": 1344, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 30, + 0 + ] + ] + }, + { + "type": "text", + "id": "legB1_l", + "x": 517, + "y": 1271, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1345, + "version": 1, + "versionNonce": 1346, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "solid edge = within-sector relationship", + "originalText": "solid edge = within-sector relationship", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "legB2", + "x": 477, + "y": 1308, + "width": 30, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1347, + "version": 1, + "versionNonce": 1348, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 30, + 0 + ] + ] + }, + { + "type": "text", + "id": "legB2_l", + "x": 517, + "y": 1299, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1349, + "version": 1, + "versionNonce": 1350, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "colored thick edge = cross-sector relationship", + "originalText": "colored thick edge = cross-sector relationship", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "legB3", + "x": 477, + "y": 1336, + "width": 30, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1351, + "version": 1, + "versionNonce": 1352, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 30, + 0 + ] + ] + }, + { + "type": "text", + "id": "legB3_l", + "x": 517, + "y": 1327, + "width": 340, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1353, + "version": 1, + "versionNonce": 1354, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "dashed = possible or planned", + "originalText": "dashed = possible or planned", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": {} +} \ No newline at end of file diff --git a/tools/diagrams/excalidraw/nodule-neighborhood.excalidraw b/tools/diagrams/excalidraw/nodule-neighborhood.excalidraw new file mode 100644 index 0000000..e1cbdf0 --- /dev/null +++ b/tools/diagrams/excalidraw/nodule-neighborhood.excalidraw @@ -0,0 +1,6015 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "rectangle", + "id": "fc", + "x": 400, + "y": 270, + "width": 240, + "height": 80, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_gr_fc", + "type": "arrow" + }, + { + "id": "e_mma_gn", + "type": "arrow" + }, + { + "id": "e_mma_neo", + "type": "arrow" + }, + { + "id": "e_st1", + "type": "arrow" + }, + { + "id": "e_st2", + "type": "arrow" + }, + { + "id": "e_st3", + "type": "arrow" + }, + { + "id": "e_de0", + "type": "arrow" + }, + { + "id": "e_de1", + "type": "arrow" + }, + { + "id": "e_de2", + "type": "arrow" + }, + { + "id": "e_de3", + "type": "arrow" + }, + { + "id": "e_de4", + "type": "arrow" + }, + { + "id": "e_ms0", + "type": "arrow" + }, + { + "id": "e_ms1", + "type": "arrow" + }, + { + "id": "e_sc", + "type": "arrow" + }, + { + "id": "e_sp", + "type": "arrow" + }, + { + "id": "e_sn0", + "type": "arrow" + }, + { + "id": "e_sn1", + "type": "arrow" + }, + { + "id": "e_sn2", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "fc_n", + "x": 408, + "y": 291.0, + "width": 224, + "height": 21.25, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary nodule", + "originalText": "pulmonary nodule", + "fontSize": 17, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "fc_k", + "x": 408, + "y": 315.25, + "width": 224, + "height": 13.75, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "FindingClass", + "originalText": "FindingClass", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "gr", + "x": 320, + "y": 25, + "width": 250, + "height": 70, + "strokeColor": "#10b981", + "backgroundColor": "#ecfdf5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_gr_fc", + "type": "arrow" + }, + { + "id": "e_gr_gn", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "gr_n", + "x": 328, + "y": 42.875, + "width": 234, + "height": 17.5, + "strokeColor": "#065f46", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary parenchymal abnormality", + "originalText": "pulmonary parenchymal abnormality", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "gr_k", + "x": 328, + "y": 63.375, + "width": 234, + "height": 13.75, + "strokeColor": "#059669", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Grouping", + "originalText": "Grouping", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "dx_gran", + "x": 20, + "y": 30, + "width": 165, + "height": 62, + "strokeColor": "#059669", + "backgroundColor": "#6ee7b7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_gr_gn", + "type": "arrow" + }, + { + "id": "e_mma_gn", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "dx_gran_n", + "x": 28, + "y": 43.875, + "width": 149, + "height": 17.5, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary granuloma", + "originalText": "pulmonary granuloma", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "dx_gran_k", + "x": 28, + "y": 64.375, + "width": 149, + "height": 13.75, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Diagnosis", + "originalText": "Diagnosis", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "dx_neo", + "x": 20, + "y": 140, + "width": 165, + "height": 62, + "strokeColor": "#059669", + "backgroundColor": "#6ee7b7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_mma_neo", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "dx_neo_n", + "x": 28, + "y": 153.875, + "width": 149, + "height": 17.5, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary neoplasm", + "originalText": "pulmonary neoplasm", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "dx_neo_k", + "x": 28, + "y": 174.375, + "width": 149, + "height": 13.75, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Diagnosis", + "originalText": "Diagnosis", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "as_lr", + "x": 600, + "y": 14, + "width": 190, + "height": 92, + "strokeColor": "#7e22ce", + "backgroundColor": "#e9d5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "as_lr_n", + "x": 626.6, + "y": 42.875, + "width": 136.79999999999998, + "height": 17.5, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Lung-RADS", + "originalText": "Lung-RADS", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "as_lr_k", + "x": 626.6, + "y": 63.375, + "width": 136.79999999999998, + "height": 13.75, + "strokeColor": "#7e22ce", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "AssessmentScheme", + "originalText": "AssessmentScheme", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "as_fl", + "x": 845, + "y": 14, + "width": 190, + "height": 92, + "strokeColor": "#7e22ce", + "backgroundColor": "#e9d5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "as_fl_n", + "x": 871.6, + "y": 42.875, + "width": 136.79999999999998, + "height": 17.5, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Fleischner criteria", + "originalText": "Fleischner criteria", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "as_fl_k", + "x": 871.6, + "y": 63.375, + "width": 136.79999999999998, + "height": 13.75, + "strokeColor": "#7e22ce", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "AssessmentScheme", + "originalText": "AssessmentScheme", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "s1", + "x": 300, + "y": 500, + "width": 140, + "height": 80, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_st1", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "s1_n", + "x": 308, + "y": 515.375, + "width": 124, + "height": 32.5, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "solid\npulmonary nodule", + "originalText": "solid\npulmonary nodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "s1_k", + "x": 308, + "y": 550.875, + "width": 124, + "height": 13.75, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "FindingClass", + "originalText": "FindingClass", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "s2", + "x": 450, + "y": 500, + "width": 140, + "height": 80, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_st2", + "type": "arrow" + }, + { + "id": "e_hc", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "s2_n", + "x": 458, + "y": 515.375, + "width": 124, + "height": 32.5, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "part-solid\npulmonary nodule", + "originalText": "part-solid\npulmonary nodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "s2_k", + "x": 458, + "y": 550.875, + "width": 124, + "height": 13.75, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "FindingClass", + "originalText": "FindingClass", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "s3", + "x": 600, + "y": 500, + "width": 140, + "height": 80, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_st3", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "s3_n", + "x": 608, + "y": 515.375, + "width": 124, + "height": 32.5, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "ground glass\nnodule", + "originalText": "ground glass\nnodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "s3_k", + "x": 608, + "y": 550.875, + "width": 124, + "height": 13.75, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "FindingClass", + "originalText": "FindingClass", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "sc", + "x": 400, + "y": 630, + "width": 240, + "height": 70, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1055, + "version": 1, + "versionNonce": 1056, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_hc", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "sc_n", + "x": 408, + "y": 640.375, + "width": 224, + "height": 32.5, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1057, + "version": 1, + "versionNonce": 1058, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "solid component of\npart-solid pulmonary nodule", + "originalText": "solid component of\npart-solid pulmonary nodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "sc_k", + "x": 408, + "y": 675.875, + "width": 224, + "height": 13.75, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1059, + "version": 1, + "versionNonce": 1060, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "FindingClass", + "originalText": "FindingClass", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "an", + "x": 60, + "y": 290, + "width": 140, + "height": 60, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1061, + "version": 1, + "versionNonce": 1062, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_sc", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "an_n", + "x": 68, + "y": 302.875, + "width": 124, + "height": 17.5, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1063, + "version": 1, + "versionNonce": 1064, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lung", + "originalText": "lung", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "an_k", + "x": 68, + "y": 323.375, + "width": 124, + "height": 13.75, + "strokeColor": "#1d4ed8", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1065, + "version": 1, + "versionNonce": 1066, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "AnatomicLocation", + "originalText": "AnatomicLocation", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "sp", + "x": 40, + "y": 370, + "width": 170, + "height": 60, + "strokeColor": "#78748f", + "backgroundColor": "#e5e3ee", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1067, + "version": 1, + "versionNonce": 1068, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_sp", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "sp_n", + "x": 48, + "y": 382.875, + "width": 154, + "height": 17.5, + "strokeColor": "#3c3a52", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1069, + "version": 1, + "versionNonce": 1070, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "chest radiology", + "originalText": "chest radiology", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "sp_k", + "x": 48, + "y": 403.375, + "width": 154, + "height": 13.75, + "strokeColor": "#5d5a78", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1071, + "version": 1, + "versionNonce": 1072, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Subspecialty", + "originalText": "Subspecialty", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "md_ct", + "x": 55, + "y": 480, + "width": 90, + "height": 38, + "strokeColor": "#6b7280", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1073, + "version": 1, + "versionNonce": 1074, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_sn0", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "md_ct_n", + "x": 63, + "y": 483.75, + "width": 74, + "height": 16.25, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1075, + "version": 1, + "versionNonce": 1076, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT", + "originalText": "CT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "md_ct_k", + "x": 63, + "y": 503.0, + "width": 74, + "height": 11.25, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1077, + "version": 1, + "versionNonce": 1078, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Modality", + "originalText": "Modality", + "fontSize": 9, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "md_xr", + "x": 55, + "y": 528, + "width": 90, + "height": 38, + "strokeColor": "#6b7280", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1079, + "version": 1, + "versionNonce": 1080, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_sn1", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "md_xr_n", + "x": 63, + "y": 531.75, + "width": 74, + "height": 16.25, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1081, + "version": 1, + "versionNonce": 1082, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "XR", + "originalText": "XR", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "md_xr_k", + "x": 63, + "y": 551.0, + "width": 74, + "height": 11.25, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1083, + "version": 1, + "versionNonce": 1084, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Modality", + "originalText": "Modality", + "fontSize": 9, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "md_mr", + "x": 55, + "y": 576, + "width": 90, + "height": 38, + "strokeColor": "#6b7280", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1085, + "version": 1, + "versionNonce": 1086, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_sn2", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "md_mr_n", + "x": 63, + "y": 579.75, + "width": 74, + "height": 16.25, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1087, + "version": 1, + "versionNonce": 1088, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MR", + "originalText": "MR", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "md_mr_k", + "x": 63, + "y": 599.0, + "width": 74, + "height": 11.25, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1089, + "version": 1, + "versionNonce": 1090, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Modality", + "originalText": "Modality", + "fontSize": 9, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "de0", + "x": 845, + "y": 150, + "width": 190, + "height": 48, + "strokeColor": "#be123c", + "backgroundColor": "#ffe4e6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1091, + "version": 1, + "versionNonce": 1092, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_de0", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "de0_n", + "x": 853, + "y": 156.875, + "width": 174, + "height": 17.5, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1093, + "version": 1, + "versionNonce": 1094, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "presence", + "originalText": "presence", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "de0_k", + "x": 853, + "y": 177.375, + "width": 174, + "height": 13.75, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1095, + "version": 1, + "versionNonce": 1096, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "DataElement", + "originalText": "DataElement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "de1", + "x": 845, + "y": 216, + "width": 190, + "height": 48, + "strokeColor": "#be123c", + "backgroundColor": "#ffe4e6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1097, + "version": 1, + "versionNonce": 1098, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_de1", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "de1_n", + "x": 853, + "y": 222.875, + "width": 174, + "height": 17.5, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1099, + "version": 1, + "versionNonce": 1100, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "composition", + "originalText": "composition", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "de1_k", + "x": 853, + "y": 243.375, + "width": 174, + "height": 13.75, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1101, + "version": 1, + "versionNonce": 1102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "DataElement", + "originalText": "DataElement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "de2", + "x": 845, + "y": 282, + "width": 190, + "height": 48, + "strokeColor": "#be123c", + "backgroundColor": "#ffe4e6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1103, + "version": 1, + "versionNonce": 1104, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_de2", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "de2_n", + "x": 853, + "y": 288.875, + "width": 174, + "height": 17.5, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1105, + "version": 1, + "versionNonce": 1106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "margin", + "originalText": "margin", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "de2_k", + "x": 853, + "y": 309.375, + "width": 174, + "height": 13.75, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1107, + "version": 1, + "versionNonce": 1108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "DataElement", + "originalText": "DataElement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "de3", + "x": 845, + "y": 348, + "width": 190, + "height": 48, + "strokeColor": "#be123c", + "backgroundColor": "#ffe4e6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1109, + "version": 1, + "versionNonce": 1110, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_de3", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "de3_n", + "x": 853, + "y": 354.875, + "width": 174, + "height": 17.5, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1111, + "version": 1, + "versionNonce": 1112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "calcification", + "originalText": "calcification", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "de3_k", + "x": 853, + "y": 375.375, + "width": 174, + "height": 13.75, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1113, + "version": 1, + "versionNonce": 1114, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "DataElement", + "originalText": "DataElement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "de4", + "x": 845, + "y": 414, + "width": 190, + "height": 48, + "strokeColor": "#be123c", + "backgroundColor": "#ffe4e6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1115, + "version": 1, + "versionNonce": 1116, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_de4", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "de4_n", + "x": 853, + "y": 420.875, + "width": 174, + "height": 17.5, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1117, + "version": 1, + "versionNonce": 1118, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "interval change", + "originalText": "interval change", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "de4_k", + "x": 853, + "y": 441.375, + "width": 174, + "height": 13.75, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1119, + "version": 1, + "versionNonce": 1120, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "DataElement", + "originalText": "DataElement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ms0", + "x": 845, + "y": 510, + "width": 190, + "height": 48, + "strokeColor": "#6d28d9", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1121, + "version": 1, + "versionNonce": 1122, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_ms0", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ms0_n", + "x": 853, + "y": 516.875, + "width": 174, + "height": 17.5, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1123, + "version": 1, + "versionNonce": 1124, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "mean diameter", + "originalText": "mean diameter", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ms0_k", + "x": 853, + "y": 537.375, + "width": 174, + "height": 13.75, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1125, + "version": 1, + "versionNonce": 1126, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Measurement", + "originalText": "Measurement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "ms1", + "x": 845, + "y": 576, + "width": 190, + "height": 48, + "strokeColor": "#6d28d9", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1127, + "version": 1, + "versionNonce": 1128, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "e_ms1", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "ms1_n", + "x": 853, + "y": 582.875, + "width": 174, + "height": 17.5, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1129, + "version": 1, + "versionNonce": 1130, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "long-axis diameter", + "originalText": "long-axis diameter", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ms1_k", + "x": 853, + "y": 603.375, + "width": 174, + "height": 13.75, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1131, + "version": 1, + "versionNonce": 1132, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Measurement", + "originalText": "Measurement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "e_gr_fc", + "x": 520.0, + "y": 270, + "width": 0.0, + "height": -175, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1133, + "version": 1, + "versionNonce": 1134, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + -175 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "gr", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_gr_fc_h", + "x": 520.0, + "y": 95, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1135, + "version": 1, + "versionNonce": 1136, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 6.2000000000000455, + 10.0 + ], + [ + -6.2000000000000455, + 10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_gr_gn", + "x": 185, + "y": 61.0, + "width": 135, + "height": -1.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1137, + "version": 1, + "versionNonce": 1138, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 135, + -1.0 + ] + ], + "startBinding": { + "elementId": "dx_gran", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "gr", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_gr_gn_h", + "x": 320, + "y": 60.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1139, + "version": 1, + "versionNonce": 1140, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -9.953800996859798, + 6.2739019529247315 + ], + [ + -10.045650328874274, + -6.125757869030409 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_mma_gn", + "x": 185, + "y": 83.94, + "width": 215, + "height": 198.06, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1141, + "version": 1, + "versionNonce": 1142, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 215, + 198.06 + ] + ], + "startBinding": { + "elementId": "dx_gran", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_mma_gn_h", + "x": 400, + "y": 282.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1143, + "version": 1, + "versionNonce": 1144, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -11.555613006793124, + -2.215357315927804 + ], + [ + -3.1541406350992247, + -11.335404573900973 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_mma_neo", + "x": 185, + "y": 171.0, + "width": 215, + "height": 143.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1145, + "version": 1, + "versionNonce": 1146, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 215, + 143.0 + ] + ], + "startBinding": { + "elementId": "dx_neo", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_mma_neo_h", + "x": 400, + "y": 314.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1147, + "version": 1, + "versionNonce": 1148, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -11.76005444388511, + -0.37565872392042365 + ], + [ + -4.892858059805235, + -10.70046447620848 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_asb_lr", + "x": 548.8, + "y": 270, + "width": 112.71064285388809, + "height": -161.896272225147, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1149, + "version": 1, + "versionNonce": 1150, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 112.71064285388809, + -161.896272225147 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_asb_lr_h", + "x": 664.6531347457569, + "y": 103.58988853208655, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1151, + "version": 1, + "versionNonce": 1152, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + -0.6252937479156344, + 11.749425846772994 + ], + [ + -10.801949495243775, + 4.6645350360141435 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_asb_fl", + "x": 592.0, + "y": 270, + "width": 283.8362978722794, + "height": -171.28052457809966, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1153, + "version": 1, + "versionNonce": 1154, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 283.8362978722794, + -171.28052457809966 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_asb_fl_h", + "x": 880.5453300081402, + "y": 95.87781809853604, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1155, + "version": 1, + "versionNonce": 1156, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + -5.358553809772729, + 10.475013177450933 + ], + [ + -11.76519941153947, + -0.1417138197625718 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_st1", + "x": 370.0, + "y": 500, + "width": 114.0, + "height": -150, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1157, + "version": 1, + "versionNonce": 1158, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 114.0, + -150 + ] + ], + "startBinding": { + "elementId": "s1", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_st1_h", + "x": 484.0, + "y": 350, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1159, + "version": 1, + "versionNonce": 1160, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -1.114627071772361, + 11.713138199939124 + ], + [ + -10.987038278898808, + 4.210105682522965 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_st2", + "x": 520.0, + "y": 500, + "width": 0.0, + "height": -150, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1161, + "version": 1, + "versionNonce": 1162, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + -150 + ] + ], + "startBinding": { + "elementId": "s2", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_st2_h", + "x": 520.0, + "y": 350, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1163, + "version": 1, + "versionNonce": 1164, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 6.2000000000000455, + 10.0 + ], + [ + -6.2000000000000455, + 10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_st3", + "x": 670.0, + "y": 500, + "width": -97.20000000000005, + "height": -150, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1165, + "version": 1, + "versionNonce": 1166, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -97.20000000000005, + -150 + ] + ], + "startBinding": { + "elementId": "s3", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_st3_h", + "x": 572.8, + "y": 350, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1167, + "version": 1, + "versionNonce": 1168, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 10.641179331118224, + 5.020488267388146 + ], + [ + 0.23497872340010417, + 11.763706261189554 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_hc", + "x": 520.0, + "y": 580, + "width": 0.0, + "height": 50, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1169, + "version": 1, + "versionNonce": 1170, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 50 + ] + ], + "startBinding": { + "elementId": "s2", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "sc", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_hc_h", + "x": 520.0, + "y": 630, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1171, + "version": 1, + "versionNonce": 1172, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -6.2000000000000455, + -10.0 + ], + [ + 6.2000000000000455, + -10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_de0", + "x": 640, + "y": 278.0, + "width": 205, + "height": -104.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1173, + "version": 1, + "versionNonce": 1174, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 205, + -104.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "de0", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_de0_h", + "x": 845, + "y": 174.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1175, + "version": 1, + "versionNonce": 1176, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -6.112975105396686, + 10.053434008377423 + ], + [ + -11.723060997739253, + -1.0049083755669415 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_de1", + "x": 640, + "y": 290.0, + "width": 205, + "height": -50.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1177, + "version": 1, + "versionNonce": 1178, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 205, + -50.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "de1", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_de1_h", + "x": 845, + "y": 240.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1179, + "version": 1, + "versionNonce": 1180, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -8.246075070646953, + 8.392987902365462 + ], + [ + -11.184331705015552, + -3.6538642985453293 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_de2", + "x": 640, + "y": 302.0, + "width": 205, + "height": 4.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1181, + "version": 1, + "versionNonce": 1182, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 205, + 4.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "de2", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_de2_h", + "x": 845, + "y": 306.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1183, + "version": 1, + "versionNonce": 1184, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -10.119049501670133, + 6.003735269209528 + ], + [ + -9.877144327541146, + -6.393904904901433 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_de3", + "x": 640, + "y": 314.0, + "width": 205, + "height": 58.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1185, + "version": 1, + "versionNonce": 1186, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 205, + 58.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "de3", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_de3_h", + "x": 845, + "y": 372.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1187, + "version": 1, + "versionNonce": 1188, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -11.310183398971162, + 3.243416637072187 + ], + [ + -7.934401567737837, + -8.688226042287397 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_de4", + "x": 640, + "y": 326.0, + "width": 205, + "height": 112.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1189, + "version": 1, + "versionNonce": 1190, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 205, + 112.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "de4", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_de4_h", + "x": 845, + "y": 438.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1191, + "version": 1, + "versionNonce": 1192, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -11.748283365385305, + 0.6464038726763874 + ], + [ + -5.803080064901678, + -10.235441454101533 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_ms0", + "x": 640, + "y": 338.0, + "width": 205, + "height": 196.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1193, + "version": 1, + "versionNonce": 1194, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 205, + 196.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ms0", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_ms0_h", + "x": 845, + "y": 534.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1195, + "version": 1, + "versionNonce": 1196, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -11.512537487962504, + -2.429296315449619 + ], + [ + -2.9433622121006238, + -11.391954129488795 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_ms1", + "x": 640, + "y": 346.0, + "width": 205, + "height": 254.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1197, + "version": 1, + "versionNonce": 1198, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 205, + 254.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "ms1", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_ms1_h", + "x": 845, + "y": 600.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1199, + "version": 1, + "versionNonce": 1200, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -11.105181598440822, + -3.8877939330229765 + ], + [ + -1.4558547493872993, + -11.67563646867643 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_sc", + "x": 400, + "y": 340.0, + "width": -200, + "height": -20.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1201, + "version": 1, + "versionNonce": 1202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -200, + -20.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "an", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_sc_h", + "x": 200, + "y": 320.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1203, + "version": 1, + "versionNonce": 1204, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 10.567294960030097, + -5.174193389091954 + ], + [ + 9.333448844169709, + 7.164267769511923 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_sp", + "x": 412.0, + "y": 350, + "width": -202.0, + "height": 50.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1205, + "version": 1, + "versionNonce": 1206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -202.0, + 50.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "sp", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_sp_h", + "x": 210.0, + "y": 400.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1207, + "version": 1, + "versionNonce": 1208, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 8.217356019995549, + -8.421108005520637 + ], + [ + 11.196748261163549, + 3.615636648798045 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_sn0", + "x": 424.0, + "y": 350, + "width": -279.0, + "height": 149.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1209, + "version": 1, + "versionNonce": 1210, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -279.0, + 149.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "md_ct", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_sn0_h", + "x": 145.0, + "y": 499.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1211, + "version": 1, + "versionNonce": 1212, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 5.9002036567105165, + -10.179764084169165 + ], + [ + 11.741601296908925, + 0.7581549870749313 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_sn1", + "x": 443.2, + "y": 350, + "width": -298.2, + "height": 197.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1213, + "version": 1, + "versionNonce": 1214, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -298.2, + 197.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "md_xr", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_sn1_h", + "x": 145.0, + "y": 547.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1215, + "version": 1, + "versionNonce": 1216, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 4.926181777142375, + -10.68516415870863 + ], + [ + 11.761168057503227, + -0.3390072612282893 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "arrow", + "id": "e_sn2", + "x": 462.4, + "y": 350, + "width": -317.4, + "height": 245.0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1217, + "version": 1, + "versionNonce": 1218, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -317.4, + 245.0 + ] + ], + "startBinding": { + "elementId": "fc", + "focus": 0, + "gap": 2 + }, + "endBinding": { + "elementId": "md_mr", + "focus": 0, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_sn2_h", + "x": 145.0, + "y": 595.0, + "width": 0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "#64748b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1219, + "version": 1, + "versionNonce": 1220, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 4.127608686525235, + -11.018295990348065 + ], + [ + 11.704451701427757, + -1.2024185498172528 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_gr_fc", + "x": 471.0, + "y": 167.875, + "width": 98.0, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1221, + "version": 1, + "versionNonce": 1222, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_gr_fc_t", + "x": 477.0, + "y": 171.875, + "width": 86.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1223, + "version": 1, + "versionNonce": 1224, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SUBTYPE_OF", + "originalText": "SUBTYPE_OF", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_gr_gn", + "x": 203.0, + "y": 47.875, + "width": 98.0, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1225, + "version": 1, + "versionNonce": 1226, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_gr_gn_t", + "x": 209.0, + "y": 51.875, + "width": 86.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1227, + "version": 1, + "versionNonce": 1228, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SUBTYPE_OF", + "originalText": "SUBTYPE_OF", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_mma_gn", + "x": 223.65, + "y": 170.875, + "width": 136.7, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1229, + "version": 1, + "versionNonce": 1230, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_mma_gn_t", + "x": 229.65, + "y": 174.875, + "width": 124.7, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1231, + "version": 1, + "versionNonce": 1232, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MAY_MANIFEST_AS", + "originalText": "MAY_MANIFEST_AS", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_mma_neo", + "x": 223.65, + "y": 229.875, + "width": 136.7, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1233, + "version": 1, + "versionNonce": 1234, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_mma_neo_t", + "x": 229.65, + "y": 233.875, + "width": 124.7, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1235, + "version": 1, + "versionNonce": 1236, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MAY_MANIFEST_AS", + "originalText": "MAY_MANIFEST_AS", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_e_asb_lr", + "x": 534.8400658135573, + "y": 201.29556210090942, + "width": 106.7, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1237, + "version": 1, + "versionNonce": 1238, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_e_asb_lr_t", + "x": 540.8400658135573, + "y": 205.29556210090942, + "width": 94.7, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1239, + "version": 1, + "versionNonce": 1240, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "ASSESSED_BY", + "originalText": "ASSESSED_BY", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_e_asb_fl", + "x": 729.0899178053726, + "y": 142.9543599450338, + "width": 106.7, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1241, + "version": 1, + "versionNonce": 1242, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_e_asb_fl_t", + "x": 735.0899178053726, + "y": 146.9543599450338, + "width": 94.7, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1243, + "version": 1, + "versionNonce": 1244, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "ASSESSED_BY", + "originalText": "ASSESSED_BY", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_st1", + "x": 361.0, + "y": 435.875, + "width": 98.0, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1245, + "version": 1, + "versionNonce": 1246, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_st1_t", + "x": 367.0, + "y": 439.875, + "width": 86.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1247, + "version": 1, + "versionNonce": 1248, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SUBTYPE_OF", + "originalText": "SUBTYPE_OF", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_st2", + "x": 471.0, + "y": 412.875, + "width": 98.0, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1249, + "version": 1, + "versionNonce": 1250, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_st2_t", + "x": 477.0, + "y": 416.875, + "width": 86.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1251, + "version": 1, + "versionNonce": 1252, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SUBTYPE_OF", + "originalText": "SUBTYPE_OF", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_st3", + "x": 543.0, + "y": 367.875, + "width": 98.0, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1253, + "version": 1, + "versionNonce": 1254, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_st3_t", + "x": 549.0, + "y": 371.875, + "width": 86.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1255, + "version": 1, + "versionNonce": 1256, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SUBTYPE_OF", + "originalText": "SUBTYPE_OF", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_hc", + "x": 559.75, + "y": 592.875, + "width": 130.5, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1257, + "version": 1, + "versionNonce": 1258, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_hc_t", + "x": 565.75, + "y": 596.875, + "width": 118.5, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1259, + "version": 1, + "versionNonce": 1260, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HAS_COMPONENT", + "originalText": "HAS_COMPONENT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_de0", + "x": 668.35, + "y": 213.875, + "width": 147.3, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1261, + "version": 1, + "versionNonce": 1262, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_de0_t", + "x": 674.35, + "y": 217.875, + "width": 135.3, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1263, + "version": 1, + "versionNonce": 1264, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HAS_DATA_ELEMENT", + "originalText": "HAS_DATA_ELEMENT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_de1", + "x": 668.35, + "y": 252.875, + "width": 147.3, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1265, + "version": 1, + "versionNonce": 1266, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_de1_t", + "x": 674.35, + "y": 256.875, + "width": 135.3, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1267, + "version": 1, + "versionNonce": 1268, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HAS_DATA_ELEMENT", + "originalText": "HAS_DATA_ELEMENT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_de2", + "x": 668.35, + "y": 291.875, + "width": 147.3, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1269, + "version": 1, + "versionNonce": 1270, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_de2_t", + "x": 674.35, + "y": 295.875, + "width": 135.3, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1271, + "version": 1, + "versionNonce": 1272, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HAS_DATA_ELEMENT", + "originalText": "HAS_DATA_ELEMENT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_de3", + "x": 668.35, + "y": 330.875, + "width": 147.3, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1273, + "version": 1, + "versionNonce": 1274, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_de3_t", + "x": 674.35, + "y": 334.875, + "width": 135.3, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1275, + "version": 1, + "versionNonce": 1276, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HAS_DATA_ELEMENT", + "originalText": "HAS_DATA_ELEMENT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_de4", + "x": 668.35, + "y": 369.875, + "width": 147.3, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1277, + "version": 1, + "versionNonce": 1278, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_de4_t", + "x": 674.35, + "y": 373.875, + "width": 135.3, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1279, + "version": 1, + "versionNonce": 1280, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HAS_DATA_ELEMENT", + "originalText": "HAS_DATA_ELEMENT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_ms0", + "x": 653.45, + "y": 408.875, + "width": 147.1, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1281, + "version": 1, + "versionNonce": 1282, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_ms0_t", + "x": 659.45, + "y": 412.875, + "width": 135.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1283, + "version": 1, + "versionNonce": 1284, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HAS_MEASUREMENT", + "originalText": "HAS_MEASUREMENT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_ms1", + "x": 658.45, + "y": 447.875, + "width": 147.1, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1285, + "version": 1, + "versionNonce": 1286, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_ms1_t", + "x": 664.45, + "y": 451.875, + "width": 135.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1287, + "version": 1, + "versionNonce": 1288, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HAS_MEASUREMENT", + "originalText": "HAS_MEASUREMENT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_sc", + "x": 254.05, + "y": 317.875, + "width": 91.9, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1289, + "version": 1, + "versionNonce": 1290, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_sc_t", + "x": 260.05, + "y": 321.875, + "width": 79.9, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1291, + "version": 1, + "versionNonce": 1292, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SCOPED_TO", + "originalText": "SCOPED_TO", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_sp", + "x": 246.25, + "y": 362.875, + "width": 129.5, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1293, + "version": 1, + "versionNonce": 1294, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_sp_t", + "x": 252.25, + "y": 366.875, + "width": 117.5, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1295, + "version": 1, + "versionNonce": 1296, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "IN_SUBSPECIALTY", + "originalText": "IN_SUBSPECIALTY", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_sn0", + "x": 269.77, + "y": 400.455, + "width": 74.1, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1297, + "version": 1, + "versionNonce": 1298, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_sn0_t", + "x": 275.77, + "y": 404.455, + "width": 62.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1299, + "version": 1, + "versionNonce": 1300, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SEEN_ON", + "originalText": "SEEN_ON", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_sn1", + "x": 257.05, + "y": 436.375, + "width": 74.1, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1301, + "version": 1, + "versionNonce": 1302, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_sn1_t", + "x": 263.05, + "y": 440.375, + "width": 62.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1303, + "version": 1, + "versionNonce": 1304, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SEEN_ON", + "originalText": "SEEN_ON", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "l_sn2", + "x": 209.51799999999997, + "y": 504.475, + "width": 74.1, + "height": 24.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1305, + "version": 1, + "versionNonce": 1306, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_sn2_t", + "x": 215.51799999999997, + "y": 508.475, + "width": 62.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1307, + "version": 1, + "versionNonce": 1308, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SEEN_ON", + "originalText": "SEEN_ON", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "leg", + "x": 15, + "y": 735, + "width": 1020, + "height": 52, + "strokeColor": "#e2e8f0", + "backgroundColor": "#f8fafc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1309, + "version": 1, + "versionNonce": 1310, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "rectangle", + "id": "lg0", + "x": 32, + "y": 753, + "width": 22, + "height": 16, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1311, + "version": 1, + "versionNonce": 1312, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg0_t", + "x": 62, + "y": 754, + "width": 63.6, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1313, + "version": 1, + "versionNonce": 1314, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "FindingClass", + "originalText": "FindingClass", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg1", + "x": 141.6, + "y": 753, + "width": 22, + "height": 16, + "strokeColor": "#10b981", + "backgroundColor": "#ecfdf5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1315, + "version": 1, + "versionNonce": 1316, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg1_t", + "x": 171.6, + "y": 754, + "width": 45.3, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1317, + "version": 1, + "versionNonce": 1318, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Grouping", + "originalText": "Grouping", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg2", + "x": 232.89999999999998, + "y": 753, + "width": 22, + "height": 16, + "strokeColor": "#059669", + "backgroundColor": "#6ee7b7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1319, + "version": 1, + "versionNonce": 1320, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg2_t", + "x": 262.9, + "y": 754, + "width": 48.3, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1321, + "version": 1, + "versionNonce": 1322, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Diagnosis", + "originalText": "Diagnosis", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "lg3", + "x": 326.2, + "y": 752, + "width": 26, + "height": 18, + "strokeColor": "#7e22ce", + "backgroundColor": "#e9d5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1323, + "version": 1, + "versionNonce": 1324, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "text", + "id": "lg3_t", + "x": 361.2, + "y": 754, + "width": 100.3, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1325, + "version": 1, + "versionNonce": 1326, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "AssessmentScheme", + "originalText": "AssessmentScheme", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg4", + "x": 477.5, + "y": 753, + "width": 22, + "height": 16, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1327, + "version": 1, + "versionNonce": 1328, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg4_t", + "x": 507.5, + "y": 754, + "width": 87.4, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1329, + "version": 1, + "versionNonce": 1330, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "AnatomicLocation", + "originalText": "AnatomicLocation", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg5", + "x": 610.9, + "y": 753, + "width": 22, + "height": 16, + "strokeColor": "#6b7280", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1331, + "version": 1, + "versionNonce": 1332, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg5_t", + "x": 640.9, + "y": 754, + "width": 41.0, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1333, + "version": 1, + "versionNonce": 1334, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Modality", + "originalText": "Modality", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg6", + "x": 697.9, + "y": 753, + "width": 22, + "height": 16, + "strokeColor": "#be123c", + "backgroundColor": "#ffe4e6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1335, + "version": 1, + "versionNonce": 1336, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg6_t", + "x": 727.9, + "y": 754, + "width": 63.6, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1337, + "version": 1, + "versionNonce": 1338, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "DataElement", + "originalText": "DataElement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg7", + "x": 807.5, + "y": 753, + "width": 22, + "height": 16, + "strokeColor": "#6d28d9", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1339, + "version": 1, + "versionNonce": 1340, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg7_t", + "x": 837.5, + "y": 754, + "width": 67.3, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1341, + "version": 1, + "versionNonce": 1342, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Measurement", + "originalText": "Measurement", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lg8", + "x": 920.8, + "y": 753, + "width": 22, + "height": 16, + "strokeColor": "#78748f", + "backgroundColor": "#e5e3ee", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1343, + "version": 1, + "versionNonce": 1344, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "lg8_t", + "x": 950.8, + "y": 754, + "width": 62.4, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1345, + "version": 1, + "versionNonce": 1346, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Subspecialty", + "originalText": "Subspecialty", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "cap", + "x": 225.0, + "y": 799, + "width": 600, + "height": 13.75, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1347, + "version": 1, + "versionNonce": 1348, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "illustrative: reflects pending updates to the CDE graph", + "originalText": "illustrative: reflects pending updates to the CDE graph", + "fontSize": 11, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": {} +} \ No newline at end of file diff --git a/tools/diagrams/excalidraw/pillars-b3.excalidraw b/tools/diagrams/excalidraw/pillars-b3.excalidraw new file mode 100644 index 0000000..9342760 --- /dev/null +++ b/tools/diagrams/excalidraw/pillars-b3.excalidraw @@ -0,0 +1,3350 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "rectangle", + "id": "band48", + "x": 0, + "y": 373.05, + "width": 880, + "height": 198.24999999999994, + "strokeColor": "#b45309", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1095, + "version": 1, + "versionNonce": 1096, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "rectangle", + "id": "fill83", + "x": -1.0, + "y": 373.05, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1165, + "version": 1, + "versionNonce": 1166, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill84", + "x": 848, + "y": 373.05, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1167, + "version": 1, + "versionNonce": 1168, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "band32", + "x": 0, + "y": 151.05, + "width": 880, + "height": 258.0, + "strokeColor": "#0f766e", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1063, + "version": 1, + "versionNonce": 1064, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "rectangle", + "id": "fill81", + "x": -1.0, + "y": 377.05, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1161, + "version": 1, + "versionNonce": 1162, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill82", + "x": 848, + "y": 377.05, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1163, + "version": 1, + "versionNonce": 1164, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill69", + "x": -1.0, + "y": 151.05, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1137, + "version": 1, + "versionNonce": 1138, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill70", + "x": 848, + "y": 151.05, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1139, + "version": 1, + "versionNonce": 1140, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "band14", + "x": 0, + "y": 95.65, + "width": 880, + "height": 79.4, + "strokeColor": "#6d28d9", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "rectangle", + "id": "fill67", + "x": -1.0, + "y": 143.05, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1133, + "version": 1, + "versionNonce": 1134, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill68", + "x": 848, + "y": 143.05, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1135, + "version": 1, + "versionNonce": 1136, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill51", + "x": 423.0, + "y": 95.65, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1101, + "version": 1, + "versionNonce": 1102, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill52", + "x": 848, + "y": 95.65, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1103, + "version": 1, + "versionNonce": 1104, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "panel1", + "x": 0, + "y": 0, + "width": 304.0, + "height": 71.65, + "strokeColor": "#be123c", + "backgroundColor": "#ffe4e6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "rectangle", + "id": "panel2", + "x": 424.0, + "y": 0, + "width": 456.0, + "height": 119.65, + "strokeColor": "#15803d", + "backgroundColor": "#dcfce7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "rectangle", + "id": "fill49", + "x": 423.0, + "y": 87.65, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#dcfce7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1097, + "version": 1, + "versionNonce": 1098, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill50", + "x": 848, + "y": 87.65, + "width": 33, + "height": 32.0, + "strokeColor": "transparent", + "backgroundColor": "#dcfce7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1099, + "version": 1, + "versionNonce": 1100, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "image", + "id": "bicon3", + "x": 18, + "y": 14.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_bicon3", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "btitle4", + "x": 58, + "y": 16, + "width": 93.96, + "height": 22.5, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Use Cases", + "originalText": "Use Cases", + "fontSize": 18, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bsub5", + "x": 58, + "y": 39.400000000000006, + "width": 211.11999999999998, + "height": 16.25, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "what the project wants built", + "originalText": "what the project wants built", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "image", + "id": "bicon6", + "x": 442.0, + "y": 14.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_bicon6", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "btitle7", + "x": 482.0, + "y": 16, + "width": 198.35999999999999, + "height": 22.5, + "strokeColor": "#14532d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Sample Applications", + "originalText": "Sample Applications", + "fontSize": 18, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bsub8", + "x": 482.0, + "y": 39.400000000000006, + "width": 143.26, + "height": 16.25, + "strokeColor": "#15803d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "what has been built", + "originalText": "what has been built", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "conn9", + "x": 306.0, + "y": 35.825, + "width": 116.0, + "height": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 116.0, + 0 + ] + ] + }, + { + "type": "rectangle", + "id": "chip10", + "x": 327.55, + "y": 24.700000000000003, + "width": 72.9, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "chipt11", + "x": 336.55, + "y": 27.700000000000003, + "width": 54.900000000000006, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "illustrates", + "originalText": "illustrates", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "image", + "id": "bicon12", + "x": 18, + "y": 109.65, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_bicon12", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "btitle13", + "x": 58, + "y": 111.65, + "width": 41.76, + "height": 22.5, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "SDKs", + "originalText": "SDKs", + "fontSize": 18, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "image", + "id": "bicon15", + "x": 18, + "y": 183.05, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_bicon15", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "btitle16", + "x": 58, + "y": 185.05, + "width": 174.0, + "height": 25.0, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Data Structures", + "originalText": "Data Structures", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bsub17", + "x": 58, + "y": 211.05, + "width": 226.2, + "height": 16.25, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "this patient \u00b7 Patient Context", + "originalText": "this patient \u00b7 Patient Context", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "blk18", + "x": 20.0, + "y": 241.3, + "width": 188.5, + "height": 95.5, + "strokeColor": "#0f766e", + "backgroundColor": "#99f6e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "blki19", + "x": 100.25, + "y": 261.675, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_blki19", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "blkt20", + "x": 26.0, + "y": 295.675, + "width": 176.5, + "height": 18.75, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Observation", + "originalText": "Observation", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "blk21", + "x": 228.5, + "y": 241.3, + "width": 188.5, + "height": 95.5, + "strokeColor": "#0f766e", + "backgroundColor": "#99f6e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "blki22", + "x": 308.75, + "y": 252.3, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_blki22", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "blkt23", + "x": 234.5, + "y": 286.3, + "width": 176.5, + "height": 18.75, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Exam Finding", + "originalText": "Exam Finding", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "blkt24", + "x": 234.5, + "y": 305.05, + "width": 176.5, + "height": 18.75, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "List", + "originalText": "List", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "blk25", + "x": 437.0, + "y": 241.3, + "width": 188.5, + "height": 95.5, + "strokeColor": "#0f766e", + "backgroundColor": "#99f6e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "blki26", + "x": 517.25, + "y": 252.3, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_blki26", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "blkt27", + "x": 443.0, + "y": 286.3, + "width": 176.5, + "height": 18.75, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Imaging Problem", + "originalText": "Imaging Problem", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "blkt28", + "x": 443.0, + "y": 305.05, + "width": 176.5, + "height": 18.75, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1055, + "version": 1, + "versionNonce": 1056, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "List", + "originalText": "List", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "blk29", + "x": 645.5, + "y": 241.3, + "width": 188.5, + "height": 95.5, + "strokeColor": "#0f766e", + "backgroundColor": "#99f6e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1057, + "version": 1, + "versionNonce": 1058, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "blki30", + "x": 723.75, + "y": 259.675, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1059, + "version": 1, + "versionNonce": 1060, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_blki30", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "blkt31", + "x": 651.5, + "y": 295.675, + "width": 176.5, + "height": 18.75, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1061, + "version": 1, + "versionNonce": 1062, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Imaging Persona", + "originalText": "Imaging Persona", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "image", + "id": "bicon33", + "x": 18, + "y": 416.05, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1065, + "version": 1, + "versionNonce": 1066, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_bicon33", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "btitle34", + "x": 58, + "y": 418.05, + "width": 208.79999999999998, + "height": 25.0, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1067, + "version": 1, + "versionNonce": 1068, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Foundation Context", + "originalText": "Foundation Context", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bsub35", + "x": 58, + "y": 444.05, + "width": 188.5, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1069, + "version": 1, + "versionNonce": 1070, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "shared, curated knowledge", + "originalText": "shared, curated knowledge", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "blk36", + "x": 20.0, + "y": 472.3, + "width": 266.6666666666667, + "height": 84.0, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1071, + "version": 1, + "versionNonce": 1072, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "blki37", + "x": 139.33333333333334, + "y": 477.8, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1073, + "version": 1, + "versionNonce": 1074, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_blki37", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "blkt38", + "x": 26.0, + "y": 511.8, + "width": 254.66666666666669, + "height": 18.75, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1075, + "version": 1, + "versionNonce": 1076, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Finding/diagnosis definitions", + "originalText": "Finding/diagnosis definitions", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "blkd39", + "x": 26.0, + "y": 532.55, + "width": 254.66666666666669, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1077, + "version": 1, + "versionNonce": 1078, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "finding models \u00b7 CDEs", + "originalText": "finding models \u00b7 CDEs", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "blk40", + "x": 306.6666666666667, + "y": 472.3, + "width": 266.6666666666667, + "height": 84.0, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1079, + "version": 1, + "versionNonce": 1080, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "blki41", + "x": 424.0, + "y": 475.8, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1081, + "version": 1, + "versionNonce": 1082, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_blki41", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "blkt42", + "x": 312.6666666666667, + "y": 511.8, + "width": 254.66666666666669, + "height": 18.75, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1083, + "version": 1, + "versionNonce": 1084, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Anatomic locations", + "originalText": "Anatomic locations", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "blkd43", + "x": 312.6666666666667, + "y": 532.55, + "width": 254.66666666666669, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1085, + "version": 1, + "versionNonce": 1086, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "anchored in RadLex", + "originalText": "anchored in RadLex", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "blk44", + "x": 593.3333333333334, + "y": 472.3, + "width": 266.6666666666667, + "height": 84.0, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1087, + "version": 1, + "versionNonce": 1088, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "blki45", + "x": 710.6666666666667, + "y": 475.8, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1089, + "version": 1, + "versionNonce": 1090, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_blki45", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "blkt46", + "x": 599.3333333333334, + "y": 511.8, + "width": 254.66666666666669, + "height": 18.75, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1091, + "version": 1, + "versionNonce": 1092, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Exam types", + "originalText": "Exam types", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "blkd47", + "x": 599.3333333333334, + "y": 532.55, + "width": 254.66666666666669, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1093, + "version": 1, + "versionNonce": 1094, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "LOINC/RSNA Playbook", + "originalText": "LOINC/RSNA Playbook", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "fill53", + "x": 423.0, + "y": 95.65, + "width": 36.076923076923094, + "height": 27.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1105, + "version": 1, + "versionNonce": 1106, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill54", + "x": 529.2307692307693, + "y": 95.65, + "width": 35.076923076923094, + "height": 27.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1107, + "version": 1, + "versionNonce": 1108, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill55", + "x": 634.4615384615385, + "y": 95.65, + "width": 35.076923076923094, + "height": 27.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1109, + "version": 1, + "versionNonce": 1110, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill56", + "x": 739.6923076923077, + "y": 95.65, + "width": 35.076923076923094, + "height": 27.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1111, + "version": 1, + "versionNonce": 1112, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill57", + "x": 844.9230769230769, + "y": 95.65, + "width": 36.076923076923094, + "height": 27.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1113, + "version": 1, + "versionNonce": 1114, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "line", + "id": "ln58", + "x": 424.0, + "y": 63.650000000000006, + "width": 0.0, + "height": 32.0, + "strokeColor": "#15803d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1115, + "version": 1, + "versionNonce": 1116, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 32.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln59", + "x": 880, + "y": 63.650000000000006, + "width": 0, + "height": 32.0, + "strokeColor": "#15803d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1117, + "version": 1, + "versionNonce": 1118, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 0, + 32.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln60", + "x": 880, + "y": 95.65, + "width": 0, + "height": 56.0, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1119, + "version": 1, + "versionNonce": 1120, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 0, + 56.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln61", + "x": 424.0, + "y": 95.65, + "width": 456.0, + "height": 24.0, + "strokeColor": "#15803d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1121, + "version": 1, + "versionNonce": 1122, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 35.076923076923094, + 0.0 + ], + [ + 35.076923076923094, + 24.0 + ], + [ + 105.23076923076928, + 24.0 + ], + [ + 105.23076923076928, + 0.0 + ], + [ + 140.30769230769238, + 0.0 + ], + [ + 140.30769230769238, + 24.0 + ], + [ + 210.46153846153845, + 24.0 + ], + [ + 210.46153846153845, + 0.0 + ], + [ + 245.53846153846155, + 0.0 + ], + [ + 245.53846153846155, + 24.0 + ], + [ + 315.69230769230774, + 24.0 + ], + [ + 315.69230769230774, + 0.0 + ], + [ + 350.76923076923083, + 0.0 + ], + [ + 350.76923076923083, + 24.0 + ], + [ + 420.9230769230769, + 24.0 + ], + [ + 420.9230769230769, + 0.0 + ], + [ + 456.0, + 0.0 + ] + ], + "roundness": null + }, + { + "type": "text", + "id": "jw62", + "x": 459.0769230769231, + "y": 99.525, + "width": 70.15384615384619, + "height": 16.25, + "strokeColor": "#14532d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1123, + "version": 1, + "versionNonce": 1124, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "create", + "originalText": "create", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jw63", + "x": 564.3076923076924, + "y": 99.525, + "width": 70.15384615384608, + "height": 16.25, + "strokeColor": "#14532d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1125, + "version": 1, + "versionNonce": 1126, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "read", + "originalText": "read", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jw64", + "x": 669.5384615384615, + "y": 99.525, + "width": 70.15384615384619, + "height": 16.25, + "strokeColor": "#14532d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1127, + "version": 1, + "versionNonce": 1128, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "resolve", + "originalText": "resolve", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jw65", + "x": 774.7692307692308, + "y": 99.525, + "width": 70.15384615384608, + "height": 16.25, + "strokeColor": "#14532d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1129, + "version": 1, + "versionNonce": 1130, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "author", + "originalText": "author", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jl66", + "x": 459.0769230769231, + "y": 69.4, + "width": 143.26, + "height": 16.25, + "strokeColor": "#15803d", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1131, + "version": 1, + "versionNonce": 1132, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "an application can:", + "originalText": "an application can:", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "fill71", + "x": -1.0, + "y": 151.05, + "width": 121.85714285714286, + "height": 27.0, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1141, + "version": 1, + "versionNonce": 1142, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill72", + "x": 362.57142857142856, + "y": 151.05, + "width": 120.85714285714289, + "height": 27.0, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1143, + "version": 1, + "versionNonce": 1144, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill73", + "x": 725.1428571428571, + "y": 151.05, + "width": 120.85714285714289, + "height": 27.0, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1145, + "version": 1, + "versionNonce": 1146, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "line", + "id": "ln74", + "x": 0.0, + "y": 119.05000000000001, + "width": 0.0, + "height": 32.0, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1147, + "version": 1, + "versionNonce": 1148, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 32.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln75", + "x": 0.0, + "y": 151.05, + "width": 0.0, + "height": 56.0, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1149, + "version": 1, + "versionNonce": 1150, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 56.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln76", + "x": 880, + "y": 119.05000000000001, + "width": 0, + "height": 32.0, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1151, + "version": 1, + "versionNonce": 1152, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 0, + 32.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln77", + "x": 880, + "y": 151.05, + "width": 0, + "height": 56.0, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1153, + "version": 1, + "versionNonce": 1154, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 0, + 56.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln78", + "x": 0.0, + "y": 151.05, + "width": 846.0, + "height": 24.0, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1155, + "version": 1, + "versionNonce": 1156, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 120.85714285714286, + 0.0 + ], + [ + 120.85714285714286, + 24.0 + ], + [ + 362.57142857142856, + 24.0 + ], + [ + 362.57142857142856, + 0.0 + ], + [ + 483.42857142857144, + 0.0 + ], + [ + 483.42857142857144, + 24.0 + ], + [ + 725.1428571428571, + 24.0 + ], + [ + 725.1428571428571, + 0.0 + ], + [ + 846.0, + 0.0 + ] + ], + "roundness": null + }, + { + "type": "text", + "id": "jw79", + "x": 120.85714285714286, + "y": 154.925, + "width": 241.7142857142857, + "height": 16.25, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1157, + "version": 1, + "versionNonce": 1158, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "wraps", + "originalText": "wraps", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jw80", + "x": 483.42857142857144, + "y": 154.925, + "width": 241.71428571428567, + "height": 16.25, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1159, + "version": 1, + "versionNonce": 1160, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "manipulates instances", + "originalText": "manipulates instances", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "fill85", + "x": -1.0, + "y": 373.05, + "width": 51.0, + "height": 39.0, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1169, + "version": 1, + "versionNonce": 1170, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill86", + "x": 150.0, + "y": 373.05, + "width": 50.0, + "height": 39.0, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1171, + "version": 1, + "versionNonce": 1172, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill87", + "x": 300.0, + "y": 373.05, + "width": 50.0, + "height": 39.0, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1173, + "version": 1, + "versionNonce": 1174, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill88", + "x": 450.0, + "y": 373.05, + "width": 50.0, + "height": 39.0, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1175, + "version": 1, + "versionNonce": 1176, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill89", + "x": 600.0, + "y": 373.05, + "width": 281.0, + "height": 39.0, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1177, + "version": 1, + "versionNonce": 1178, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "line", + "id": "ln90", + "x": 0.0, + "y": 341.05, + "width": 0.0, + "height": 32.0, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1179, + "version": 1, + "versionNonce": 1180, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 32.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln91", + "x": 0.0, + "y": 373.05, + "width": 0.0, + "height": 68.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1181, + "version": 1, + "versionNonce": 1182, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 68.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln92", + "x": 880, + "y": 341.05, + "width": 0, + "height": 32.0, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1183, + "version": 1, + "versionNonce": 1184, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 0, + 32.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln93", + "x": 880, + "y": 373.05, + "width": 0, + "height": 68.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1185, + "version": 1, + "versionNonce": 1186, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 0, + 68.0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "ln94", + "x": 0.0, + "y": 373.05, + "width": 880.0, + "height": 36.0, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1187, + "version": 1, + "versionNonce": 1188, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 50.0, + 0.0 + ], + [ + 50.0, + 36.0 + ], + [ + 150.0, + 36.0 + ], + [ + 150.0, + 0.0 + ], + [ + 200.0, + 0.0 + ], + [ + 200.0, + 36.0 + ], + [ + 300.0, + 36.0 + ], + [ + 300.0, + 0.0 + ], + [ + 350.0, + 0.0 + ], + [ + 350.0, + 36.0 + ], + [ + 450.0, + 36.0 + ], + [ + 450.0, + 0.0 + ], + [ + 500.0, + 0.0 + ], + [ + 500.0, + 36.0 + ], + [ + 600.0, + 36.0 + ], + [ + 600.0, + 0.0 + ], + [ + 880.0, + 0.0 + ] + ], + "roundness": null + }, + { + "type": "text", + "id": "jw95", + "x": 50.0, + "y": 382.925, + "width": 100.0, + "height": 16.25, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1189, + "version": 1, + "versionNonce": 1190, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "finding", + "originalText": "finding", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jw96", + "x": 200.0, + "y": 382.925, + "width": 100.0, + "height": 16.25, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1191, + "version": 1, + "versionNonce": 1192, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "diagnosis", + "originalText": "diagnosis", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jw97", + "x": 350.0, + "y": 382.925, + "width": 100.0, + "height": 16.25, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1193, + "version": 1, + "versionNonce": 1194, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "location", + "originalText": "location", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jw98", + "x": 500.0, + "y": 382.925, + "width": 100.0, + "height": 16.25, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1195, + "version": 1, + "versionNonce": 1196, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "exam type", + "originalText": "exam type", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "jl99", + "x": 50.0, + "y": 346.8, + "width": 180.95999999999998, + "height": 16.25, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1197, + "version": 1, + "versionNonce": 1198, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "woven together at every:", + "originalText": "woven together at every:", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "fill100", + "x": 846.0, + "y": 151.05, + "width": 34.0, + "height": 222.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1199, + "version": 1, + "versionNonce": 1200, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "rectangle", + "id": "fill101", + "x": 650.0, + "y": 373.05, + "width": 230.0, + "height": 36.0, + "strokeColor": "transparent", + "backgroundColor": "#ede9fe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1201, + "version": 1, + "versionNonce": 1202, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": null + }, + { + "type": "line", + "id": "ln102", + "x": 846.0, + "y": 151.05, + "width": 230.0, + "height": 258.0, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1203, + "version": 1, + "versionNonce": 1204, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 222.0 + ], + [ + -196.0, + 222.0 + ], + [ + -196.0, + 258.0 + ], + [ + 34.0, + 258.0 + ], + [ + 34.0, + 0.0 + ] + ], + "roundness": null + }, + { + "type": "text", + "id": "lanet103", + "x": 650.0, + "y": 382.925, + "width": 230.0, + "height": 16.25, + "strokeColor": "#4c1d95", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1205, + "version": 1, + "versionNonce": 1206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "attaches to \u00b7 authors & maintains", + "originalText": "attaches to \u00b7 authors & maintains", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": { + "img_bicon3": { + "mimeType": "image/svg+xml", + "id": "img_bicon3", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiNiZTEyM2MiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTE1IDE0Yy4yLTEgLjctMS43IDEuNS0yLjUgMS0uOSAxLjUtMi4yIDEuNS0zLjVBNiA2IDAgMCAwIDYgOGMwIDEgLjIgMi4yIDEuNSAzLjUuNy43IDEuMyAxLjUgMS41IDIuNSIgLz4KICA8cGF0aCBkPSJNOSAxOGg2IiAvPgogIDxwYXRoIGQ9Ik0xMCAyMmg0IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_bicon6": { + "mimeType": "image/svg+xml", + "id": "img_bicon6", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMxNTgwM2QiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHJlY3QgeD0iMiIgeT0iNCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjE2IiByeD0iMiIgLz4KICA8cGF0aCBkPSJNMTAgNHY0IiAvPgogIDxwYXRoIGQ9Ik0yIDhoMjAiIC8+CiAgPHBhdGggZD0iTTYgNHY0IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_bicon12": { + "mimeType": "image/svg+xml", + "id": "img_bicon12", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiM2ZDI4ZDkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTExIDIxLjczYTIgMiAwIDAgMCAyIDBsNy00QTIgMiAwIDAgMCAyMSAxNlY4YTIgMiAwIDAgMC0xLTEuNzNsLTctNGEyIDIgMCAwIDAtMiAwbC03IDRBMiAyIDAgMCAwIDMgOHY4YTIgMiAwIDAgMCAxIDEuNzN6IiAvPgogIDxwYXRoIGQ9Ik0xMiAyMlYxMiIgLz4KICA8cG9seWxpbmUgcG9pbnRzPSIzLjI5IDcgMTIgMTIgMjAuNzEgNyIgLz4KICA8cGF0aCBkPSJtNy41IDQuMjcgOSA1LjE1IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_bicon15": { + "mimeType": "image/svg+xml", + "id": "img_bicon15", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwZjc2NmUiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyLjgzIDIuMThhMiAyIDAgMCAwLTEuNjYgMEwyLjYgNi4wOGExIDEgMCAwIDAgMCAxLjgzbDguNTggMy45MWEyIDIgMCAwIDAgMS42NiAwbDguNTgtMy45YTEgMSAwIDAgMCAwLTEuODN6IiAvPgogIDxwYXRoIGQ9Ik0yIDEyYTEgMSAwIDAgMCAuNTguOTFsOC42IDMuOTFhMiAyIDAgMCAwIDEuNjUgMGw4LjU4LTMuOUExIDEgMCAwIDAgMjIgMTIiIC8+CiAgPHBhdGggZD0iTTIgMTdhMSAxIDAgMCAwIC41OC45MWw4LjYgMy45MWEyIDIgMCAwIDAgMS42NSAwbDguNTgtMy45QTEgMSAwIDAgMCAyMiAxNyIgLz4KPG1ldGFkYXRhIGlkPSJsdWNpZGUtbGljZW5zZSI+CklTQyBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMjYgTHVjaWRlIEljb25zIGFuZCBDb250cmlidXRvcnMKClBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueQpwdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlCmNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2UgYXBwZWFyIGluIGFsbCBjb3BpZXMuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiBBTkQgVEhFIEFVVEhPUiBESVNDTEFJTVMgQUxMIFdBUlJBTlRJRVMKV0lUSCBSRUdBUkQgVE8gVEhJUyBTT0ZUV0FSRSBJTkNMVURJTkcgQUxMIElNUExJRUQgV0FSUkFOVElFUyBPRgpNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1IgQkUgTElBQkxFIEZPUgpBTlkgU1BFQ0lBTCwgRElSRUNULCBJTkRJUkVDVCwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIE9SIEFOWSBEQU1BR0VTCldIQVRTT0VWRVIgUkVTVUxUSU5HIEZST00gTE9TUyBPRiBVU0UsIERBVEEgT1IgUFJPRklUUywgV0hFVEhFUiBJTiBBTgpBQ1RJT04gT0YgQ09OVFJBQ1QsIE5FR0xJR0VOQ0UgT1IgT1RIRVIgVE9SVElPVVMgQUNUSU9OLCBBUklTSU5HIE9VVCBPRgpPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFVTRSBPUiBQRVJGT1JNQU5DRSBPRiBUSElTIFNPRlRXQVJFLgoKLS0tCgpUaGUgZm9sbG93aW5nIEx1Y2lkZSBpY29ucyBhcmUgZGVyaXZlZCBmcm9tIHRoZSBGZWF0aGVyIHByb2plY3Q6CgphaXJwbGF5LCBhbGVydC1jaXJjbGUsIGFsZXJ0LW9jdGFnb24sIGFsZXJ0LXRyaWFuZ2xlLCBhcGVydHVyZSwgYXJyb3ctZG93bi1jaXJjbGUsIGFycm93LWRvd24tbGVmdCwgYXJyb3ctZG93bi1yaWdodCwgYXJyb3ctZG93biwgYXJyb3ctbGVmdC1jaXJjbGUsIGFycm93LWxlZnQsIGFycm93LXJpZ2h0LWNpcmNsZSwgYXJyb3ctcmlnaHQsIGFycm93LXVwLWNpcmNsZSwgYXJyb3ctdXAtbGVmdCwgYXJyb3ctdXAtcmlnaHQsIGFycm93LXVwLCBhdC1zaWduLCBjYWxlbmRhciwgY2FzdCwgY2hlY2ssIGNoZXZyb24tZG93biwgY2hldnJvbi1sZWZ0LCBjaGV2cm9uLXJpZ2h0LCBjaGV2cm9uLXVwLCBjaGV2cm9ucy1kb3duLCBjaGV2cm9ucy1sZWZ0LCBjaGV2cm9ucy1yaWdodCwgY2hldnJvbnMtdXAsIGNpcmNsZSwgY2xpcGJvYXJkLCBjbG9jaywgY29kZSwgY29sdW1ucywgY29tbWFuZCwgY29tcGFzcywgY29ybmVyLWRvd24tbGVmdCwgY29ybmVyLWRvd24tcmlnaHQsIGNvcm5lci1sZWZ0LWRvd24sIGNvcm5lci1sZWZ0LXVwLCBjb3JuZXItcmlnaHQtZG93biwgY29ybmVyLXJpZ2h0LXVwLCBjb3JuZXItdXAtbGVmdCwgY29ybmVyLXVwLXJpZ2h0LCBjcm9zc2hhaXIsIGRhdGFiYXNlLCBkaXZpZGUtY2lyY2xlLCBkaXZpZGUtc3F1YXJlLCBkb2xsYXItc2lnbiwgZG93bmxvYWQsIGV4dGVybmFsLWxpbmssIGZlYXRoZXIsIGZyb3duLCBoYXNoLCBoZWFkcGhvbmVzLCBoZWxwLWNpcmNsZSwgaW5mbywgaXRhbGljLCBrZXksIGxheW91dCwgbGlmZS1idW95LCBsaW5rLTIsIGxpbmssIGxvYWRlciwgbG9jaywgbG9nLWluLCBsb2ctb3V0LCBtYXhpbWl6ZSwgbWVoLCBtaW5pbWl6ZSwgbWluaW1pemUtMiwgbWludXMtY2lyY2xlLCBtaW51cy1zcXVhcmUsIG1pbnVzLCBtb25pdG9yLCBtb29uLCBtb3JlLWhvcml6b250YWwsIG1vcmUtdmVydGljYWwsIG1vdmUsIG11c2ljLCBuYXZpZ2F0aW9uLTIsIG5hdmlnYXRpb24sIG9jdGFnb24sIHBhdXNlLWNpcmNsZSwgcGVyY2VudCwgcGx1cy1jaXJjbGUsIHBsdXMtc3F1YXJlLCBwbHVzLCBwb3dlciwgcmFkaW8sIHJzcywgc2VhcmNoLCBzZXJ2ZXIsIHNoYXJlLCBzaG9wcGluZy1iYWcsIHNpZGViYXIsIHNtYXJ0cGhvbmUsIHNtaWxlLCBzcXVhcmUsIHRhYmxlLTIsIHRhYmxldCwgdGFyZ2V0LCB0ZXJtaW5hbCwgdHJhc2gtMiwgdHJhc2gsIHRyaWFuZ2xlLCB0diwgdHlwZSwgdXBsb2FkLCB4LWNpcmNsZSwgeC1vY3RhZ29uLCB4LXNxdWFyZSwgeCwgem9vbS1pbiwgem9vbS1vdXQKClRoZSBNSVQgTGljZW5zZSAoTUlUKSAoZm9yIHRoZSBpY29ucyBsaXN0ZWQgYWJvdmUpCgpDb3B5cmlnaHQgKGMpIDIwMTMtcHJlc2VudCBDb2xlIEJlbWlzCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo8L21ldGFkYXRhPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_blki19": { + "mimeType": "image/svg+xml", + "id": "img_blki19", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwZjc2NmUiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIiAvPgogIDxwYXRoIGQ9Ik0xNiA0aDJhMiAyIDAgMCAxIDIgMnYxNGEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY2YTIgMiAwIDAgMSAyLTJoMiIgLz4KPG1ldGFkYXRhIGlkPSJsdWNpZGUtbGljZW5zZSI+CklTQyBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMjYgTHVjaWRlIEljb25zIGFuZCBDb250cmlidXRvcnMKClBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueQpwdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlCmNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2UgYXBwZWFyIGluIGFsbCBjb3BpZXMuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiBBTkQgVEhFIEFVVEhPUiBESVNDTEFJTVMgQUxMIFdBUlJBTlRJRVMKV0lUSCBSRUdBUkQgVE8gVEhJUyBTT0ZUV0FSRSBJTkNMVURJTkcgQUxMIElNUExJRUQgV0FSUkFOVElFUyBPRgpNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1IgQkUgTElBQkxFIEZPUgpBTlkgU1BFQ0lBTCwgRElSRUNULCBJTkRJUkVDVCwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIE9SIEFOWSBEQU1BR0VTCldIQVRTT0VWRVIgUkVTVUxUSU5HIEZST00gTE9TUyBPRiBVU0UsIERBVEEgT1IgUFJPRklUUywgV0hFVEhFUiBJTiBBTgpBQ1RJT04gT0YgQ09OVFJBQ1QsIE5FR0xJR0VOQ0UgT1IgT1RIRVIgVE9SVElPVVMgQUNUSU9OLCBBUklTSU5HIE9VVCBPRgpPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFVTRSBPUiBQRVJGT1JNQU5DRSBPRiBUSElTIFNPRlRXQVJFLgoKLS0tCgpUaGUgZm9sbG93aW5nIEx1Y2lkZSBpY29ucyBhcmUgZGVyaXZlZCBmcm9tIHRoZSBGZWF0aGVyIHByb2plY3Q6CgphaXJwbGF5LCBhbGVydC1jaXJjbGUsIGFsZXJ0LW9jdGFnb24sIGFsZXJ0LXRyaWFuZ2xlLCBhcGVydHVyZSwgYXJyb3ctZG93bi1jaXJjbGUsIGFycm93LWRvd24tbGVmdCwgYXJyb3ctZG93bi1yaWdodCwgYXJyb3ctZG93biwgYXJyb3ctbGVmdC1jaXJjbGUsIGFycm93LWxlZnQsIGFycm93LXJpZ2h0LWNpcmNsZSwgYXJyb3ctcmlnaHQsIGFycm93LXVwLWNpcmNsZSwgYXJyb3ctdXAtbGVmdCwgYXJyb3ctdXAtcmlnaHQsIGFycm93LXVwLCBhdC1zaWduLCBjYWxlbmRhciwgY2FzdCwgY2hlY2ssIGNoZXZyb24tZG93biwgY2hldnJvbi1sZWZ0LCBjaGV2cm9uLXJpZ2h0LCBjaGV2cm9uLXVwLCBjaGV2cm9ucy1kb3duLCBjaGV2cm9ucy1sZWZ0LCBjaGV2cm9ucy1yaWdodCwgY2hldnJvbnMtdXAsIGNpcmNsZSwgY2xpcGJvYXJkLCBjbG9jaywgY29kZSwgY29sdW1ucywgY29tbWFuZCwgY29tcGFzcywgY29ybmVyLWRvd24tbGVmdCwgY29ybmVyLWRvd24tcmlnaHQsIGNvcm5lci1sZWZ0LWRvd24sIGNvcm5lci1sZWZ0LXVwLCBjb3JuZXItcmlnaHQtZG93biwgY29ybmVyLXJpZ2h0LXVwLCBjb3JuZXItdXAtbGVmdCwgY29ybmVyLXVwLXJpZ2h0LCBjcm9zc2hhaXIsIGRhdGFiYXNlLCBkaXZpZGUtY2lyY2xlLCBkaXZpZGUtc3F1YXJlLCBkb2xsYXItc2lnbiwgZG93bmxvYWQsIGV4dGVybmFsLWxpbmssIGZlYXRoZXIsIGZyb3duLCBoYXNoLCBoZWFkcGhvbmVzLCBoZWxwLWNpcmNsZSwgaW5mbywgaXRhbGljLCBrZXksIGxheW91dCwgbGlmZS1idW95LCBsaW5rLTIsIGxpbmssIGxvYWRlciwgbG9jaywgbG9nLWluLCBsb2ctb3V0LCBtYXhpbWl6ZSwgbWVoLCBtaW5pbWl6ZSwgbWluaW1pemUtMiwgbWludXMtY2lyY2xlLCBtaW51cy1zcXVhcmUsIG1pbnVzLCBtb25pdG9yLCBtb29uLCBtb3JlLWhvcml6b250YWwsIG1vcmUtdmVydGljYWwsIG1vdmUsIG11c2ljLCBuYXZpZ2F0aW9uLTIsIG5hdmlnYXRpb24sIG9jdGFnb24sIHBhdXNlLWNpcmNsZSwgcGVyY2VudCwgcGx1cy1jaXJjbGUsIHBsdXMtc3F1YXJlLCBwbHVzLCBwb3dlciwgcmFkaW8sIHJzcywgc2VhcmNoLCBzZXJ2ZXIsIHNoYXJlLCBzaG9wcGluZy1iYWcsIHNpZGViYXIsIHNtYXJ0cGhvbmUsIHNtaWxlLCBzcXVhcmUsIHRhYmxlLTIsIHRhYmxldCwgdGFyZ2V0LCB0ZXJtaW5hbCwgdHJhc2gtMiwgdHJhc2gsIHRyaWFuZ2xlLCB0diwgdHlwZSwgdXBsb2FkLCB4LWNpcmNsZSwgeC1vY3RhZ29uLCB4LXNxdWFyZSwgeCwgem9vbS1pbiwgem9vbS1vdXQKClRoZSBNSVQgTGljZW5zZSAoTUlUKSAoZm9yIHRoZSBpY29ucyBsaXN0ZWQgYWJvdmUpCgpDb3B5cmlnaHQgKGMpIDIwMTMtcHJlc2VudCBDb2xlIEJlbWlzCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo8L21ldGFkYXRhPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_blki22": { + "mimeType": "image/svg+xml", + "id": "img_blki22", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwZjc2NmUiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIiAvPgogIDxwYXRoIGQ9Ik0xNiA0aDJhMiAyIDAgMCAxIDIgMnYxNGEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY2YTIgMiAwIDAgMSAyLTJoMiIgLz4KICA8cGF0aCBkPSJNMTIgMTFoNCIgLz4KICA8cGF0aCBkPSJNMTIgMTZoNCIgLz4KICA8cGF0aCBkPSJNOCAxMWguMDEiIC8+CiAgPHBhdGggZD0iTTggMTZoLjAxIiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_blki26": { + "mimeType": "image/svg+xml", + "id": "img_blki26", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwZjc2NmUiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTMgNWguMDEiIC8+CiAgPHBhdGggZD0iTTMgMTJoLjAxIiAvPgogIDxwYXRoIGQ9Ik0zIDE5aC4wMSIgLz4KICA8cGF0aCBkPSJNOCA1aDEzIiAvPgogIDxwYXRoIGQ9Ik04IDEyaDEzIiAvPgogIDxwYXRoIGQ9Ik04IDE5aDEzIiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_blki30": { + "mimeType": "image/svg+xml", + "id": "img_blki30", + "dataURL": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yNCAyNEMyOC40MjA0IDI0IDMyIDIwLjQyMDQgMzIgMTZDMzIgMTEuNTc5NiAyOC40MjA0IDggMjQgOEMxOS41Nzk2IDggMTYgMTEuNTc5NiAxNiAxNkMxNiAyMC40MjA0IDE5LjU3OTYgMjQgMjQgMjRaTTM0IDE2QzM0IDIxLjUyNSAyOS41MjUgMjYgMjQgMjZDMTguNDc1IDI2IDE0IDIxLjUyNSAxNCAxNkMxNCAxMC40NzUgMTguNDc1IDYgMjQgNkMyOS41MjUgNiAzNCAxMC40NzUgMzQgMTZaIiBmaWxsPSIjMGY3NjZlIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOS4yMjM0OCAzNC4yMTE5QzguMjIwMzggMzUuMDIxMSA4IDM1LjYyOTEgOCAzNlY0MEg0MFYzNkM0MCAzNS42MjkxIDM5Ljc3OTYgMzUuMDIxMSAzOC43NzY1IDM0LjIxMTlDMzcuNzk1OCAzMy40MjA3IDM2LjMzNDEgMzIuNjY2OSAzNC41NjIyIDMyLjAxNUMzMS4wMTk5IDMwLjcxMTcgMjYuNzUzMiAzMCAyNCAzMEMyMS4yNDY4IDMwIDE2Ljk4MDEgMzAuNzExNyAxMy40Mzc4IDMyLjAxNUMxMS42NjU5IDMyLjY2NjkgMTAuMjA0MiAzMy40MjA3IDkuMjIzNDggMzQuMjExOVpNMjQgMjhDMTcuOTkyNSAyOCA2IDMxLjAzNDcgNiAzNlY0Mkg0MlYzNkM0MiAzMS4wMzQ3IDMwLjAwNzUgMjggMjQgMjhaIiBmaWxsPSIjMGY3NjZlIi8+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_bicon33": { + "mimeType": "image/svg+xml", + "id": "img_bicon33", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiNiNDUzMDkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyIDV2MTYiIC8+CiAgPHBhdGggZD0iTTIwLjAwMSAxOUEyIDIgMCAwMDIyIDE3VjVhMiAyIDAgMDAtMS45OTktMkwxNiAzLjAwMkE1IDUgMCAwMDEyIDVhNSA1IDAgMDAtNC0ySDRhMiAyIDAgMDAtMiAydjEyYTIgMiAwIDAwMS45OTkgMkg4YTUgNSAwIDAxNCAyIDUgNSAwIDAxNC0yeiIgLz4KPG1ldGFkYXRhIGlkPSJsdWNpZGUtbGljZW5zZSI+CklTQyBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMjYgTHVjaWRlIEljb25zIGFuZCBDb250cmlidXRvcnMKClBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueQpwdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlCmNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2UgYXBwZWFyIGluIGFsbCBjb3BpZXMuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiBBTkQgVEhFIEFVVEhPUiBESVNDTEFJTVMgQUxMIFdBUlJBTlRJRVMKV0lUSCBSRUdBUkQgVE8gVEhJUyBTT0ZUV0FSRSBJTkNMVURJTkcgQUxMIElNUExJRUQgV0FSUkFOVElFUyBPRgpNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1IgQkUgTElBQkxFIEZPUgpBTlkgU1BFQ0lBTCwgRElSRUNULCBJTkRJUkVDVCwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIE9SIEFOWSBEQU1BR0VTCldIQVRTT0VWRVIgUkVTVUxUSU5HIEZST00gTE9TUyBPRiBVU0UsIERBVEEgT1IgUFJPRklUUywgV0hFVEhFUiBJTiBBTgpBQ1RJT04gT0YgQ09OVFJBQ1QsIE5FR0xJR0VOQ0UgT1IgT1RIRVIgVE9SVElPVVMgQUNUSU9OLCBBUklTSU5HIE9VVCBPRgpPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFVTRSBPUiBQRVJGT1JNQU5DRSBPRiBUSElTIFNPRlRXQVJFLgoKLS0tCgpUaGUgZm9sbG93aW5nIEx1Y2lkZSBpY29ucyBhcmUgZGVyaXZlZCBmcm9tIHRoZSBGZWF0aGVyIHByb2plY3Q6CgphaXJwbGF5LCBhbGVydC1jaXJjbGUsIGFsZXJ0LW9jdGFnb24sIGFsZXJ0LXRyaWFuZ2xlLCBhcGVydHVyZSwgYXJyb3ctZG93bi1jaXJjbGUsIGFycm93LWRvd24tbGVmdCwgYXJyb3ctZG93bi1yaWdodCwgYXJyb3ctZG93biwgYXJyb3ctbGVmdC1jaXJjbGUsIGFycm93LWxlZnQsIGFycm93LXJpZ2h0LWNpcmNsZSwgYXJyb3ctcmlnaHQsIGFycm93LXVwLWNpcmNsZSwgYXJyb3ctdXAtbGVmdCwgYXJyb3ctdXAtcmlnaHQsIGFycm93LXVwLCBhdC1zaWduLCBjYWxlbmRhciwgY2FzdCwgY2hlY2ssIGNoZXZyb24tZG93biwgY2hldnJvbi1sZWZ0LCBjaGV2cm9uLXJpZ2h0LCBjaGV2cm9uLXVwLCBjaGV2cm9ucy1kb3duLCBjaGV2cm9ucy1sZWZ0LCBjaGV2cm9ucy1yaWdodCwgY2hldnJvbnMtdXAsIGNpcmNsZSwgY2xpcGJvYXJkLCBjbG9jaywgY29kZSwgY29sdW1ucywgY29tbWFuZCwgY29tcGFzcywgY29ybmVyLWRvd24tbGVmdCwgY29ybmVyLWRvd24tcmlnaHQsIGNvcm5lci1sZWZ0LWRvd24sIGNvcm5lci1sZWZ0LXVwLCBjb3JuZXItcmlnaHQtZG93biwgY29ybmVyLXJpZ2h0LXVwLCBjb3JuZXItdXAtbGVmdCwgY29ybmVyLXVwLXJpZ2h0LCBjcm9zc2hhaXIsIGRhdGFiYXNlLCBkaXZpZGUtY2lyY2xlLCBkaXZpZGUtc3F1YXJlLCBkb2xsYXItc2lnbiwgZG93bmxvYWQsIGV4dGVybmFsLWxpbmssIGZlYXRoZXIsIGZyb3duLCBoYXNoLCBoZWFkcGhvbmVzLCBoZWxwLWNpcmNsZSwgaW5mbywgaXRhbGljLCBrZXksIGxheW91dCwgbGlmZS1idW95LCBsaW5rLTIsIGxpbmssIGxvYWRlciwgbG9jaywgbG9nLWluLCBsb2ctb3V0LCBtYXhpbWl6ZSwgbWVoLCBtaW5pbWl6ZSwgbWluaW1pemUtMiwgbWludXMtY2lyY2xlLCBtaW51cy1zcXVhcmUsIG1pbnVzLCBtb25pdG9yLCBtb29uLCBtb3JlLWhvcml6b250YWwsIG1vcmUtdmVydGljYWwsIG1vdmUsIG11c2ljLCBuYXZpZ2F0aW9uLTIsIG5hdmlnYXRpb24sIG9jdGFnb24sIHBhdXNlLWNpcmNsZSwgcGVyY2VudCwgcGx1cy1jaXJjbGUsIHBsdXMtc3F1YXJlLCBwbHVzLCBwb3dlciwgcmFkaW8sIHJzcywgc2VhcmNoLCBzZXJ2ZXIsIHNoYXJlLCBzaG9wcGluZy1iYWcsIHNpZGViYXIsIHNtYXJ0cGhvbmUsIHNtaWxlLCBzcXVhcmUsIHRhYmxlLTIsIHRhYmxldCwgdGFyZ2V0LCB0ZXJtaW5hbCwgdHJhc2gtMiwgdHJhc2gsIHRyaWFuZ2xlLCB0diwgdHlwZSwgdXBsb2FkLCB4LWNpcmNsZSwgeC1vY3RhZ29uLCB4LXNxdWFyZSwgeCwgem9vbS1pbiwgem9vbS1vdXQKClRoZSBNSVQgTGljZW5zZSAoTUlUKSAoZm9yIHRoZSBpY29ucyBsaXN0ZWQgYWJvdmUpCgpDb3B5cmlnaHQgKGMpIDIwMTMtcHJlc2VudCBDb2xlIEJlbWlzCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo8L21ldGFkYXRhPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_blki37": { + "mimeType": "image/svg+xml", + "id": "img_blki37", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiNiNDUzMDkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyLjU4NiAyLjU4NkEyIDIgMCAwIDAgMTEuMTcyIDJINGEyIDIgMCAwIDAtMiAydjcuMTcyYTIgMiAwIDAgMCAuNTg2IDEuNDE0bDguNzA0IDguNzA0YTIuNDI2IDIuNDI2IDAgMCAwIDMuNDIgMGw2LjU4LTYuNThhMi40MjYgMi40MjYgMCAwIDAgMC0zLjQyeiIgLz4KICA8Y2lyY2xlIGN4PSI3LjUiIGN5PSI3LjUiIHI9Ii41IiBmaWxsPSIjYjQ1MzA5IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_blki41": { + "mimeType": "image/svg+xml", + "id": "img_blki41", + "dataURL": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMyA2LjAwMDEzQzIzIDYuMDAwMDQgMjMgNiAyNCA2SDI0LjE3NjFDMjQuMjUzMyA2IDI0LjMyMzMgNiAyNC4zODY4IDZDMjUgNi4wMDAwMSAyNSA2LjAwMDA1IDI1IDYuMDAwMTNWMjAuNUMyNSAyMS44OTc3IDI1LjU2NDEgMjIuNDQyMSAyNi4wMDM5IDIyLjY5ODdDMjYuMDUzIDIyLjcyNzMgMjYuMTAyNCAyMi43NTMzIDI2LjE1MTYgMjIuNzc3QzI2LjE1MDYgMjIuNjU2OCAyNi4xNDkzIDIyLjUzMTYgMjYuMTQ3OSAyMi40MDE5TDI2LjE0NzkgMjIuNDAwM0MyNi4xMTAxIDE4Ljg3MTYgMjYuMDM2NSAxMS45OTk3IDMxLjQ3NTMgMTEuOTk5N0MzOC45MjU5IDExLjk5OTcgNDQuMjQ3OCAzOC4wMDYyIDQxLjA1NDcgNDAuNjU5OUMzNy44NjE1IDQzLjMxMzYgMzEuNDc1MyA0MS43MjE0IDI4LjI4MjIgMzguNTM2OUMyNS43MDQyIDM1Ljk2NTkgMjUuOTI3MiAyOS45NTgzIDI2LjA4NzMgMjUuNjQ1N0wyNi4wODc0IDI1LjY0NDNDMjYuMDk3MSAyNS4zODE5IDI2LjEwNjcgMjUuMTI1OCAyNi4xMTUzIDI0Ljg3NzFDMjUuNzkgMjQuNzk2MiAyNS4zOTU5IDI0LjY1OTUgMjQuOTk2MiAyNC40MjYzQzI0LjYzOTggMjQuMjE4NCAyNC4yOTc1IDIzLjk0NDIgMjQgMjMuNTkxOUMyMy43MDI2IDIzLjk0NDIgMjMuMzYwMyAyNC4yMTg0IDIzLjAwMzkgMjQuNDI2M0MyMi42MDQxIDI0LjY1OTUgMjIuMjEwMSAyNC43OTYyIDIxLjg4NDggMjQuODc3MUMyMS44OTM0IDI1LjEyNjEgMjEuOTAyOSAyNS4zODI2IDIxLjkxMjcgMjUuNjQ1M0MyMi4wNzI4IDI5Ljk1NzkgMjIuMjk1OSAzNS45NjYyIDE5LjcxNzkgMzguNTM3MkMxNi41MjQ3IDQxLjcyMTcgMTAuMTM4NSA0My4zMTM5IDYuOTQ1MzYgNDAuNjYwMkMzLjc1MjIyIDM4LjAwNjUgOS4wNzQxMyAxMiAxNi41MjQ3IDEyQzIxLjk2MzYgMTIgMjEuODkgMTguODcxNyAyMS44NTIyIDIyLjQwMDNDMjEuODUyIDIyLjQxNjYgMjEuODUxOCAyMi40MzI4IDIxLjg1MTYgMjIuNDQ4OUMyMS44NTA0IDIyLjU2MTkgMjEuODQ5MyAyMi42NzE0IDIxLjg0ODQgMjIuNzc3QzIxLjg5NzcgMjIuNzUzNCAyMS45NDcxIDIyLjcyNzQgMjEuOTk2MiAyMi42OTg3QzIyLjQzNiAyMi40NDIxIDIzIDIxLjg5NzcgMjMgMjAuNVY2LjAwMDEzWk0zMS40NzUzIDEzLjk5OTdDMzAuNTIxMiAxMy45OTk3IDI5Ljk4NDMgMTQuMzAwMiAyOS42MDg2IDE0LjY4NjlDMjkuMTc1MiAxNS4xMzMxIDI4LjgxMzIgMTUuODUwNyAyOC41NTkgMTYuODY0NkMyOC4xMTQxIDE4LjYzOTUgMjguMTMzIDIwLjY4MTkgMjguMTQ4MyAyMi4zMjkzQzI4LjE1MDkgMjIuNjE0MiAyOC4xNTM0IDIyLjg4NzQgMjguMTUzNCAyMy4xNDUzQzI4LjE1MzQgMjMuOTA1MyAyOC4xMTk2IDI0LjgxNTMgMjguMDg0NSAyNS43NjIxQzI4LjA3MDcgMjYuMTM0OSAyOC4wNTY2IDI2LjUxMzMgMjguMDQ0MyAyNi44OTA1QzI3Ljk5OTYgMjguMjY1NyAyNy45NzQxIDI5LjcwNTUgMjguMDQwNSAzMS4wOTgyQzI4LjEwNzIgMzIuNDk4NSAyOC4yNjQyIDMzLjc4MzUgMjguNTU3MyAzNC44NTk2QzI4Ljg1MzggMzUuOTQ4MyAyOS4yNTA5IDM2LjY3ODQgMjkuNjk0NCAzNy4xMjA4QzMwLjk3MjYgMzguMzk1NCAzMi45OTA4IDM5LjQyNTEgMzUuMDQ5MiAzOS44MjQzQzM3LjA4NDIgNDAuMjE4OSAzOC43MjI0IDM5LjkyNjkgMzkuNjgxOCAzOS4xOTY5QzM5LjY4NjggMzkuMTg1NyAzOS42OTIzIDM5LjE3MjYgMzkuNjk4NCAzOS4xNTc0QzM5Ljc1NzYgMzkuMDA5NSAzOS44Mjk0IDM4Ljc1NTQgMzkuODg3OCAzOC4zNjgzQzQwLjAwNDcgMzcuNTkzMyA0MC4wMzUgMzYuNTEyMiAzOS45NTYyIDM1LjE4MDNDMzkuNzk5NSAzMi41MzA1IDM5LjIyODYgMjkuMTY4OSAzOC4zMjY4IDI1Ljg2MDZDMzcuNDIxNiAyMi41Mzk2IDM2LjIxNzMgMTkuMzk5OCAzNC44NTIxIDE3LjEzODNDMzQuMTY4OCAxNi4wMDYyIDMzLjQ5NTEgMTUuMTc4NiAzMi44NzAzIDE0LjY1MzVDMzIuMjUxOCAxNC4xMzM3IDMxLjc5MzUgMTMuOTk5NyAzMS40NzUzIDEzLjk5OTdaTTE2LjUyNDcgMTRDMTcuNDc4OCAxNCAxOC4wMTU4IDE0LjMwMDUgMTguMzkxNCAxNC42ODcyQzE4LjgyNDggMTUuMTMzNCAxOS4xODY4IDE1Ljg1MSAxOS40NDEgMTYuODY0OUMxOS44ODU5IDE4LjYzOTkgMTkuODY3IDIwLjY4MjIgMTkuODUxOCAyMi4zMjk2QzE5Ljg0OTEgMjIuNjE0NiAxOS44NDY2IDIyLjg4NzcgMTkuODQ2NiAyMy4xNDU2QzE5Ljg0NjYgMjMuOTA1NiAxOS44ODA0IDI0LjgxNTYgMTkuOTE1NSAyNS43NjI0QzE5LjkyOTQgMjYuMTM1IDE5Ljk0MzQgMjYuNTEzOCAxOS45NTU3IDI2Ljg5MDhDMjAuMDAwNSAyOC4yNjYgMjAuMDI1OSAyOS43MDU4IDE5Ljk1OTYgMzEuMDk4NUMxOS44OTI5IDMyLjQ5ODggMTkuNzM1OSAzMy43ODM4IDE5LjQ0MjcgMzQuODU5OUMxOS4xNDYyIDM1Ljk0ODYgMTguNzQ5MiAzNi42Nzg3IDE4LjMwNTYgMzcuMTIxMUMxNy4wMjc1IDM4LjM5NTcgMTUuMDA5MyAzOS40MjU0IDEyLjk1MDkgMzkuODI0NkMxMC45MTU5IDQwLjIxOTIgOS4yNzc2OCAzOS45MjcyIDguMzE4MjMgMzkuMTk3MkM4LjMxMzI2IDM5LjE4NiA4LjMwNzcgMzkuMTcyOSA4LjMwMTYyIDM5LjE1NzdDOC4yNDI0MSAzOS4wMDk4IDguMTcwNjEgMzguNzU1NyA4LjExMjI0IDM4LjM2ODZDNy45OTUzOSAzNy41OTM2IDcuOTY1IDM2LjUxMjUgOC4wNDM4IDM1LjE4MDZDOC4yMDA1NiAzMi41MzA4IDguNzcxNDcgMjkuMTY5MiA5LjY3MzI0IDI1Ljg2MDlDMTAuNTc4NSAyMi41Mzk5IDExLjc4MjcgMTkuNDAwMSAxMy4xNDc5IDE3LjEzODZDMTMuODMxMyAxNi4wMDY1IDE0LjUwNDkgMTUuMTc4OSAxNS4xMjk4IDE0LjY1MzhDMTUuNzQ4MiAxNC4xMzQgMTYuMjA2NSAxNCAxNi41MjQ3IDE0WiIgZmlsbD0iI2I0NTMwOSIvPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_blki45": { + "mimeType": "image/svg+xml", + "id": "img_blki45", + "dataURL": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yOSA5QzI5IDExLjc2MTQgMjYuNzYxNCAxNCAyNCAxNEMyMS4yMzg2IDE0IDE5IDExLjc2MTQgMTkgOUMxOSA2LjIzODU4IDIxLjIzODYgNCAyNCA0QzI2Ljc2MTQgNCAyOSA2LjIzODU4IDI5IDlaTTI3IDlDMjcgMTAuNjU2OSAyNS42NTY5IDEyIDI0IDEyQzIyLjM0MzEgMTIgMjEgMTAuNjU2OSAyMSA5QzIxIDcuMzQzMTUgMjIuMzQzMSA2IDI0IDZDMjUuNjU2OSA2IDI3IDcuMzQzMTUgMjcgOVoiIGZpbGw9IiNiNDUzMDkiLz4KPHBhdGggZD0iTTE2Ljk5OTggNDFWMzRIMTguOTk5OFY0MUMxOC45OTk4IDQxLjU1MjMgMTkuNDQ3NSA0MiAxOS45OTk4IDQySDIwLjA4NjVDMjAuNjAzNyA0MiAyMS4wMzU2IDQxLjYwNTYgMjEuMDgyNCA0MS4wOTA1TDIxLjcyNyAzNEgyMy43MzUzTDIzLjA3NDIgNDEuMjcxNkMyMy4wNjkgNDEuMzI4OCAyMy4wNjIyIDQxLjM4NTYgMjMuMDUzOSA0MS40NDE3QzIzLjAzNTcgNDEuNTY0NyAyMy4wMTAxIDQxLjY4NTEgMjIuOTc3NiA0MS44MDI1QzIyLjYyNDIgNDMuMDc5OCAyMS40NTQ5IDQ0IDIwLjA4NjUgNDRIMTkuOTk5OEMxOC4zNDI5IDQ0IDE2Ljk5OTggNDIuNjU2OSAxNi45OTk4IDQxWiIgZmlsbD0iI2I0NTMwOSIvPgo8cGF0aCBkPSJNMjYuOTE3MSA0MS4wOTA1TDI2LjI3MjUgMzRIMjQuMjY0MkwyNC45MjUzIDQxLjI3MTZDMjQuOTMwNSA0MS4zMjg4IDI0LjkzNzMgNDEuMzg1NiAyNC45NDU2IDQxLjQ0MTdDMjQuOTYzOCA0MS41NjQ3IDI0Ljk4OTQgNDEuNjg1MSAyNS4wMjE5IDQxLjgwMjVDMjUuMzc1MyA0My4wNzk4IDI2LjU0NDYgNDQgMjcuOTEzIDQ0SDI3Ljk5OThDMjkuNjU2NiA0NCAzMC45OTk4IDQyLjY1NjkgMzAuOTk5OCA0MVYzNEgyOC45OTk4VjQxQzI4Ljk5OTggNDEuNTUyMyAyOC41NTIgNDIgMjcuOTk5OCA0MkgyNy45MTNDMjcuMzk1OCA0MiAyNi45NjM5IDQxLjYwNTYgMjYuOTE3MSA0MS4wOTA1WiIgZmlsbD0iI2I0NTMwOSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTExIDE1QzEwLjQ0NzcgMTUgMTAgMTUuNDQ3NyAxMCAxNlYzMUMxMCAzMS41NTIzIDEwLjQ0NzcgMzIgMTEgMzJIMzdDMzcuNTUyMyAzMiAzOCAzMS41NTIzIDM4IDMxVjE2QzM4IDE1LjQ0NzcgMzcuNTUyMyAxNSAzNyAxNUgxMVpNMTIgMTdWMzBIMTkuNUMxOS41IDI5LjQ0MjQgMTkuNzU5MSAyOC45NTcyIDIwLjAyNzYgMjguNjAwNkMyMC4zMDg2IDI4LjIyNzYgMjAuNjc5NiAyNy44ODgzIDIxLjA3OTQgMjcuNjAxNkMyMS42MjUyIDI3LjIxMDMgMjIuMjk4NiAyNi44NjUgMjMgMjYuNjY3OFYyNC41SDIyLjJDMjEuNTk3MyAyNC41IDIwLjc1MzUgMjQuODY0MiAxOS45NDMzIDI1LjM1NTJDMTkuNTYzOCAyNS41ODUyIDE5LjIzMzcgMjUuODE2NyAxOC45OTg0IDI1Ljk5MTFDMTguODgxMiAyNi4wNzc4IDE4Ljc4ODggMjYuMTQ5NSAxOC43MjcgMjYuMTk4NEMxOC42OTYxIDI2LjIyMjggMTguNjczIDI2LjI0MTUgMTguNjU4MiAyNi4yNTM1TDE4LjY0MjYgMjYuMjY2M0wxOC42Mzk5IDI2LjI2ODRDMTguNjM5OSAyNi4yNjg0IDE4LjY0MDIgMjYuMjY4MiAxOCAyNS41QzE3LjM1OTggMjQuNzMxOCAxNy4zNjAxIDI0LjczMTUgMTcuMzYwMSAyNC43MzE1TDE3LjM2MTQgMjQuNzMwNUwxNy4zNjM4IDI0LjcyODVMMTcuMzcxMyAyNC43MjIzTDE3LjM5NjYgMjQuNzAxNkMxNy40MTc5IDI0LjY4NDMgMTcuNDQ4IDI0LjY2IDE3LjQ4NjMgMjQuNjI5N0MxNy41NjI3IDI0LjU2OTMgMTcuNjcxOSAyNC40ODQ3IDE3LjgwNzkgMjQuMzgzOUMxOC4wNzg4IDI0LjE4MzMgMTguNDYxMiAyMy45MTQ4IDE4LjkwNjcgMjMuNjQ0OEMxOS43NDY1IDIzLjEzNTggMjEuMDAyNyAyMi41IDIyLjIgMjIuNUgyM1YyMC41SDIyLjVDMjEuOTMwOSAyMC41IDIxLjE5MDkgMjAuNjk5MiAyMC41MjkyIDIwLjkzOThDMjAuMjEyIDIxLjA1NTIgMTkuOTM3IDIxLjE3MDkgMTkuNzQyMSAyMS4yNTc2QzE5LjY0NDkgMjEuMzAwNyAxOS41Njg1IDIxLjMzNjMgMTkuNTE3NiAyMS4zNjA1QzE5LjQ5MjIgMjEuMzcyNSAxOS40NzMyIDIxLjM4MTcgMTkuNDYxMyAyMS4zODc1TDE5LjQ0ODcgMjEuMzkzN0wxOS40NDcgMjEuMzk0NUMxOS40NDcgMjEuMzk0NSAxOS40NDcyIDIxLjM5NDQgMTkgMjAuNUMxOC41NTI4IDE5LjYwNTYgMTguNTUzMSAxOS42MDU0IDE4LjU1MzEgMTkuNjA1NEwxOC41NTY0IDE5LjYwMzhMMTguNTYyOSAxOS42MDA2TDE4LjU4NDQgMTkuNTlDMTguNjAyNSAxOS41ODEyIDE4LjYyNzkgMTkuNTY4OSAxOC42NjAxIDE5LjU1MzZDMTguNzI0NSAxOS41MjMxIDE4LjgxNiAxOS40ODA1IDE4LjkyOTggMTkuNDI5OUMxOS4xNTY3IDE5LjMyOTEgMTkuNDc1NSAxOS4xOTQ4IDE5Ljg0NTggMTkuMDYwMkMyMC41NTkxIDE4LjgwMDggMjEuNTY5MSAxOC41IDIyLjUgMTguNUgyM1YxN0gxMlpNMjUgMjQuNUgyNi4xQzI2Ljg0ODkgMjQuNSAyNy42MTg2IDI0Ljg2NjMgMjguMjYzMSAyNS4zMTg2QzI4LjU3MzUgMjUuNTM2NCAyOC44Mjg1IDI1Ljc1NTYgMjkuMDA0OCAyNS45MTk3QzI5LjA5MjUgMjYuMDAxMiAyOS4xNTk0IDI2LjA2NzkgMjkuMjAyNiAyNi4xMTIyQzI5LjIyNDEgMjYuMTM0MyAyOS4yMzk3IDI2LjE1MDcgMjkuMjQ4OCAyNi4xNjA1TDI5LjI1NzQgMjYuMTY5N0MyOS4yNTc0IDI2LjE2OTcgMjkuMjU2NyAyNi4xNjkgMzAgMjUuNUMzMC43NDMzIDI0LjgzMSAzMC43NDI5IDI0LjgzMDYgMzAuNzQyOSAyNC44MzA2TDMwLjc0MTQgMjQuODI5TDMwLjczODkgMjQuODI2MkwzMC43MzE4IDI0LjgxODRMMzAuNzA5NyAyNC43OTQ2QzMwLjY5MTUgMjQuNzc1MSAzMC42NjYzIDI0Ljc0ODUgMzAuNjM0NiAyNC43MTZDMzAuNTcxMSAyNC42NTA5IDMwLjQ4MDkgMjQuNTYxMyAzMC4zNjcgMjQuNDU1M0MzMC4xNDAzIDI0LjI0NDQgMjkuODE0IDIzLjk2MzYgMjkuNDExOSAyMy42ODE0QzI4LjYzMTQgMjMuMTMzNyAyNy40NTExIDIyLjUgMjYuMSAyMi41SDI1VjIwLjVIMjUuNzVDMjYuNDQ0MyAyMC41IDI3LjExOTMgMjAuNzAxMSAyNy42NDMyIDIwLjkyMTZDMjcuOTAwMyAyMS4wMjk5IDI4LjEwOTIgMjEuMTM3OCAyOC4yNTA5IDIxLjIxNjlDMjguMzIxNSAyMS4yNTY0IDI4LjM3NDcgMjEuMjg4MiAyOC40MDggMjEuMzA4N0MyOC40MjQ3IDIxLjMxODkgMjguNDM2MyAyMS4zMjYzIDI4LjQ0MjcgMjEuMzMwNEwyOC40NDU3IDIxLjMzMjNDMjguNDQ1NyAyMS4zMzIzIDI4LjQ0NTMgMjEuMzMyMSAyOSAyMC41QzI5LjU1NDcgMTkuNjY3OSAyOS41NTQyIDE5LjY2NzYgMjkuNTU0MiAxOS42Njc2TDI5LjU1MjcgMTkuNjY2NkwyOS41NTAxIDE5LjY2NDlMMjkuNTQzMiAxOS42NjA0TDI5LjUyMjggMTkuNjQ3MkMyOS41MDYzIDE5LjYzNjYgMjkuNDg0IDE5LjYyMjUgMjkuNDU2MiAxOS42MDU0QzI5LjQwMDcgMTkuNTcxMiAyOS4zMjMgMTkuNTI0OSAyOS4yMjU2IDE5LjQ3MDZDMjkuMDMxNSAxOS4zNjIyIDI4Ljc1NTkgMTkuMjIwMSAyOC40MTkzIDE5LjA3ODRDMjcuNzU1NyAxOC43OTg5IDI2LjgwNTcgMTguNSAyNS43NSAxOC41SDI1VjE3SDM2VjMwSDI4LjVDMjguNSAyOS40Mzc5IDI4LjIzMjQgMjguOTQ1MiAyNy45NjQxIDI4LjU4NzVDMjcuNjgwMyAyOC4yMDkyIDI3LjMwNTQgMjcuODYwNiAyNi45MDA3IDI3LjU2NjNDMjYuMzY3NSAyNy4xNzg1IDI1LjY5ODkgMjYuODI1NyAyNSAyNi42MzkxVjI0LjVaTTI2LjQ5MjkgMzBDMjYuNDc5NyAyOS45NjcgMjYuNDQ1NyAyOS44OTY0IDI2LjM2NDEgMjkuNzg3NUMyNi4yMjU5IDI5LjYwMzMgMjYuMDA3MSAyOS4zODk0IDI1LjcyNDMgMjkuMTgzN0MyNS4xNDc4IDI4Ljc2NDUgMjQuNDkzNyAyOC41MTE3IDI0LjAzMiAyOC41MDA0QzIzLjQ4NzcgMjguNTQ2NCAyMi44MDUyIDI4LjgyNTIgMjIuMjQ0OSAyOS4yMjdDMjEuOTY1NyAyOS40MjcyIDIxLjc1NTYgMjkuNjMwNyAyMS42MjUyIDI5LjgwMzhDMjEuNTQ5NiAyOS45MDQzIDIxLjUxODQgMjkuOTY5NCAyMS41MDYzIDMwSDI2LjQ5MjlaIiBmaWxsPSIjYjQ1MzA5Ii8+Cjwvc3ZnPgo=", + "created": 1700000000000 + } + } +} \ No newline at end of file diff --git a/tools/diagrams/excalidraw/three-axes.excalidraw b/tools/diagrams/excalidraw/three-axes.excalidraw new file mode 100644 index 0000000..e1d6f74 --- /dev/null +++ b/tools/diagrams/excalidraw/three-axes.excalidraw @@ -0,0 +1,5762 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "rectangle", + "id": "col1", + "x": 0, + "y": 0, + "width": 316, + "height": 822.5, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "coli2", + "x": 140.0, + "y": 16, + "width": 36, + "height": 36, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_coli2", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "colt3", + "x": 8, + "y": 61, + "width": 300, + "height": 21.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Findings/Diagnoses", + "originalText": "Findings/Diagnoses", + "fontSize": 17, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "coltag4", + "x": 8, + "y": 87.25, + "width": 300, + "height": 17.5, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "WHAT was found", + "originalText": "WHAT was found", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "rule5", + "x": 18, + "y": 115.75, + "width": 280, + "height": 0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 35, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 280, + 0 + ] + ] + }, + { + "type": "rectangle", + "id": "tb6", + "x": 18, + "y": 127.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt7", + "x": 28, + "y": 129.625, + "width": 60.31999999999999, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Findings", + "originalText": "Findings", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn8", + "x": 34, + "y": 156.5, + "width": 129.92, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary nodule", + "originalText": "pulmonary nodule", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc9", + "x": 41, + "y": 175.25, + "width": 0, + "height": 13.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 13.0 + ] + ] + }, + { + "type": "line", + "id": "tc10", + "x": 41, + "y": 188.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn11", + "x": 52, + "y": 180.125, + "width": 37.699999999999996, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "solid", + "originalText": "solid", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc12", + "x": 41, + "y": 175.25, + "width": 0, + "height": 36.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 36.0 + ] + ] + }, + { + "type": "line", + "id": "tc13", + "x": 41, + "y": 211.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn14", + "x": 52, + "y": 203.125, + "width": 75.39999999999999, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "part-solid", + "originalText": "part-solid", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc15", + "x": 41, + "y": 175.25, + "width": 0, + "height": 59.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 59.0 + ] + ] + }, + { + "type": "line", + "id": "tc16", + "x": 41, + "y": 234.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn17", + "x": 52, + "y": 226.125, + "width": 67.86, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "non-solid", + "originalText": "non-solid", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb18", + "x": 18, + "y": 257.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt19", + "x": 28, + "y": 259.625, + "width": 67.86, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Diagnoses", + "originalText": "Diagnoses", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn20", + "x": 34, + "y": 286.5, + "width": 146.16, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary neoplasm", + "originalText": "pulmonary neoplasm", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc21", + "x": 41, + "y": 305.25, + "width": 0, + "height": 13.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 13.0 + ] + ] + }, + { + "type": "line", + "id": "tc22", + "x": 41, + "y": 318.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn23", + "x": 52, + "y": 310.125, + "width": 196.04, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "non-small cell lung cancer", + "originalText": "non-small cell lung cancer", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc24", + "x": 59, + "y": 328.25, + "width": 0, + "height": 13.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 13.0 + ] + ] + }, + { + "type": "line", + "id": "tc25", + "x": 59, + "y": 341.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn26", + "x": 70, + "y": 333.125, + "width": 196.04, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "adenocarcinoma of the lung", + "originalText": "adenocarcinoma of the lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb27", + "x": 18, + "y": 364.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt28", + "x": 28, + "y": 366.625, + "width": 82.94, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1055, + "version": 1, + "versionNonce": 1056, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Assessments", + "originalText": "Assessments", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn29", + "x": 34, + "y": 393.5, + "width": 73.08, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1057, + "version": 1, + "versionNonce": 1058, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Lung-RADS", + "originalText": "Lung-RADS", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn30", + "x": 34, + "y": 416.5, + "width": 178.64, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1059, + "version": 1, + "versionNonce": 1060, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Salter-Harris category", + "originalText": "Salter-Harris category", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb31", + "x": 18, + "y": 448.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1061, + "version": 1, + "versionNonce": 1062, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt32", + "x": 28, + "y": 450.625, + "width": 98.02, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1063, + "version": 1, + "versionNonce": 1064, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Data Elements", + "originalText": "Data Elements", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn33", + "x": 34, + "y": 477.5, + "width": 64.96, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1065, + "version": 1, + "versionNonce": 1066, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "presence", + "originalText": "presence", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ta34", + "x": 42, + "y": 501.125, + "width": 316.67999999999995, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1067, + "version": 1, + "versionNonce": 1068, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "present \u00b7 absent \u00b7 indeterminate \u00b7 unknown", + "originalText": "present \u00b7 absent \u00b7 indeterminate \u00b7 unknown", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn35", + "x": 34, + "y": 523.5, + "width": 64.96, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1069, + "version": 1, + "versionNonce": 1070, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "severity", + "originalText": "severity", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ta36", + "x": 42, + "y": 547.125, + "width": 256.35999999999996, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1071, + "version": 1, + "versionNonce": 1072, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "minimal \u00b7 mild \u00b7 moderate \u00b7 severe", + "originalText": "minimal \u00b7 mild \u00b7 moderate \u00b7 severe", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb37", + "x": 18, + "y": 578.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1073, + "version": 1, + "versionNonce": 1074, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt38", + "x": 28, + "y": 580.625, + "width": 90.47999999999999, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1075, + "version": 1, + "versionNonce": 1076, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Measurements", + "originalText": "Measurements", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn39", + "x": 34, + "y": 607.5, + "width": 48.72, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1077, + "version": 1, + "versionNonce": 1078, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "length", + "originalText": "length", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ta40", + "x": 42, + "y": 631.125, + "width": 82.94, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1079, + "version": 1, + "versionNonce": 1080, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "mm, cm, ...", + "originalText": "mm, cm, ...", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn41", + "x": 34, + "y": 653.5, + "width": 81.19999999999999, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1081, + "version": 1, + "versionNonce": 1082, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT density", + "originalText": "CT density", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ta42", + "x": 42, + "y": 677.125, + "width": 15.079999999999998, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1083, + "version": 1, + "versionNonce": 1084, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HU", + "originalText": "HU", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "colblk43", + "x": 18, + "y": 758.0, + "width": 280, + "height": 48.5, + "strokeColor": "#78350f", + "backgroundColor": "#b45309", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1085, + "version": 1, + "versionNonce": 1086, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "colblkt44", + "x": 24, + "y": 768.0, + "width": 268, + "height": 16.25, + "strokeColor": "#fef3c7", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1087, + "version": 1, + "versionNonce": 1088, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "layered over: finding models", + "originalText": "layered over: finding models", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "colblkt45", + "x": 24, + "y": 784.25, + "width": 268, + "height": 16.25, + "strokeColor": "#fef3c7", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1089, + "version": 1, + "versionNonce": 1090, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "\u00b7 ACR/RSNA CDEs", + "originalText": "\u00b7 ACR/RSNA CDEs", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "col46", + "x": 336, + "y": 0, + "width": 316, + "height": 822.5, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1091, + "version": 1, + "versionNonce": 1092, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "coli47", + "x": 476.0, + "y": 16, + "width": 36, + "height": 36, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1093, + "version": 1, + "versionNonce": 1094, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_coli47", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "colt48", + "x": 344, + "y": 61, + "width": 300, + "height": 21.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1095, + "version": 1, + "versionNonce": 1096, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Anatomic Locations", + "originalText": "Anatomic Locations", + "fontSize": 17, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "coltag49", + "x": 344, + "y": 87.25, + "width": 300, + "height": 17.5, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1097, + "version": 1, + "versionNonce": 1098, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "WHERE it is", + "originalText": "WHERE it is", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "rule50", + "x": 354, + "y": 115.75, + "width": 280, + "height": 0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 35, + "angle": 0, + "seed": 1099, + "version": 1, + "versionNonce": 1100, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 280, + 0 + ] + ] + }, + { + "type": "rectangle", + "id": "tb51", + "x": 354, + "y": 127.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1101, + "version": 1, + "versionNonce": 1102, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt52", + "x": 364, + "y": 129.625, + "width": 45.239999999999995, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1103, + "version": 1, + "versionNonce": 1104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "thorax", + "originalText": "thorax", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn53", + "x": 370, + "y": 156.24404761904762, + "width": 32.48, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1105, + "version": 1, + "versionNonce": 1106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lung", + "originalText": "lung", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc54", + "x": 377, + "y": 174.99404761904762, + "width": 0, + "height": 12.48809523809524, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1107, + "version": 1, + "versionNonce": 1108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 12.48809523809524 + ] + ] + }, + { + "type": "line", + "id": "tc55", + "x": 377, + "y": 187.48214285714286, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1109, + "version": 1, + "versionNonce": 1110, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn56", + "x": 388, + "y": 179.35714285714286, + "width": 75.39999999999999, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1111, + "version": 1, + "versionNonce": 1112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "right lung", + "originalText": "right lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc57", + "x": 395, + "y": 197.48214285714286, + "width": 0, + "height": 12.48809523809524, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1113, + "version": 1, + "versionNonce": 1114, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 12.48809523809524 + ] + ] + }, + { + "type": "line", + "id": "tc58", + "x": 395, + "y": 209.9702380952381, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1115, + "version": 1, + "versionNonce": 1116, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn59", + "x": 406, + "y": 201.8452380952381, + "width": 180.95999999999998, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1117, + "version": 1, + "versionNonce": 1118, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "upper lobe of right lung", + "originalText": "upper lobe of right lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc60", + "x": 395, + "y": 197.48214285714286, + "width": 0, + "height": 34.97619047619048, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1119, + "version": 1, + "versionNonce": 1120, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 34.97619047619048 + ] + ] + }, + { + "type": "line", + "id": "tc61", + "x": 395, + "y": 232.45833333333334, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1121, + "version": 1, + "versionNonce": 1122, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn62", + "x": 406, + "y": 224.33333333333334, + "width": 188.5, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1123, + "version": 1, + "versionNonce": 1124, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "middle lobe of right lung", + "originalText": "middle lobe of right lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc63", + "x": 395, + "y": 197.48214285714286, + "width": 0, + "height": 57.46428571428572, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1125, + "version": 1, + "versionNonce": 1126, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 57.46428571428572 + ] + ] + }, + { + "type": "line", + "id": "tc64", + "x": 395, + "y": 254.94642857142858, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1127, + "version": 1, + "versionNonce": 1128, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn65", + "x": 406, + "y": 246.82142857142858, + "width": 180.95999999999998, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1129, + "version": 1, + "versionNonce": 1130, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lower lobe of right lung", + "originalText": "lower lobe of right lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc66", + "x": 377, + "y": 174.99404761904762, + "width": 0, + "height": 102.44047619047618, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1131, + "version": 1, + "versionNonce": 1132, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 102.44047619047618 + ] + ] + }, + { + "type": "line", + "id": "tc67", + "x": 377, + "y": 277.4345238095238, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1133, + "version": 1, + "versionNonce": 1134, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn68", + "x": 388, + "y": 269.3095238095238, + "width": 67.86, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1135, + "version": 1, + "versionNonce": 1136, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "left lung", + "originalText": "left lung", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn69", + "x": 370, + "y": 291.172619047619, + "width": 105.55999999999999, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1137, + "version": 1, + "versionNonce": 1138, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pleural space", + "originalText": "pleural space", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn70", + "x": 370, + "y": 313.66071428571416, + "width": 89.32, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1139, + "version": 1, + "versionNonce": 1140, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "mediastinum", + "originalText": "mediastinum", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc71", + "x": 377, + "y": 332.41071428571416, + "width": 0, + "height": 12.488095238095184, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1141, + "version": 1, + "versionNonce": 1142, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 12.488095238095184 + ] + ] + }, + { + "type": "line", + "id": "tc72", + "x": 377, + "y": 344.89880952380935, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1143, + "version": 1, + "versionNonce": 1144, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn73", + "x": 388, + "y": 336.77380952380935, + "width": 37.699999999999996, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1145, + "version": 1, + "versionNonce": 1146, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "heart", + "originalText": "heart", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb74", + "x": 354, + "y": 368.14285714285694, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1147, + "version": 1, + "versionNonce": 1148, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt75", + "x": 364, + "y": 370.01785714285694, + "width": 52.779999999999994, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1149, + "version": 1, + "versionNonce": 1150, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "abdomen", + "originalText": "abdomen", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn76", + "x": 370, + "y": 396.63690476190453, + "width": 48.72, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1151, + "version": 1, + "versionNonce": 1152, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "kidney", + "originalText": "kidney", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc77", + "x": 377, + "y": 415.38690476190453, + "width": 0, + "height": 12.488095238095184, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1153, + "version": 1, + "versionNonce": 1154, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 12.488095238095184 + ] + ] + }, + { + "type": "line", + "id": "tc78", + "x": 377, + "y": 427.8749999999997, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1155, + "version": 1, + "versionNonce": 1156, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn79", + "x": 388, + "y": 419.7499999999997, + "width": 82.94, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1157, + "version": 1, + "versionNonce": 1158, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "left kidney", + "originalText": "left kidney", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc80", + "x": 377, + "y": 415.38690476190453, + "width": 0, + "height": 34.97619047619037, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1159, + "version": 1, + "versionNonce": 1160, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 34.97619047619037 + ] + ] + }, + { + "type": "line", + "id": "tc81", + "x": 377, + "y": 450.3630952380949, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1161, + "version": 1, + "versionNonce": 1162, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn82", + "x": 388, + "y": 442.2380952380949, + "width": 90.47999999999999, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1163, + "version": 1, + "versionNonce": 1164, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "right kidney", + "originalText": "right kidney", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc83", + "x": 395, + "y": 460.3630952380949, + "width": 0, + "height": 12.488095238095184, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1165, + "version": 1, + "versionNonce": 1166, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 12.488095238095184 + ] + ] + }, + { + "type": "line", + "id": "tc84", + "x": 395, + "y": 472.8511904761901, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1167, + "version": 1, + "versionNonce": 1168, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn85", + "x": 406, + "y": 464.7261904761901, + "width": 90.47999999999999, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1169, + "version": 1, + "versionNonce": 1170, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "renal pelvis", + "originalText": "renal pelvis", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn86", + "x": 370, + "y": 486.58928571428527, + "width": 40.599999999999994, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1171, + "version": 1, + "versionNonce": 1172, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "liver", + "originalText": "liver", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn87", + "x": 370, + "y": 509.0773809523805, + "width": 48.72, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1173, + "version": 1, + "versionNonce": 1174, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "spleen", + "originalText": "spleen", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb88", + "x": 354, + "y": 541.0714285714281, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1175, + "version": 1, + "versionNonce": 1176, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt89", + "x": 364, + "y": 542.9464285714281, + "width": 30.159999999999997, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1177, + "version": 1, + "versionNonce": 1178, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "head", + "originalText": "head", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn90", + "x": 370, + "y": 569.5654761904757, + "width": 40.599999999999994, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1179, + "version": 1, + "versionNonce": 1180, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "brain", + "originalText": "brain", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc91", + "x": 377, + "y": 588.3154761904757, + "width": 0, + "height": 12.488095238095184, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1181, + "version": 1, + "versionNonce": 1182, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 12.488095238095184 + ] + ] + }, + { + "type": "line", + "id": "tc92", + "x": 377, + "y": 600.8035714285709, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1183, + "version": 1, + "versionNonce": 1184, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn93", + "x": 388, + "y": 592.6785714285709, + "width": 75.39999999999999, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1185, + "version": 1, + "versionNonce": 1186, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "cerebellum", + "originalText": "cerebellum", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc94", + "x": 377, + "y": 588.3154761904757, + "width": 0, + "height": 34.97619047619037, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1187, + "version": 1, + "versionNonce": 1188, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 34.97619047619037 + ] + ] + }, + { + "type": "line", + "id": "tc95", + "x": 377, + "y": 623.2916666666661, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1189, + "version": 1, + "versionNonce": 1190, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn96", + "x": 388, + "y": 615.1666666666661, + "width": 90.47999999999999, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1191, + "version": 1, + "versionNonce": 1192, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "frontal lobe", + "originalText": "frontal lobe", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn97", + "x": 370, + "y": 637.0297619047612, + "width": 40.599999999999994, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1193, + "version": 1, + "versionNonce": 1194, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "orbit", + "originalText": "orbit", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb98", + "x": 354, + "y": 669.0238095238088, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1195, + "version": 1, + "versionNonce": 1196, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt99", + "x": 364, + "y": 670.8988095238088, + "width": 45.239999999999995, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1197, + "version": 1, + "versionNonce": 1198, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pelvis", + "originalText": "pelvis", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn100", + "x": 370, + "y": 697.5178571428564, + "width": 121.8, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1199, + "version": 1, + "versionNonce": 1200, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "urinary bladder", + "originalText": "urinary bladder", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn101", + "x": 370, + "y": 720.0059523809516, + "width": 64.96, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1201, + "version": 1, + "versionNonce": 1202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "prostate", + "originalText": "prostate", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "colblk102", + "x": 354, + "y": 758.0, + "width": 280, + "height": 48.5, + "strokeColor": "#78350f", + "backgroundColor": "#b45309", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1203, + "version": 1, + "versionNonce": 1204, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "colblkt103", + "x": 360, + "y": 776.125, + "width": 268, + "height": 16.25, + "strokeColor": "#fef3c7", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1205, + "version": 1, + "versionNonce": 1206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "layered over: RadLex", + "originalText": "layered over: RadLex", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "col104", + "x": 672, + "y": 0, + "width": 316, + "height": 822.5, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1207, + "version": 1, + "versionNonce": 1208, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "coli105", + "x": 812.0, + "y": 16, + "width": 36, + "height": 36, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1209, + "version": 1, + "versionNonce": 1210, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_coli105", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "colt106", + "x": 680, + "y": 61, + "width": 300, + "height": 21.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1211, + "version": 1, + "versionNonce": 1212, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Exam Types", + "originalText": "Exam Types", + "fontSize": 17, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "coltag107", + "x": 680, + "y": 87.25, + "width": 300, + "height": 17.5, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1213, + "version": 1, + "versionNonce": 1214, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "HOW it was seen", + "originalText": "HOW it was seen", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "rule108", + "x": 690, + "y": 115.75, + "width": 280, + "height": 0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 35, + "angle": 0, + "seed": 1215, + "version": 1, + "versionNonce": 1216, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 280, + 0 + ] + ] + }, + { + "type": "rectangle", + "id": "tb109", + "x": 690, + "y": 127.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1217, + "version": 1, + "versionNonce": 1218, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt110", + "x": 700, + "y": 129.625, + "width": 15.079999999999998, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1219, + "version": 1, + "versionNonce": 1220, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT", + "originalText": "CT", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn111", + "x": 706, + "y": 154.5, + "width": 64.96, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1221, + "version": 1, + "versionNonce": 1222, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest", + "originalText": "CT Chest", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tns112", + "x": 772.1, + "y": 155.125, + "width": 135.72, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1223, + "version": 1, + "versionNonce": 1224, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "(preferred family)", + "originalText": "(preferred family)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc113", + "x": 713, + "y": 173.25, + "width": 0, + "height": 9.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1225, + "version": 1, + "versionNonce": 1226, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 9.0 + ] + ] + }, + { + "type": "line", + "id": "tc114", + "x": 713, + "y": 182.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1227, + "version": 1, + "versionNonce": 1228, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn115", + "x": 724, + "y": 174.125, + "width": 150.79999999999998, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1229, + "version": 1, + "versionNonce": 1230, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest WO contrast", + "originalText": "CT Chest WO contrast", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc116", + "x": 713, + "y": 173.25, + "width": 0, + "height": 28.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1231, + "version": 1, + "versionNonce": 1232, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 28.0 + ] + ] + }, + { + "type": "line", + "id": "tc117", + "x": 713, + "y": 201.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1233, + "version": 1, + "versionNonce": 1234, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn118", + "x": 724, + "y": 193.125, + "width": 165.88, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1235, + "version": 1, + "versionNonce": 1236, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest W contrast IV", + "originalText": "CT Chest W contrast IV", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc119", + "x": 713, + "y": 173.25, + "width": 0, + "height": 47.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1237, + "version": 1, + "versionNonce": 1238, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 47.0 + ] + ] + }, + { + "type": "line", + "id": "tc120", + "x": 713, + "y": 220.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1239, + "version": 1, + "versionNonce": 1240, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn121", + "x": 724, + "y": 212.125, + "width": 218.66, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1241, + "version": 1, + "versionNonce": 1242, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest WO and W contrast IV", + "originalText": "CT Chest WO and W contrast IV", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc122", + "x": 713, + "y": 173.25, + "width": 0, + "height": 66.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1243, + "version": 1, + "versionNonce": 1244, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 66.0 + ] + ] + }, + { + "type": "line", + "id": "tc123", + "x": 713, + "y": 239.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1245, + "version": 1, + "versionNonce": 1246, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn124", + "x": 724, + "y": 231.125, + "width": 226.2, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1247, + "version": 1, + "versionNonce": 1248, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Lung parenchyma WO contrast", + "originalText": "CT Lung parenchyma WO contrast", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc125", + "x": 713, + "y": 173.25, + "width": 0, + "height": 85.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1249, + "version": 1, + "versionNonce": 1250, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 85.0 + ] + ] + }, + { + "type": "line", + "id": "tc126", + "x": 713, + "y": 258.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1251, + "version": 1, + "versionNonce": 1252, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn127", + "x": 724, + "y": 250.125, + "width": 128.17999999999998, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1253, + "version": 1, + "versionNonce": 1254, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CTA Chest vessels", + "originalText": "CTA Chest vessels", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb128", + "x": 690, + "y": 279.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1255, + "version": 1, + "versionNonce": 1256, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt129", + "x": 700, + "y": 281.625, + "width": 15.079999999999998, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1257, + "version": 1, + "versionNonce": 1258, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MR", + "originalText": "MR", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn130", + "x": 706, + "y": 306.5, + "width": 56.839999999999996, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1259, + "version": 1, + "versionNonce": 1260, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MR Knee", + "originalText": "MR Knee", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tns131", + "x": 771.4, + "y": 307.125, + "width": 135.72, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1261, + "version": 1, + "versionNonce": 1262, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "(preferred family)", + "originalText": "(preferred family)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc132", + "x": 713, + "y": 325.25, + "width": 0, + "height": 9.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1263, + "version": 1, + "versionNonce": 1264, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 9.0 + ] + ] + }, + { + "type": "line", + "id": "tc133", + "x": 713, + "y": 334.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1265, + "version": 1, + "versionNonce": 1266, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn134", + "x": 724, + "y": 326.125, + "width": 143.26, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1267, + "version": 1, + "versionNonce": 1268, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MR Knee WO contrast", + "originalText": "MR Knee WO contrast", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc135", + "x": 713, + "y": 325.25, + "width": 0, + "height": 28.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1269, + "version": 1, + "versionNonce": 1270, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 28.0 + ] + ] + }, + { + "type": "line", + "id": "tc136", + "x": 713, + "y": 353.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1271, + "version": 1, + "versionNonce": 1272, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn137", + "x": 724, + "y": 345.125, + "width": 158.33999999999997, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1273, + "version": 1, + "versionNonce": 1274, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MR Knee W contrast IV", + "originalText": "MR Knee W contrast IV", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn138", + "x": 706, + "y": 369.5, + "width": 64.96, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1275, + "version": 1, + "versionNonce": 1276, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MR Brain", + "originalText": "MR Brain", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tns139", + "x": 771.3, + "y": 370.125, + "width": 135.72, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1277, + "version": 1, + "versionNonce": 1278, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "(preferred family)", + "originalText": "(preferred family)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc140", + "x": 713, + "y": 388.25, + "width": 0, + "height": 9.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1279, + "version": 1, + "versionNonce": 1280, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 9.0 + ] + ] + }, + { + "type": "line", + "id": "tc141", + "x": 713, + "y": 397.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1281, + "version": 1, + "versionNonce": 1282, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn142", + "x": 724, + "y": 389.125, + "width": 150.79999999999998, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1283, + "version": 1, + "versionNonce": 1284, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MR Brain WO contrast", + "originalText": "MR Brain WO contrast", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc143", + "x": 713, + "y": 388.25, + "width": 0, + "height": 28.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1285, + "version": 1, + "versionNonce": 1286, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 28.0 + ] + ] + }, + { + "type": "line", + "id": "tc144", + "x": 713, + "y": 416.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1287, + "version": 1, + "versionNonce": 1288, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn145", + "x": 724, + "y": 408.125, + "width": 218.66, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1289, + "version": 1, + "versionNonce": 1290, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "MR Brain WO and W contrast IV", + "originalText": "MR Brain WO and W contrast IV", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb146", + "x": 690, + "y": 437.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1291, + "version": 1, + "versionNonce": 1292, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt147", + "x": 700, + "y": 439.625, + "width": 15.079999999999998, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1293, + "version": 1, + "versionNonce": 1294, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "XR", + "originalText": "XR", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn148", + "x": 706, + "y": 464.5, + "width": 56.839999999999996, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1295, + "version": 1, + "versionNonce": 1296, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "XR Knee", + "originalText": "XR Knee", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tns149", + "x": 769.0, + "y": 465.125, + "width": 135.72, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1297, + "version": 1, + "versionNonce": 1298, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "(preferred family)", + "originalText": "(preferred family)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc150", + "x": 713, + "y": 483.25, + "width": 0, + "height": 9.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1299, + "version": 1, + "versionNonce": 1300, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 9.0 + ] + ] + }, + { + "type": "line", + "id": "tc151", + "x": 713, + "y": 492.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1301, + "version": 1, + "versionNonce": 1302, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn152", + "x": 724, + "y": 484.125, + "width": 113.1, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1303, + "version": 1, + "versionNonce": 1304, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "XR Knee 2 Views", + "originalText": "XR Knee 2 Views", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc153", + "x": 713, + "y": 483.25, + "width": 0, + "height": 28.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1305, + "version": 1, + "versionNonce": 1306, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 28.0 + ] + ] + }, + { + "type": "line", + "id": "tc154", + "x": 713, + "y": 511.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1307, + "version": 1, + "versionNonce": 1308, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn155", + "x": 724, + "y": 503.125, + "width": 113.1, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1309, + "version": 1, + "versionNonce": 1310, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "XR Knee 3 Views", + "originalText": "XR Knee 3 Views", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn156", + "x": 706, + "y": 527.5, + "width": 64.96, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1311, + "version": 1, + "versionNonce": 1312, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "XR Chest", + "originalText": "XR Chest", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tns157", + "x": 772.9, + "y": 528.125, + "width": 135.72, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1313, + "version": 1, + "versionNonce": 1314, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "(preferred family)", + "originalText": "(preferred family)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc158", + "x": 713, + "y": 546.25, + "width": 0, + "height": 9.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1315, + "version": 1, + "versionNonce": 1316, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 9.0 + ] + ] + }, + { + "type": "line", + "id": "tc159", + "x": 713, + "y": 555.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1317, + "version": 1, + "versionNonce": 1318, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn160", + "x": 724, + "y": 547.125, + "width": 120.63999999999999, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1319, + "version": 1, + "versionNonce": 1320, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "XR Chest 2 Views", + "originalText": "XR Chest 2 Views", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc161", + "x": 713, + "y": 546.25, + "width": 0, + "height": 28.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1321, + "version": 1, + "versionNonce": 1322, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 28.0 + ] + ] + }, + { + "type": "line", + "id": "tc162", + "x": 713, + "y": 574.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1323, + "version": 1, + "versionNonce": 1324, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn163", + "x": 724, + "y": 566.125, + "width": 173.42, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1325, + "version": 1, + "versionNonce": 1326, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "XR Chest PA and Lateral", + "originalText": "XR Chest PA and Lateral", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "tb164", + "x": 690, + "y": 595.75, + "width": 280, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#fcd34d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1327, + "version": 1, + "versionNonce": 1328, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "tbt165", + "x": 700, + "y": 597.625, + "width": 15.079999999999998, + "height": 16.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1329, + "version": 1, + "versionNonce": 1330, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "US", + "originalText": "US", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn166", + "x": 706, + "y": 622.5, + "width": 81.19999999999999, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1331, + "version": 1, + "versionNonce": 1332, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "US Abdomen", + "originalText": "US Abdomen", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tns167", + "x": 796.3, + "y": 623.125, + "width": 135.72, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1333, + "version": 1, + "versionNonce": 1334, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "(preferred family)", + "originalText": "(preferred family)", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc168", + "x": 713, + "y": 641.25, + "width": 0, + "height": 9.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1335, + "version": 1, + "versionNonce": 1336, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 9.0 + ] + ] + }, + { + "type": "line", + "id": "tc169", + "x": 713, + "y": 650.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1337, + "version": 1, + "versionNonce": 1338, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn170", + "x": 724, + "y": 642.125, + "width": 135.72, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1339, + "version": 1, + "versionNonce": 1340, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "US Abdomen limited", + "originalText": "US Abdomen limited", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "line", + "id": "tc171", + "x": 713, + "y": 641.25, + "width": 0, + "height": 28.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1341, + "version": 1, + "versionNonce": 1342, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 28.0 + ] + ] + }, + { + "type": "line", + "id": "tc172", + "x": 713, + "y": 669.25, + "width": 7, + "height": 0.0, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 45, + "angle": 0, + "seed": 1343, + "version": 1, + "versionNonce": 1344, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 0.0 + ] + ] + }, + { + "type": "text", + "id": "tn173", + "x": 724, + "y": 661.125, + "width": 105.55999999999999, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1345, + "version": 1, + "versionNonce": 1346, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "US Abdomen RUQ", + "originalText": "US Abdomen RUQ", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "tn174", + "x": 706, + "y": 685.5, + "width": 129.92, + "height": 17.5, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1347, + "version": 1, + "versionNonce": 1348, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "US Thyroid gland", + "originalText": "US Thyroid gland", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "coln175", + "x": 690, + "y": 723.75, + "width": 241.27999999999997, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1349, + "version": 1, + "versionNonce": 1350, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "each a LOINC/RSNA Playbook entry", + "originalText": "each a LOINC/RSNA Playbook entry", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "colblk176", + "x": 690, + "y": 758.0, + "width": 280, + "height": 48.5, + "strokeColor": "#78350f", + "backgroundColor": "#b45309", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1351, + "version": 1, + "versionNonce": 1352, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "colblkt177", + "x": 696, + "y": 768.0, + "width": 268, + "height": 16.25, + "strokeColor": "#fef3c7", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1353, + "version": 1, + "versionNonce": 1354, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "layered over: LOINC/RSNA", + "originalText": "layered over: LOINC/RSNA", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "colblkt178", + "x": 696, + "y": 784.25, + "width": 268, + "height": 16.25, + "strokeColor": "#fef3c7", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1355, + "version": 1, + "versionNonce": 1356, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Playbook", + "originalText": "Playbook", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": { + "img_coli2": { + "mimeType": "image/svg+xml", + "id": "img_coli2", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiNiNDUzMDkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyLjU4NiAyLjU4NkEyIDIgMCAwIDAgMTEuMTcyIDJINGEyIDIgMCAwIDAtMiAydjcuMTcyYTIgMiAwIDAgMCAuNTg2IDEuNDE0bDguNzA0IDguNzA0YTIuNDI2IDIuNDI2IDAgMCAwIDMuNDIgMGw2LjU4LTYuNThhMi40MjYgMi40MjYgMCAwIDAgMC0zLjQyeiIgLz4KICA8Y2lyY2xlIGN4PSI3LjUiIGN5PSI3LjUiIHI9Ii41IiBmaWxsPSIjYjQ1MzA5IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_coli47": { + "mimeType": "image/svg+xml", + "id": "img_coli47", + "dataURL": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMyA2LjAwMDEzQzIzIDYuMDAwMDQgMjMgNiAyNCA2SDI0LjE3NjFDMjQuMjUzMyA2IDI0LjMyMzMgNiAyNC4zODY4IDZDMjUgNi4wMDAwMSAyNSA2LjAwMDA1IDI1IDYuMDAwMTNWMjAuNUMyNSAyMS44OTc3IDI1LjU2NDEgMjIuNDQyMSAyNi4wMDM5IDIyLjY5ODdDMjYuMDUzIDIyLjcyNzMgMjYuMTAyNCAyMi43NTMzIDI2LjE1MTYgMjIuNzc3QzI2LjE1MDYgMjIuNjU2OCAyNi4xNDkzIDIyLjUzMTYgMjYuMTQ3OSAyMi40MDE5TDI2LjE0NzkgMjIuNDAwM0MyNi4xMTAxIDE4Ljg3MTYgMjYuMDM2NSAxMS45OTk3IDMxLjQ3NTMgMTEuOTk5N0MzOC45MjU5IDExLjk5OTcgNDQuMjQ3OCAzOC4wMDYyIDQxLjA1NDcgNDAuNjU5OUMzNy44NjE1IDQzLjMxMzYgMzEuNDc1MyA0MS43MjE0IDI4LjI4MjIgMzguNTM2OUMyNS43MDQyIDM1Ljk2NTkgMjUuOTI3MiAyOS45NTgzIDI2LjA4NzMgMjUuNjQ1N0wyNi4wODc0IDI1LjY0NDNDMjYuMDk3MSAyNS4zODE5IDI2LjEwNjcgMjUuMTI1OCAyNi4xMTUzIDI0Ljg3NzFDMjUuNzkgMjQuNzk2MiAyNS4zOTU5IDI0LjY1OTUgMjQuOTk2MiAyNC40MjYzQzI0LjYzOTggMjQuMjE4NCAyNC4yOTc1IDIzLjk0NDIgMjQgMjMuNTkxOUMyMy43MDI2IDIzLjk0NDIgMjMuMzYwMyAyNC4yMTg0IDIzLjAwMzkgMjQuNDI2M0MyMi42MDQxIDI0LjY1OTUgMjIuMjEwMSAyNC43OTYyIDIxLjg4NDggMjQuODc3MUMyMS44OTM0IDI1LjEyNjEgMjEuOTAyOSAyNS4zODI2IDIxLjkxMjcgMjUuNjQ1M0MyMi4wNzI4IDI5Ljk1NzkgMjIuMjk1OSAzNS45NjYyIDE5LjcxNzkgMzguNTM3MkMxNi41MjQ3IDQxLjcyMTcgMTAuMTM4NSA0My4zMTM5IDYuOTQ1MzYgNDAuNjYwMkMzLjc1MjIyIDM4LjAwNjUgOS4wNzQxMyAxMiAxNi41MjQ3IDEyQzIxLjk2MzYgMTIgMjEuODkgMTguODcxNyAyMS44NTIyIDIyLjQwMDNDMjEuODUyIDIyLjQxNjYgMjEuODUxOCAyMi40MzI4IDIxLjg1MTYgMjIuNDQ4OUMyMS44NTA0IDIyLjU2MTkgMjEuODQ5MyAyMi42NzE0IDIxLjg0ODQgMjIuNzc3QzIxLjg5NzcgMjIuNzUzNCAyMS45NDcxIDIyLjcyNzQgMjEuOTk2MiAyMi42OTg3QzIyLjQzNiAyMi40NDIxIDIzIDIxLjg5NzcgMjMgMjAuNVY2LjAwMDEzWk0zMS40NzUzIDEzLjk5OTdDMzAuNTIxMiAxMy45OTk3IDI5Ljk4NDMgMTQuMzAwMiAyOS42MDg2IDE0LjY4NjlDMjkuMTc1MiAxNS4xMzMxIDI4LjgxMzIgMTUuODUwNyAyOC41NTkgMTYuODY0NkMyOC4xMTQxIDE4LjYzOTUgMjguMTMzIDIwLjY4MTkgMjguMTQ4MyAyMi4zMjkzQzI4LjE1MDkgMjIuNjE0MiAyOC4xNTM0IDIyLjg4NzQgMjguMTUzNCAyMy4xNDUzQzI4LjE1MzQgMjMuOTA1MyAyOC4xMTk2IDI0LjgxNTMgMjguMDg0NSAyNS43NjIxQzI4LjA3MDcgMjYuMTM0OSAyOC4wNTY2IDI2LjUxMzMgMjguMDQ0MyAyNi44OTA1QzI3Ljk5OTYgMjguMjY1NyAyNy45NzQxIDI5LjcwNTUgMjguMDQwNSAzMS4wOTgyQzI4LjEwNzIgMzIuNDk4NSAyOC4yNjQyIDMzLjc4MzUgMjguNTU3MyAzNC44NTk2QzI4Ljg1MzggMzUuOTQ4MyAyOS4yNTA5IDM2LjY3ODQgMjkuNjk0NCAzNy4xMjA4QzMwLjk3MjYgMzguMzk1NCAzMi45OTA4IDM5LjQyNTEgMzUuMDQ5MiAzOS44MjQzQzM3LjA4NDIgNDAuMjE4OSAzOC43MjI0IDM5LjkyNjkgMzkuNjgxOCAzOS4xOTY5QzM5LjY4NjggMzkuMTg1NyAzOS42OTIzIDM5LjE3MjYgMzkuNjk4NCAzOS4xNTc0QzM5Ljc1NzYgMzkuMDA5NSAzOS44Mjk0IDM4Ljc1NTQgMzkuODg3OCAzOC4zNjgzQzQwLjAwNDcgMzcuNTkzMyA0MC4wMzUgMzYuNTEyMiAzOS45NTYyIDM1LjE4MDNDMzkuNzk5NSAzMi41MzA1IDM5LjIyODYgMjkuMTY4OSAzOC4zMjY4IDI1Ljg2MDZDMzcuNDIxNiAyMi41Mzk2IDM2LjIxNzMgMTkuMzk5OCAzNC44NTIxIDE3LjEzODNDMzQuMTY4OCAxNi4wMDYyIDMzLjQ5NTEgMTUuMTc4NiAzMi44NzAzIDE0LjY1MzVDMzIuMjUxOCAxNC4xMzM3IDMxLjc5MzUgMTMuOTk5NyAzMS40NzUzIDEzLjk5OTdaTTE2LjUyNDcgMTRDMTcuNDc4OCAxNCAxOC4wMTU4IDE0LjMwMDUgMTguMzkxNCAxNC42ODcyQzE4LjgyNDggMTUuMTMzNCAxOS4xODY4IDE1Ljg1MSAxOS40NDEgMTYuODY0OUMxOS44ODU5IDE4LjYzOTkgMTkuODY3IDIwLjY4MjIgMTkuODUxOCAyMi4zMjk2QzE5Ljg0OTEgMjIuNjE0NiAxOS44NDY2IDIyLjg4NzcgMTkuODQ2NiAyMy4xNDU2QzE5Ljg0NjYgMjMuOTA1NiAxOS44ODA0IDI0LjgxNTYgMTkuOTE1NSAyNS43NjI0QzE5LjkyOTQgMjYuMTM1IDE5Ljk0MzQgMjYuNTEzOCAxOS45NTU3IDI2Ljg5MDhDMjAuMDAwNSAyOC4yNjYgMjAuMDI1OSAyOS43MDU4IDE5Ljk1OTYgMzEuMDk4NUMxOS44OTI5IDMyLjQ5ODggMTkuNzM1OSAzMy43ODM4IDE5LjQ0MjcgMzQuODU5OUMxOS4xNDYyIDM1Ljk0ODYgMTguNzQ5MiAzNi42Nzg3IDE4LjMwNTYgMzcuMTIxMUMxNy4wMjc1IDM4LjM5NTcgMTUuMDA5MyAzOS40MjU0IDEyLjk1MDkgMzkuODI0NkMxMC45MTU5IDQwLjIxOTIgOS4yNzc2OCAzOS45MjcyIDguMzE4MjMgMzkuMTk3MkM4LjMxMzI2IDM5LjE4NiA4LjMwNzcgMzkuMTcyOSA4LjMwMTYyIDM5LjE1NzdDOC4yNDI0MSAzOS4wMDk4IDguMTcwNjEgMzguNzU1NyA4LjExMjI0IDM4LjM2ODZDNy45OTUzOSAzNy41OTM2IDcuOTY1IDM2LjUxMjUgOC4wNDM4IDM1LjE4MDZDOC4yMDA1NiAzMi41MzA4IDguNzcxNDcgMjkuMTY5MiA5LjY3MzI0IDI1Ljg2MDlDMTAuNTc4NSAyMi41Mzk5IDExLjc4MjcgMTkuNDAwMSAxMy4xNDc5IDE3LjEzODZDMTMuODMxMyAxNi4wMDY1IDE0LjUwNDkgMTUuMTc4OSAxNS4xMjk4IDE0LjY1MzhDMTUuNzQ4MiAxNC4xMzQgMTYuMjA2NSAxNCAxNi41MjQ3IDE0WiIgZmlsbD0iI2I0NTMwOSIvPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_coli105": { + "mimeType": "image/svg+xml", + "id": "img_coli105", + "dataURL": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yOSA5QzI5IDExLjc2MTQgMjYuNzYxNCAxNCAyNCAxNEMyMS4yMzg2IDE0IDE5IDExLjc2MTQgMTkgOUMxOSA2LjIzODU4IDIxLjIzODYgNCAyNCA0QzI2Ljc2MTQgNCAyOSA2LjIzODU4IDI5IDlaTTI3IDlDMjcgMTAuNjU2OSAyNS42NTY5IDEyIDI0IDEyQzIyLjM0MzEgMTIgMjEgMTAuNjU2OSAyMSA5QzIxIDcuMzQzMTUgMjIuMzQzMSA2IDI0IDZDMjUuNjU2OSA2IDI3IDcuMzQzMTUgMjcgOVoiIGZpbGw9IiNiNDUzMDkiLz4KPHBhdGggZD0iTTE2Ljk5OTggNDFWMzRIMTguOTk5OFY0MUMxOC45OTk4IDQxLjU1MjMgMTkuNDQ3NSA0MiAxOS45OTk4IDQySDIwLjA4NjVDMjAuNjAzNyA0MiAyMS4wMzU2IDQxLjYwNTYgMjEuMDgyNCA0MS4wOTA1TDIxLjcyNyAzNEgyMy43MzUzTDIzLjA3NDIgNDEuMjcxNkMyMy4wNjkgNDEuMzI4OCAyMy4wNjIyIDQxLjM4NTYgMjMuMDUzOSA0MS40NDE3QzIzLjAzNTcgNDEuNTY0NyAyMy4wMTAxIDQxLjY4NTEgMjIuOTc3NiA0MS44MDI1QzIyLjYyNDIgNDMuMDc5OCAyMS40NTQ5IDQ0IDIwLjA4NjUgNDRIMTkuOTk5OEMxOC4zNDI5IDQ0IDE2Ljk5OTggNDIuNjU2OSAxNi45OTk4IDQxWiIgZmlsbD0iI2I0NTMwOSIvPgo8cGF0aCBkPSJNMjYuOTE3MSA0MS4wOTA1TDI2LjI3MjUgMzRIMjQuMjY0MkwyNC45MjUzIDQxLjI3MTZDMjQuOTMwNSA0MS4zMjg4IDI0LjkzNzMgNDEuMzg1NiAyNC45NDU2IDQxLjQ0MTdDMjQuOTYzOCA0MS41NjQ3IDI0Ljk4OTQgNDEuNjg1MSAyNS4wMjE5IDQxLjgwMjVDMjUuMzc1MyA0My4wNzk4IDI2LjU0NDYgNDQgMjcuOTEzIDQ0SDI3Ljk5OThDMjkuNjU2NiA0NCAzMC45OTk4IDQyLjY1NjkgMzAuOTk5OCA0MVYzNEgyOC45OTk4VjQxQzI4Ljk5OTggNDEuNTUyMyAyOC41NTIgNDIgMjcuOTk5OCA0MkgyNy45MTNDMjcuMzk1OCA0MiAyNi45NjM5IDQxLjYwNTYgMjYuOTE3MSA0MS4wOTA1WiIgZmlsbD0iI2I0NTMwOSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTExIDE1QzEwLjQ0NzcgMTUgMTAgMTUuNDQ3NyAxMCAxNlYzMUMxMCAzMS41NTIzIDEwLjQ0NzcgMzIgMTEgMzJIMzdDMzcuNTUyMyAzMiAzOCAzMS41NTIzIDM4IDMxVjE2QzM4IDE1LjQ0NzcgMzcuNTUyMyAxNSAzNyAxNUgxMVpNMTIgMTdWMzBIMTkuNUMxOS41IDI5LjQ0MjQgMTkuNzU5MSAyOC45NTcyIDIwLjAyNzYgMjguNjAwNkMyMC4zMDg2IDI4LjIyNzYgMjAuNjc5NiAyNy44ODgzIDIxLjA3OTQgMjcuNjAxNkMyMS42MjUyIDI3LjIxMDMgMjIuMjk4NiAyNi44NjUgMjMgMjYuNjY3OFYyNC41SDIyLjJDMjEuNTk3MyAyNC41IDIwLjc1MzUgMjQuODY0MiAxOS45NDMzIDI1LjM1NTJDMTkuNTYzOCAyNS41ODUyIDE5LjIzMzcgMjUuODE2NyAxOC45OTg0IDI1Ljk5MTFDMTguODgxMiAyNi4wNzc4IDE4Ljc4ODggMjYuMTQ5NSAxOC43MjcgMjYuMTk4NEMxOC42OTYxIDI2LjIyMjggMTguNjczIDI2LjI0MTUgMTguNjU4MiAyNi4yNTM1TDE4LjY0MjYgMjYuMjY2M0wxOC42Mzk5IDI2LjI2ODRDMTguNjM5OSAyNi4yNjg0IDE4LjY0MDIgMjYuMjY4MiAxOCAyNS41QzE3LjM1OTggMjQuNzMxOCAxNy4zNjAxIDI0LjczMTUgMTcuMzYwMSAyNC43MzE1TDE3LjM2MTQgMjQuNzMwNUwxNy4zNjM4IDI0LjcyODVMMTcuMzcxMyAyNC43MjIzTDE3LjM5NjYgMjQuNzAxNkMxNy40MTc5IDI0LjY4NDMgMTcuNDQ4IDI0LjY2IDE3LjQ4NjMgMjQuNjI5N0MxNy41NjI3IDI0LjU2OTMgMTcuNjcxOSAyNC40ODQ3IDE3LjgwNzkgMjQuMzgzOUMxOC4wNzg4IDI0LjE4MzMgMTguNDYxMiAyMy45MTQ4IDE4LjkwNjcgMjMuNjQ0OEMxOS43NDY1IDIzLjEzNTggMjEuMDAyNyAyMi41IDIyLjIgMjIuNUgyM1YyMC41SDIyLjVDMjEuOTMwOSAyMC41IDIxLjE5MDkgMjAuNjk5MiAyMC41MjkyIDIwLjkzOThDMjAuMjEyIDIxLjA1NTIgMTkuOTM3IDIxLjE3MDkgMTkuNzQyMSAyMS4yNTc2QzE5LjY0NDkgMjEuMzAwNyAxOS41Njg1IDIxLjMzNjMgMTkuNTE3NiAyMS4zNjA1QzE5LjQ5MjIgMjEuMzcyNSAxOS40NzMyIDIxLjM4MTcgMTkuNDYxMyAyMS4zODc1TDE5LjQ0ODcgMjEuMzkzN0wxOS40NDcgMjEuMzk0NUMxOS40NDcgMjEuMzk0NSAxOS40NDcyIDIxLjM5NDQgMTkgMjAuNUMxOC41NTI4IDE5LjYwNTYgMTguNTUzMSAxOS42MDU0IDE4LjU1MzEgMTkuNjA1NEwxOC41NTY0IDE5LjYwMzhMMTguNTYyOSAxOS42MDA2TDE4LjU4NDQgMTkuNTlDMTguNjAyNSAxOS41ODEyIDE4LjYyNzkgMTkuNTY4OSAxOC42NjAxIDE5LjU1MzZDMTguNzI0NSAxOS41MjMxIDE4LjgxNiAxOS40ODA1IDE4LjkyOTggMTkuNDI5OUMxOS4xNTY3IDE5LjMyOTEgMTkuNDc1NSAxOS4xOTQ4IDE5Ljg0NTggMTkuMDYwMkMyMC41NTkxIDE4LjgwMDggMjEuNTY5MSAxOC41IDIyLjUgMTguNUgyM1YxN0gxMlpNMjUgMjQuNUgyNi4xQzI2Ljg0ODkgMjQuNSAyNy42MTg2IDI0Ljg2NjMgMjguMjYzMSAyNS4zMTg2QzI4LjU3MzUgMjUuNTM2NCAyOC44Mjg1IDI1Ljc1NTYgMjkuMDA0OCAyNS45MTk3QzI5LjA5MjUgMjYuMDAxMiAyOS4xNTk0IDI2LjA2NzkgMjkuMjAyNiAyNi4xMTIyQzI5LjIyNDEgMjYuMTM0MyAyOS4yMzk3IDI2LjE1MDcgMjkuMjQ4OCAyNi4xNjA1TDI5LjI1NzQgMjYuMTY5N0MyOS4yNTc0IDI2LjE2OTcgMjkuMjU2NyAyNi4xNjkgMzAgMjUuNUMzMC43NDMzIDI0LjgzMSAzMC43NDI5IDI0LjgzMDYgMzAuNzQyOSAyNC44MzA2TDMwLjc0MTQgMjQuODI5TDMwLjczODkgMjQuODI2MkwzMC43MzE4IDI0LjgxODRMMzAuNzA5NyAyNC43OTQ2QzMwLjY5MTUgMjQuNzc1MSAzMC42NjYzIDI0Ljc0ODUgMzAuNjM0NiAyNC43MTZDMzAuNTcxMSAyNC42NTA5IDMwLjQ4MDkgMjQuNTYxMyAzMC4zNjcgMjQuNDU1M0MzMC4xNDAzIDI0LjI0NDQgMjkuODE0IDIzLjk2MzYgMjkuNDExOSAyMy42ODE0QzI4LjYzMTQgMjMuMTMzNyAyNy40NTExIDIyLjUgMjYuMSAyMi41SDI1VjIwLjVIMjUuNzVDMjYuNDQ0MyAyMC41IDI3LjExOTMgMjAuNzAxMSAyNy42NDMyIDIwLjkyMTZDMjcuOTAwMyAyMS4wMjk5IDI4LjEwOTIgMjEuMTM3OCAyOC4yNTA5IDIxLjIxNjlDMjguMzIxNSAyMS4yNTY0IDI4LjM3NDcgMjEuMjg4MiAyOC40MDggMjEuMzA4N0MyOC40MjQ3IDIxLjMxODkgMjguNDM2MyAyMS4zMjYzIDI4LjQ0MjcgMjEuMzMwNEwyOC40NDU3IDIxLjMzMjNDMjguNDQ1NyAyMS4zMzIzIDI4LjQ0NTMgMjEuMzMyMSAyOSAyMC41QzI5LjU1NDcgMTkuNjY3OSAyOS41NTQyIDE5LjY2NzYgMjkuNTU0MiAxOS42Njc2TDI5LjU1MjcgMTkuNjY2NkwyOS41NTAxIDE5LjY2NDlMMjkuNTQzMiAxOS42NjA0TDI5LjUyMjggMTkuNjQ3MkMyOS41MDYzIDE5LjYzNjYgMjkuNDg0IDE5LjYyMjUgMjkuNDU2MiAxOS42MDU0QzI5LjQwMDcgMTkuNTcxMiAyOS4zMjMgMTkuNTI0OSAyOS4yMjU2IDE5LjQ3MDZDMjkuMDMxNSAxOS4zNjIyIDI4Ljc1NTkgMTkuMjIwMSAyOC40MTkzIDE5LjA3ODRDMjcuNzU1NyAxOC43OTg5IDI2LjgwNTcgMTguNSAyNS43NSAxOC41SDI1VjE3SDM2VjMwSDI4LjVDMjguNSAyOS40Mzc5IDI4LjIzMjQgMjguOTQ1MiAyNy45NjQxIDI4LjU4NzVDMjcuNjgwMyAyOC4yMDkyIDI3LjMwNTQgMjcuODYwNiAyNi45MDA3IDI3LjU2NjNDMjYuMzY3NSAyNy4xNzg1IDI1LjY5ODkgMjYuODI1NyAyNSAyNi42MzkxVjI0LjVaTTI2LjQ5MjkgMzBDMjYuNDc5NyAyOS45NjcgMjYuNDQ1NyAyOS44OTY0IDI2LjM2NDEgMjkuNzg3NUMyNi4yMjU5IDI5LjYwMzMgMjYuMDA3MSAyOS4zODk0IDI1LjcyNDMgMjkuMTgzN0MyNS4xNDc4IDI4Ljc2NDUgMjQuNDkzNyAyOC41MTE3IDI0LjAzMiAyOC41MDA0QzIzLjQ4NzcgMjguNTQ2NCAyMi44MDUyIDI4LjgyNTIgMjIuMjQ0OSAyOS4yMjdDMjEuOTY1NyAyOS40MjcyIDIxLjc1NTYgMjkuNjMwNyAyMS42MjUyIDI5LjgwMzhDMjEuNTQ5NiAyOS45MDQzIDIxLjUxODQgMjkuOTY5NCAyMS41MDYzIDMwSDI2LjQ5MjlaIiBmaWxsPSIjYjQ1MzA5Ii8+Cjwvc3ZnPgo=", + "created": 1700000000000 + } + } +} \ No newline at end of file diff --git a/tools/diagrams/excalidraw/two-collections.excalidraw b/tools/diagrams/excalidraw/two-collections.excalidraw new file mode 100644 index 0000000..fb52a3f --- /dev/null +++ b/tools/diagrams/excalidraw/two-collections.excalidraw @@ -0,0 +1,1662 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "rectangle", + "id": "panel_oifm", + "x": 0, + "y": 0, + "width": 364, + "height": 272, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "rectangle", + "id": "panel_cde", + "x": 536, + "y": 0, + "width": 364, + "height": 272, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "hicon1", + "x": 18, + "y": 16, + "width": 26, + "height": 26, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_hicon1", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "ht2", + "x": 54, + "y": 18, + "width": 226.8, + "height": 21.25, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Open Imaging Finding Models", + "originalText": "Open Imaging Finding Models", + "fontSize": 17, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hs3", + "x": 54, + "y": 50, + "width": 143.8, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "inclusive \u00b7 beta channel \u00b7", + "originalText": "inclusive \u00b7 beta channel \u00b7", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hs4", + "x": 54, + "y": 66.25, + "width": 159.7, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "rapid additions and updates", + "originalText": "rapid additions and updates", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hs5", + "x": 54, + "y": 89.5, + "width": 182.8, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "maintained by the OIDM project", + "originalText": "maintained by the OIDM project", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "image", + "id": "hicon6", + "x": 554, + "y": 16, + "width": 26, + "height": 26, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_hicon6", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "ht7", + "x": 590, + "y": 18, + "width": 277.8, + "height": 21.25, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "ACR/RSNA Common Data Elements", + "originalText": "ACR/RSNA Common Data Elements", + "fontSize": 17, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hs8", + "x": 590, + "y": 50, + "width": 228.3, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "well-reviewed \u00b7 release channel \u00b7 stable", + "originalText": "well-reviewed \u00b7 release channel \u00b7 stable", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hs9", + "x": 590, + "y": 73.25, + "width": 186.4, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "American College of Radiology \u00b7", + "originalText": "American College of Radiology \u00b7", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hs10", + "x": 590, + "y": 89.5, + "width": 228.2, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Radiological Society of North America \u00b7", + "originalText": "Radiological Society of North America \u00b7", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hs11", + "x": 590, + "y": 105.75, + "width": 87.4, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "radelement.org", + "originalText": "radelement.org", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "n_hydro", + "x": 18, + "y": 144, + "width": 108.9, + "height": 40, + "strokeColor": "#059669", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "n_hydro_t", + "x": 26, + "y": 155.875, + "width": 92.9, + "height": 16.25, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "hydronephrosis", + "originalText": "hydronephrosis", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "n_calc", + "x": 140.9, + "y": 144, + "width": 99.5, + "height": 40, + "strokeColor": "#059669", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "n_calc_t", + "x": 148.9, + "y": 155.875, + "width": 83.5, + "height": 16.25, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "renal calculus", + "originalText": "renal calculus", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "n_nodule_l", + "x": 222.7, + "y": 200, + "width": 123.3, + "height": 40, + "strokeColor": "#059669", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "n_nodule_l_t", + "x": 230.7, + "y": 211.875, + "width": 107.3, + "height": 16.25, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary nodule", + "originalText": "pulmonary nodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "n_aaa", + "x": 554, + "y": 144, + "width": 176.1, + "height": 40, + "strokeColor": "#059669", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "n_aaa_t", + "x": 562, + "y": 155.875, + "width": 160.1, + "height": 16.25, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "abdominal aortic aneurysm", + "originalText": "abdominal aortic aneurysm", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "n_nodule_r", + "x": 554, + "y": 200, + "width": 123.3, + "height": 40, + "strokeColor": "#059669", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "n_nodule_r_t", + "x": 562, + "y": 211.875, + "width": 107.3, + "height": 16.25, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary nodule", + "originalText": "pulmonary nodule", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bl_grad12", + "x": 381.7, + "y": 28, + "width": 136.6, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "definitions proved out", + "originalText": "definitions proved out", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bl_grad13", + "x": 381.7, + "y": 44.25, + "width": 136.6, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "here graduate to review", + "originalText": "here graduate to review", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "b_grad", + "x": 368, + "y": 72, + "width": 159.04999999999995, + "height": 0.0, + "strokeColor": "#059669", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 159.04999999999995, + 0.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "b_grad_h", + "x": 532, + "y": 72, + "width": 0, + "height": 0, + "strokeColor": "#059669", + "backgroundColor": "#059669", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -9.0, + 5.579999999999998 + ], + [ + -9.0, + -5.579999999999998 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "ramp14", + "x": 368.0, + "y": 86, + "width": 23.928571428571427, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#ecfdf5", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1055, + "version": 1, + "versionNonce": 1056, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "rectangle", + "id": "ramp15", + "x": 391.42857142857144, + "y": 86, + "width": 23.928571428571427, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1057, + "version": 1, + "versionNonce": 1058, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "rectangle", + "id": "ramp16", + "x": 414.85714285714283, + "y": 86, + "width": 23.928571428571427, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1059, + "version": 1, + "versionNonce": 1060, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "rectangle", + "id": "ramp17", + "x": 438.2857142857143, + "y": 86, + "width": 23.928571428571427, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#6ee7b7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1061, + "version": 1, + "versionNonce": 1062, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "rectangle", + "id": "ramp18", + "x": 461.7142857142857, + "y": 86, + "width": 23.928571428571427, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#34d399", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1063, + "version": 1, + "versionNonce": 1064, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "rectangle", + "id": "ramp19", + "x": 485.1428571428571, + "y": 86, + "width": 23.928571428571427, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#10b981", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1065, + "version": 1, + "versionNonce": 1066, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "rectangle", + "id": "ramp20", + "x": 508.57142857142856, + "y": 86, + "width": 23.928571428571427, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#059669", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1067, + "version": 1, + "versionNonce": 1068, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "arrow", + "id": "b_cross", + "x": 532, + "y": 114, + "width": -159.05, + "height": 0.0, + "strokeColor": "#059669", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1069, + "version": 1, + "versionNonce": 1070, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -159.05, + 0.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "b_cross_h", + "x": 368, + "y": 114, + "width": 0, + "height": 0, + "strokeColor": "#059669", + "backgroundColor": "#059669", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1071, + "version": 1, + "versionNonce": 1072, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 9.0, + -5.579999999999998 + ], + [ + 9.0, + 5.579999999999998 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "text", + "id": "bl_cross21", + "x": 378.45, + "y": 124, + "width": 143.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1073, + "version": 1, + "versionNonce": 1074, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "cross-links: a migrated", + "originalText": "cross-links: a migrated", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bl_cross22", + "x": 378.45, + "y": 140.25, + "width": 143.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1075, + "version": 1, + "versionNonce": 1076, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "definition keeps its OIFM", + "originalText": "definition keeps its OIFM", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bl_cross23", + "x": 378.45, + "y": 156.5, + "width": 143.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1077, + "version": 1, + "versionNonce": 1078, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "entry and carries its", + "originalText": "entry and carries its", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "bl_cross24", + "x": 378.45, + "y": 172.75, + "width": 143.1, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1079, + "version": 1, + "versionNonce": 1080, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CDE identity", + "originalText": "CDE identity", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "l_index", + "x": 346.0, + "y": 220.0, + "width": 208.0, + "height": 0.0, + "strokeColor": "#059669", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1081, + "version": 1, + "versionNonce": 1082, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 208.0, + 0.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "rectangle", + "id": "l_index_l", + "x": 409.3, + "y": 208.875, + "width": 81.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1083, + "version": 1, + "versionNonce": 1084, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_index_l_t", + "x": 415.3, + "y": 211.875, + "width": 69.4, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1085, + "version": 1, + "versionNonce": 1086, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "index codes", + "originalText": "index codes", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "amber", + "x": 0, + "y": 320, + "width": 900, + "height": 92, + "strokeColor": "#b45309", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1087, + "version": 1, + "versionNonce": 1088, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "a_title", + "x": 344.65, + "y": 344.25, + "width": 210.7, + "height": 21.25, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1089, + "version": 1, + "versionNonce": 1090, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "the next-generation schema", + "originalText": "the next-generation schema", + "fontSize": 17, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "a_sub", + "x": 285.25, + "y": 371.5, + "width": 329.5, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1091, + "version": 1, + "versionNonce": 1092, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "one graph of interconnected concepts for both collections", + "originalText": "one graph of interconnected concepts for both collections", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "m_onto0", + "x": 182.0, + "y": 274, + "width": 0.0, + "height": 39.05000000000001, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1093, + "version": 1, + "versionNonce": 1094, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 0.0, + 39.05000000000001 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "m_onto0_h", + "x": 182.0, + "y": 318, + "width": 0, + "height": 0, + "strokeColor": "#b45309", + "backgroundColor": "#b45309", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1095, + "version": 1, + "versionNonce": 1096, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -5.5800000000000125, + -9.0 + ], + [ + 5.5800000000000125, + -9.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "text", + "id": "m_l25", + "x": 194.0, + "y": 288.0, + "width": 70.8, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1097, + "version": 1, + "versionNonce": 1098, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "moving onto", + "originalText": "moving onto", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "m_onto1", + "x": 718.0, + "y": 274, + "width": 0.0, + "height": 39.05000000000001, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1099, + "version": 1, + "versionNonce": 1100, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + 0.0, + 39.05000000000001 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "m_onto1_h", + "x": 718.0, + "y": 318, + "width": 0, + "height": 0, + "strokeColor": "#b45309", + "backgroundColor": "#b45309", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1101, + "version": 1, + "versionNonce": 1102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0 + ], + [ + -5.580000000000041, + -9.0 + ], + [ + 5.580000000000041, + -9.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "text", + "id": "m_l26", + "x": 730.0, + "y": 288.0, + "width": 70.8, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1103, + "version": 1, + "versionNonce": 1104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "moving onto", + "originalText": "moving onto", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": { + "img_hicon1": { + "mimeType": "image/svg+xml", + "id": "img_hicon1", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwNTk2NjkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyLjU4NiAyLjU4NkEyIDIgMCAwIDAgMTEuMTcyIDJINGEyIDIgMCAwIDAtMiAydjcuMTcyYTIgMiAwIDAgMCAuNTg2IDEuNDE0bDguNzA0IDguNzA0YTIuNDI2IDIuNDI2IDAgMCAwIDMuNDIgMGw2LjU4LTYuNThhMi40MjYgMi40MjYgMCAwIDAgMC0zLjQyeiIgLz4KICA8Y2lyY2xlIGN4PSI3LjUiIGN5PSI3LjUiIHI9Ii41IiBmaWxsPSIjMDU5NjY5IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_hicon6": { + "mimeType": "image/svg+xml", + "id": "img_hicon6", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwNTk2NjkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyLjU4NiAyLjU4NkEyIDIgMCAwIDAgMTEuMTcyIDJINGEyIDIgMCAwIDAtMiAydjcuMTcyYTIgMiAwIDAgMCAuNTg2IDEuNDE0bDguNzA0IDguNzA0YTIuNDI2IDIuNDI2IDAgMCAwIDMuNDIgMGw2LjU4LTYuNThhMi40MjYgMi40MjYgMCAwIDAgMC0zLjQyeiIgLz4KICA8Y2lyY2xlIGN4PSI3LjUiIGN5PSI3LjUiIHI9Ii41IiBmaWxsPSIjMDU5NjY5IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + } + } +} \ No newline at end of file diff --git a/tools/diagrams/excalidraw/two-planes.excalidraw b/tools/diagrams/excalidraw/two-planes.excalidraw new file mode 100644 index 0000000..6578a74 --- /dev/null +++ b/tools/diagrams/excalidraw/two-planes.excalidraw @@ -0,0 +1,4487 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "oidm-knowledge/tools/diagrams", + "elements": [ + { + "type": "line", + "id": "plane_patient", + "x": 55, + "y": 0, + "width": 935, + "height": 190, + "strokeColor": "#0f766e", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1001, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 880, + 0 + ], + [ + 825, + 190 + ], + [ + -55, + 190 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "line", + "id": "plane_foundation", + "x": 55, + "y": 250, + "width": 935, + "height": 440, + "strokeColor": "#b45309", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1003, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 880, + 0 + ], + [ + 825, + 440 + ], + [ + -55, + 440 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "image", + "id": "hicon1", + "x": 67, + "y": 14.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1005, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_hicon1", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "htitle2", + "x": 107, + "y": 12, + "width": 174.0, + "height": 25.0, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1007, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Patient Context", + "originalText": "Patient Context", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hsub3", + "x": 107, + "y": 38, + "width": 180.95999999999998, + "height": 16.25, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1009, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "this patient \u00b7 this exam", + "originalText": "this patient \u00b7 this exam", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "image", + "id": "hicon4", + "x": 68, + "y": 266.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1011, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_hicon4", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "htitle5", + "x": 108, + "y": 264, + "width": 208.79999999999998, + "height": 25.0, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1013, + "version": 1, + "versionNonce": 1014, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "Foundation Context", + "originalText": "Foundation Context", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "hsub6", + "x": 108, + "y": 290, + "width": 188.5, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1015, + "version": 1, + "versionNonce": 1016, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "shared, curated knowledge", + "originalText": "shared, curated knowledge", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "exam", + "x": 140, + "y": 92, + "width": 246, + "height": 66, + "strokeColor": "#0f766e", + "backgroundColor": "#99f6e4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1017, + "version": 1, + "versionNonce": 1018, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "ni7", + "x": 152.0, + "y": 111.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1019, + "version": 1, + "versionNonce": 1020, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_ni7", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "nt8", + "x": 190, + "y": 105.875, + "width": 186, + "height": 20.0, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1021, + "version": 1, + "versionNonce": 1022, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT chest without contrast", + "originalText": "CT chest without contrast", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ns9", + "x": 190, + "y": 127.875, + "width": 186, + "height": 16.25, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1023, + "version": 1, + "versionNonce": 1024, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "this patient \u00b7 2026-03-04", + "originalText": "this patient \u00b7 2026-03-04", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "nodule", + "x": 470, + "y": 92, + "width": 196, + "height": 66, + "strokeColor": "#0f766e", + "backgroundColor": "#99f6e4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1025, + "version": 1, + "versionNonce": 1026, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "ni10", + "x": 482.0, + "y": 111.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1027, + "version": 1, + "versionNonce": 1028, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_ni10", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "nt11", + "x": 520, + "y": 105.875, + "width": 136, + "height": 20.0, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1029, + "version": 1, + "versionNonce": 1030, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary nodule", + "originalText": "pulmonary nodule", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ns12", + "x": 520, + "y": 127.875, + "width": 136, + "height": 16.25, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1031, + "version": 1, + "versionNonce": 1032, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "present \u00b7 8 mm \u00b7 new", + "originalText": "present \u00b7 8 mm \u00b7 new", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "effusion", + "x": 706, + "y": 92, + "width": 176, + "height": 66, + "strokeColor": "#0f766e", + "backgroundColor": "#99f6e4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1033, + "version": 1, + "versionNonce": 1034, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "ni13", + "x": 718.0, + "y": 111.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1035, + "version": 1, + "versionNonce": 1036, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_ni13", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "nt14", + "x": 756, + "y": 105.875, + "width": 116, + "height": 20.0, + "strokeColor": "#134e4a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1037, + "version": 1, + "versionNonce": 1038, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pleural effusion", + "originalText": "pleural effusion", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "ns15", + "x": 756, + "y": 127.875, + "width": 116, + "height": 16.25, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1039, + "version": 1, + "versionNonce": 1040, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "absent", + "originalText": "absent", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "e_found1", + "x": 470, + "y": 125, + "width": -72.5, + "height": 0.0, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1041, + "version": 1, + "versionNonce": 1042, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -72.5, + 0.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_found1_h", + "x": 392, + "y": 125, + "width": 0, + "height": 0, + "strokeColor": "#0f766e", + "backgroundColor": "#0f766e", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1043, + "version": 1, + "versionNonce": 1044, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 10.0, + -6.200000000000003 + ], + [ + 10.0, + 6.199999999999989 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_found1", + "x": 399.7, + "y": 113.875, + "width": 62.6, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1045, + "version": 1, + "versionNonce": 1046, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_found1_t", + "x": 405.7, + "y": 116.875, + "width": 50.6, + "height": 16.25, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1047, + "version": 1, + "versionNonce": 1048, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "found on", + "originalText": "found on", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "e_found2", + "x": 790, + "y": 92, + "width": -540.0, + "height": -8.5, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1049, + "version": 1, + "versionNonce": 1050, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -20 + ], + [ + -540, + -20 + ], + [ + -540.0, + -8.5 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "e_found2_h", + "x": 250, + "y": 89, + "width": 0, + "height": 0, + "strokeColor": "#0f766e", + "backgroundColor": "#0f766e", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1051, + "version": 1, + "versionNonce": 1052, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -6.199999999999989, + -10.0 + ], + [ + 6.199999999999989, + -10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_found2", + "x": 528.7, + "y": 60.875, + "width": 62.6, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1053, + "version": 1, + "versionNonce": 1054, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_found2_t", + "x": 534.7, + "y": 63.875, + "width": 50.6, + "height": 16.25, + "strokeColor": "#0f766e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1055, + "version": 1, + "versionNonce": 1056, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "found on", + "originalText": "found on", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "exam_type", + "x": 120, + "y": 322, + "width": 218, + "height": 62, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1057, + "version": 1, + "versionNonce": 1058, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "ni16", + "x": 132.55, + "y": 337.0, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1059, + "version": 1, + "versionNonce": 1060, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_ni16", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "nt17", + "x": 174.55, + "y": 334.5, + "width": 150.9, + "height": 18.75, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1061, + "version": 1, + "versionNonce": 1062, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest WO contrast", + "originalText": "CT Chest WO contrast", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "nd18", + "x": 174.55, + "y": 355.25, + "width": 150.9, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1063, + "version": 1, + "versionNonce": 1064, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "exam type \u00b7 29252-4", + "originalText": "exam type \u00b7 29252-4", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "nodule_def", + "x": 370, + "y": 322, + "width": 216, + "height": 62, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1065, + "version": 1, + "versionNonce": 1066, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "ni19", + "x": 382.75, + "y": 339.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1067, + "version": 1, + "versionNonce": 1068, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_ni19", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "nt20", + "x": 420.75, + "y": 334.5, + "width": 152.5, + "height": 18.75, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1069, + "version": 1, + "versionNonce": 1070, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pulmonary nodule", + "originalText": "pulmonary nodule", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "nd21", + "x": 420.75, + "y": 355.25, + "width": 152.5, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1071, + "version": 1, + "versionNonce": 1072, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "finding/diagnosis definition", + "originalText": "finding/diagnosis definition", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "effusion_def", + "x": 618, + "y": 322, + "width": 216, + "height": 62, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1073, + "version": 1, + "versionNonce": 1074, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "ni22", + "x": 630.75, + "y": 339.0, + "width": 28, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1075, + "version": 1, + "versionNonce": 1076, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_ni22", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "nt23", + "x": 668.75, + "y": 334.5, + "width": 152.5, + "height": 18.75, + "strokeColor": "#064e3b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1077, + "version": 1, + "versionNonce": 1078, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pleural effusion", + "originalText": "pleural effusion", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "nd24", + "x": 668.75, + "y": 355.25, + "width": 152.5, + "height": 16.25, + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1079, + "version": 1, + "versionNonce": 1080, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "finding/diagnosis definition", + "originalText": "finding/diagnosis definition", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "family", + "x": 130, + "y": 448, + "width": 168, + "height": 56, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1081, + "version": 1, + "versionNonce": 1082, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "nt25", + "x": 138, + "y": 457.5, + "width": 152, + "height": 18.75, + "strokeColor": "#78350f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1083, + "version": 1, + "versionNonce": 1084, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT Chest", + "originalText": "CT Chest", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "nd26", + "x": 138, + "y": 478.25, + "width": 152, + "height": 16.25, + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1085, + "version": 1, + "versionNonce": 1086, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "exam family \u00b7 preferred", + "originalText": "exam family \u00b7 preferred", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "modality", + "x": 382, + "y": 448, + "width": 80, + "height": 56, + "strokeColor": "#6b7280", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1087, + "version": 1, + "versionNonce": 1088, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "nt27", + "x": 390, + "y": 457.5, + "width": 64, + "height": 18.75, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1089, + "version": 1, + "versionNonce": 1090, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "CT", + "originalText": "CT", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "nd28", + "x": 390, + "y": 478.25, + "width": 64, + "height": 16.25, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1091, + "version": 1, + "versionNonce": 1092, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "modality", + "originalText": "modality", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_member", + "x": 200, + "y": 384, + "width": 0.0, + "height": 55.5, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1093, + "version": 1, + "versionNonce": 1094, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 55.5 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_member_h", + "x": 200, + "y": 445, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1095, + "version": 1, + "versionNonce": 1096, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -6.199999999999989, + -10.0 + ], + [ + 6.199999999999989, + -10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_member", + "x": 144.15, + "y": 404.875, + "width": 111.7, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1097, + "version": 1, + "versionNonce": 1098, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_member_t", + "x": 150.15, + "y": 407.875, + "width": 99.7, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1099, + "version": 1, + "versionNonce": 1100, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "member of family", + "originalText": "member of family", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_modality", + "x": 298, + "y": 476.0, + "width": 75.5, + "height": 0.0, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1101, + "version": 1, + "versionNonce": 1102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 75.5, + 0.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_modality_h", + "x": 379, + "y": 476.0, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1103, + "version": 1, + "versionNonce": 1104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -10.0, + 6.199999999999989 + ], + [ + -10.0, + -6.199999999999989 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_modality", + "x": 309.8, + "y": 464.875, + "width": 60.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1105, + "version": 1, + "versionNonce": 1106, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_modality_t", + "x": 315.8, + "y": 467.875, + "width": 48.4, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1107, + "version": 1, + "versionNonce": 1108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "modality", + "originalText": "modality", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_seen", + "x": 422, + "y": 384, + "width": 0.0, + "height": 55.5, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1109, + "version": 1, + "versionNonce": 1110, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 55.5 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_seen_h", + "x": 422, + "y": 445, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1111, + "version": 1, + "versionNonce": 1112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -6.199999999999989, + -10.0 + ], + [ + 6.199999999999989, + -10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_seen", + "x": 392.85, + "y": 404.875, + "width": 58.3, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1113, + "version": 1, + "versionNonce": 1114, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_seen_t", + "x": 398.85, + "y": 407.875, + "width": 46.3, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1115, + "version": 1, + "versionNonce": 1116, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "seen on", + "originalText": "seen on", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "thorax", + "x": 130, + "y": 568, + "width": 130, + "height": 62, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1117, + "version": 1, + "versionNonce": 1118, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "nt29", + "x": 138, + "y": 580.5, + "width": 114, + "height": 18.75, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1119, + "version": 1, + "versionNonce": 1120, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "thorax", + "originalText": "thorax", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "nd30", + "x": 138, + "y": 601.25, + "width": 114, + "height": 16.25, + "strokeColor": "#1d4ed8", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1121, + "version": 1, + "versionNonce": 1122, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "anatomic location", + "originalText": "anatomic location", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "lung", + "x": 342, + "y": 568, + "width": 110, + "height": 62, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1123, + "version": 1, + "versionNonce": 1124, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "image", + "id": "ni31", + "x": 361.8, + "y": 583.0, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1125, + "version": 1, + "versionNonce": 1126, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "fileId": "img_ni31", + "status": "saved", + "scale": [ + 1, + 1 + ], + "roundness": null, + "crop": null + }, + { + "type": "text", + "id": "nt32", + "x": 403.8, + "y": 589.625, + "width": 28.4, + "height": 18.75, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1127, + "version": 1, + "versionNonce": 1128, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "lung", + "originalText": "lung", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "upper_lobe", + "x": 554, + "y": 568, + "width": 185, + "height": 62, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1129, + "version": 1, + "versionNonce": 1130, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "nt33", + "x": 562, + "y": 589.625, + "width": 169, + "height": 18.75, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1131, + "version": 1, + "versionNonce": 1132, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "upper lobe of right lung", + "originalText": "upper lobe of right lung", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "pleural_space", + "x": 760, + "y": 568, + "width": 120, + "height": 62, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1133, + "version": 1, + "versionNonce": 1134, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "nt34", + "x": 768, + "y": 589.625, + "width": 104, + "height": 18.75, + "strokeColor": "#1e3a8a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1135, + "version": 1, + "versionNonce": 1136, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "pleural space", + "originalText": "pleural space", + "fontSize": 15, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_contains1", + "x": 260, + "y": 599.0, + "width": 73.5, + "height": 0.0, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1137, + "version": 1, + "versionNonce": 1138, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 73.5, + 0.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_contains1_h", + "x": 339, + "y": 599.0, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1139, + "version": 1, + "versionNonce": 1140, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -10.0, + 6.2000000000000455 + ], + [ + -10.0, + -6.2000000000000455 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_contains1", + "x": 270.8, + "y": 587.875, + "width": 60.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1141, + "version": 1, + "versionNonce": 1142, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_contains1_t", + "x": 276.8, + "y": 590.875, + "width": 48.4, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1143, + "version": 1, + "versionNonce": 1144, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "contains", + "originalText": "contains", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_contains2", + "x": 452, + "y": 599.0, + "width": 93.5, + "height": 0.0, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1145, + "version": 1, + "versionNonce": 1146, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 93.5, + 0.0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_contains2_h", + "x": 551, + "y": 599.0, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1147, + "version": 1, + "versionNonce": 1148, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + -10.0, + 6.2000000000000455 + ], + [ + -10.0, + -6.2000000000000455 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_contains2", + "x": 472.8, + "y": 587.875, + "width": 60.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1149, + "version": 1, + "versionNonce": 1150, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_contains2_t", + "x": 478.8, + "y": 590.875, + "width": 48.4, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1151, + "version": 1, + "versionNonce": 1152, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "contains", + "originalText": "contains", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_contains3", + "x": 215, + "y": 630, + "width": 605.0, + "height": 8.5, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1153, + "version": 1, + "versionNonce": 1154, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 26 + ], + [ + 605, + 26 + ], + [ + 605.0, + 8.5 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_contains3_h", + "x": 820, + "y": 633, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1155, + "version": 1, + "versionNonce": 1156, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6.2000000000000455, + 10.0 + ], + [ + -6.2000000000000455, + 10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_contains3", + "x": 469.8, + "y": 644.875, + "width": 60.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1157, + "version": 1, + "versionNonce": 1158, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_contains3_t", + "x": 475.8, + "y": 647.875, + "width": 48.4, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1159, + "version": 1, + "versionNonce": 1160, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "contains", + "originalText": "contains", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_covers", + "x": 200, + "y": 504, + "width": 0.0, + "height": 55.5, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1161, + "version": 1, + "versionNonce": 1162, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 55.5 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_covers_h", + "x": 200, + "y": 565, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1163, + "version": 1, + "versionNonce": 1164, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -6.199999999999989, + -10.0 + ], + [ + 6.199999999999989, + -10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_covers", + "x": 174.85, + "y": 524.875, + "width": 50.3, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1165, + "version": 1, + "versionNonce": 1166, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_covers_t", + "x": 180.85, + "y": 527.875, + "width": 38.3, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1167, + "version": 1, + "versionNonce": 1168, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "covers", + "originalText": "covers", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_occurs1", + "x": 560, + "y": 384, + "width": -120.0, + "height": 175.5, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1169, + "version": 1, + "versionNonce": 1170, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 166 + ], + [ + -120, + 166 + ], + [ + -120.0, + 175.5 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_occurs1_h", + "x": 440, + "y": 565, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1171, + "version": 1, + "versionNonce": 1172, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -6.199999999999989, + -10.0 + ], + [ + 6.199999999999989, + -10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_occurs1", + "x": 519.65, + "y": 404.875, + "width": 64.7, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1173, + "version": 1, + "versionNonce": 1174, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_occurs1_t", + "x": 525.65, + "y": 407.875, + "width": 52.7, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1175, + "version": 1, + "versionNonce": 1176, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "occurs at", + "originalText": "occurs at", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "r_occurs2", + "x": 790, + "y": 384, + "width": 0.0, + "height": 175.5, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1177, + "version": 1, + "versionNonce": 1178, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 175.5 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "r_occurs2_h", + "x": 790, + "y": 565, + "width": 0, + "height": 0, + "strokeColor": "#78716c", + "backgroundColor": "#78716c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1179, + "version": 1, + "versionNonce": 1180, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -6.2000000000000455, + -10.0 + ], + [ + 6.2000000000000455, + -10.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "l_occurs2", + "x": 757.65, + "y": 404.875, + "width": 64.7, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1181, + "version": 1, + "versionNonce": 1182, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "l_occurs2_t", + "x": 763.65, + "y": 407.875, + "width": 52.7, + "height": 16.25, + "strokeColor": "#78716c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1183, + "version": 1, + "versionNonce": 1184, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "occurs at", + "originalText": "occurs at", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "g1", + "x": 85, + "y": 401, + "width": 22, + "height": 22, + "strokeColor": "#a8a29e", + "backgroundColor": "#e7e5e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 70, + "angle": 0, + "seed": 1185, + "version": 1, + "versionNonce": 1186, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "g2", + "x": 53, + "y": 439, + "width": 18, + "height": 18, + "strokeColor": "#a8a29e", + "backgroundColor": "#e7e5e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 70, + "angle": 0, + "seed": 1187, + "version": 1, + "versionNonce": 1188, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "g3", + "x": 82, + "y": 476, + "width": 20, + "height": 20, + "strokeColor": "#a8a29e", + "backgroundColor": "#e7e5e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 70, + "angle": 0, + "seed": 1189, + "version": 1, + "versionNonce": 1190, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "g4", + "x": 52, + "y": 520, + "width": 16, + "height": 16, + "strokeColor": "#a8a29e", + "backgroundColor": "#e7e5e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 70, + "angle": 0, + "seed": 1191, + "version": 1, + "versionNonce": 1192, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "g5", + "x": 664, + "y": 440, + "width": 24, + "height": 24, + "strokeColor": "#a8a29e", + "backgroundColor": "#e7e5e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 70, + "angle": 0, + "seed": 1193, + "version": 1, + "versionNonce": 1194, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "ellipse", + "id": "g6", + "x": 703, + "y": 483, + "width": 18, + "height": 18, + "strokeColor": "#a8a29e", + "backgroundColor": "#e7e5e4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 70, + "angle": 0, + "seed": 1195, + "version": 1, + "versionNonce": 1196, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false + }, + { + "type": "line", + "id": "f35", + "x": 96, + "y": 412, + "width": -34, + "height": 36, + "strokeColor": "#a8a29e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 60, + "angle": 0, + "seed": 1197, + "version": 1, + "versionNonce": 1198, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -34, + 36 + ] + ] + }, + { + "type": "line", + "id": "f36", + "x": 96, + "y": 412, + "width": -4, + "height": 74, + "strokeColor": "#a8a29e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 60, + "angle": 0, + "seed": 1199, + "version": 1, + "versionNonce": 1200, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -4, + 74 + ] + ] + }, + { + "type": "line", + "id": "f37", + "x": 92, + "y": 486, + "width": -32, + "height": 42, + "strokeColor": "#a8a29e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 60, + "angle": 0, + "seed": 1201, + "version": 1, + "versionNonce": 1202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -32, + 42 + ] + ] + }, + { + "type": "line", + "id": "f38", + "x": 104, + "y": 404, + "width": 20, + "height": -16, + "strokeColor": "#a8a29e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 60, + "angle": 0, + "seed": 1203, + "version": 1, + "versionNonce": 1204, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 20, + -16 + ] + ] + }, + { + "type": "line", + "id": "f39", + "x": 66, + "y": 535, + "width": 64, + "height": 55, + "strokeColor": "#a8a29e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 60, + "angle": 0, + "seed": 1205, + "version": 1, + "versionNonce": 1206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 64, + 55 + ] + ] + }, + { + "type": "line", + "id": "f40", + "x": 676, + "y": 452, + "width": 36, + "height": 40, + "strokeColor": "#a8a29e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 60, + "angle": 0, + "seed": 1207, + "version": 1, + "versionNonce": 1208, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 36, + 40 + ] + ] + }, + { + "type": "line", + "id": "f41", + "x": 676, + "y": 440, + "width": 0, + "height": -56, + "strokeColor": "#a8a29e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 60, + "angle": 0, + "seed": 1209, + "version": 1, + "versionNonce": 1210, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -56 + ] + ] + }, + { + "type": "arrow", + "id": "x_exam", + "x": 300, + "y": 158, + "width": 0.0, + "height": 153.85000000000002, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1211, + "version": 1, + "versionNonce": 1212, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 153.85000000000002 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "x_exam_h", + "x": 300, + "y": 319, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1213, + "version": 1, + "versionNonce": 1214, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -8.060000000000002, + -13.0 + ], + [ + 8.060000000000002, + -13.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "x_exam_l", + "x": 283.9, + "y": 200.875, + "width": 32.2, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1215, + "version": 1, + "versionNonce": 1216, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "x_exam_l_t", + "x": 289.9, + "y": 203.875, + "width": 20.2, + "height": 16.25, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1217, + "version": 1, + "versionNonce": 1218, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "is a", + "originalText": "is a", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "x_nodule_isa", + "x": 490, + "y": 158, + "width": 0.0, + "height": 153.85000000000002, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1219, + "version": 1, + "versionNonce": 1220, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 153.85000000000002 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "x_nodule_isa_h", + "x": 490, + "y": 319, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1221, + "version": 1, + "versionNonce": 1222, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -8.060000000000002, + -13.0 + ], + [ + 8.060000000000002, + -13.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "x_nodule_isa_l", + "x": 473.9, + "y": 200.875, + "width": 32.2, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1223, + "version": 1, + "versionNonce": 1224, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "x_nodule_isa_l_t", + "x": 479.9, + "y": 203.875, + "width": 20.2, + "height": 16.25, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1225, + "version": 1, + "versionNonce": 1226, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "is a", + "originalText": "is a", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "x_nodule_loc", + "x": 602, + "y": 158, + "width": 0.0, + "height": 399.85, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1227, + "version": 1, + "versionNonce": 1228, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 399.85 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "x_nodule_loc_h", + "x": 602, + "y": 565, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1229, + "version": 1, + "versionNonce": 1230, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -8.059999999999945, + -13.0 + ], + [ + 8.059999999999945, + -13.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "x_nodule_loc_l", + "x": 567.8, + "y": 200.875, + "width": 68.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1231, + "version": 1, + "versionNonce": 1232, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "x_nodule_loc_l_t", + "x": 573.8, + "y": 203.875, + "width": 56.4, + "height": 16.25, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1233, + "version": 1, + "versionNonce": 1234, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "located at", + "originalText": "located at", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "x_effusion_isa", + "x": 720, + "y": 158, + "width": 0.0, + "height": 153.85000000000002, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1235, + "version": 1, + "versionNonce": 1236, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 153.85000000000002 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "x_effusion_isa_h", + "x": 720, + "y": 319, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1237, + "version": 1, + "versionNonce": 1238, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -8.059999999999945, + -13.0 + ], + [ + 8.059999999999945, + -13.0 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "x_effusion_isa_l", + "x": 703.9, + "y": 200.875, + "width": 32.2, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1239, + "version": 1, + "versionNonce": 1240, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "x_effusion_isa_l_t", + "x": 709.9, + "y": 203.875, + "width": 20.2, + "height": 16.25, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1241, + "version": 1, + "versionNonce": 1242, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "is a", + "originalText": "is a", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "x_effusion_loc", + "x": 866, + "y": 158, + "width": -9.824377326810236, + "height": 399.85215720117867, + "strokeColor": "#be123c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1243, + "version": 1, + "versionNonce": 1244, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -9.824377326810236, + 399.85215720117867 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "id": "x_effusion_loc_h", + "x": 856, + "y": 565, + "width": 0, + "height": 0, + "strokeColor": "#be123c", + "backgroundColor": "#be123c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1245, + "version": 1, + "versionNonce": 1246, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -7.738254294690023, + -13.194052465816299 + ], + [ + 8.376882197198029, + -12.798103166261285 + ], + [ + 0, + 0 + ] + ], + "roundness": null + }, + { + "type": "rectangle", + "id": "x_effusion_loc_l", + "x": 829.8, + "y": 200.875, + "width": 68.4, + "height": 22.25, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1247, + "version": 1, + "versionNonce": 1248, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "x_effusion_loc_l_t", + "x": 835.8, + "y": 203.875, + "width": 56.4, + "height": 16.25, + "strokeColor": "#881337", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1249, + "version": 1, + "versionNonce": 1250, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "located at", + "originalText": "located at", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "leg42", + "x": 192.45, + "y": 713.6, + "width": 18, + "height": 13, + "strokeColor": "#059669", + "backgroundColor": "#d1fae5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1251, + "version": 1, + "versionNonce": 1252, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "legt43", + "x": 218.45, + "y": 712, + "width": 152.5, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1253, + "version": 1, + "versionNonce": 1254, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "finding/diagnosis definition", + "originalText": "finding/diagnosis definition", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "leg44", + "x": 398.95, + "y": 713.6, + "width": 18, + "height": 13, + "strokeColor": "#1d4ed8", + "backgroundColor": "#bfdbfe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1255, + "version": 1, + "versionNonce": 1256, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "legt45", + "x": 424.95, + "y": 712, + "width": 101.2, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1257, + "version": 1, + "versionNonce": 1258, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "anatomic location", + "originalText": "anatomic location", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "leg46", + "x": 554.15, + "y": 713.6, + "width": 18, + "height": 13, + "strokeColor": "#b45309", + "backgroundColor": "#fde68a", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1259, + "version": 1, + "versionNonce": 1260, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "legt47", + "x": 580.15, + "y": 712, + "width": 60.0, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1261, + "version": 1, + "versionNonce": 1262, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "exam type", + "originalText": "exam type", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "leg48", + "x": 668.15, + "y": 713.6, + "width": 18, + "height": 13, + "strokeColor": "#6b7280", + "backgroundColor": "#e5e7eb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1263, + "version": 1, + "versionNonce": 1264, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "legt49", + "x": 694.15, + "y": 712, + "width": 48.4, + "height": 16.25, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1265, + "version": 1, + "versionNonce": 1266, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "modality", + "originalText": "modality", + "fontSize": 13, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "caption", + "x": 0, + "y": 744, + "width": 935, + "height": 17.5, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1267, + "version": 1, + "versionNonce": 1268, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "text": "every fact about this patient is attached to the shared knowledge of what such a thing is", + "originalText": "every fact about this patient is attached to the shared knowledge of what such a thing is", + "fontSize": 14, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": null + }, + "files": { + "img_hicon1": { + "mimeType": "image/svg+xml", + "id": "img_hicon1", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwZjc2NmUiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyLjgzIDIuMThhMiAyIDAgMCAwLTEuNjYgMEwyLjYgNi4wOGExIDEgMCAwIDAgMCAxLjgzbDguNTggMy45MWEyIDIgMCAwIDAgMS42NiAwbDguNTgtMy45YTEgMSAwIDAgMCAwLTEuODN6IiAvPgogIDxwYXRoIGQ9Ik0yIDEyYTEgMSAwIDAgMCAuNTguOTFsOC42IDMuOTFhMiAyIDAgMCAwIDEuNjUgMGw4LjU4LTMuOUExIDEgMCAwIDAgMjIgMTIiIC8+CiAgPHBhdGggZD0iTTIgMTdhMSAxIDAgMCAwIC41OC45MWw4LjYgMy45MWEyIDIgMCAwIDAgMS42NSAwbDguNTgtMy45QTEgMSAwIDAgMCAyMiAxNyIgLz4KPG1ldGFkYXRhIGlkPSJsdWNpZGUtbGljZW5zZSI+CklTQyBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMjYgTHVjaWRlIEljb25zIGFuZCBDb250cmlidXRvcnMKClBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueQpwdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlCmNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2UgYXBwZWFyIGluIGFsbCBjb3BpZXMuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiBBTkQgVEhFIEFVVEhPUiBESVNDTEFJTVMgQUxMIFdBUlJBTlRJRVMKV0lUSCBSRUdBUkQgVE8gVEhJUyBTT0ZUV0FSRSBJTkNMVURJTkcgQUxMIElNUExJRUQgV0FSUkFOVElFUyBPRgpNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1IgQkUgTElBQkxFIEZPUgpBTlkgU1BFQ0lBTCwgRElSRUNULCBJTkRJUkVDVCwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIE9SIEFOWSBEQU1BR0VTCldIQVRTT0VWRVIgUkVTVUxUSU5HIEZST00gTE9TUyBPRiBVU0UsIERBVEEgT1IgUFJPRklUUywgV0hFVEhFUiBJTiBBTgpBQ1RJT04gT0YgQ09OVFJBQ1QsIE5FR0xJR0VOQ0UgT1IgT1RIRVIgVE9SVElPVVMgQUNUSU9OLCBBUklTSU5HIE9VVCBPRgpPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFVTRSBPUiBQRVJGT1JNQU5DRSBPRiBUSElTIFNPRlRXQVJFLgoKLS0tCgpUaGUgZm9sbG93aW5nIEx1Y2lkZSBpY29ucyBhcmUgZGVyaXZlZCBmcm9tIHRoZSBGZWF0aGVyIHByb2plY3Q6CgphaXJwbGF5LCBhbGVydC1jaXJjbGUsIGFsZXJ0LW9jdGFnb24sIGFsZXJ0LXRyaWFuZ2xlLCBhcGVydHVyZSwgYXJyb3ctZG93bi1jaXJjbGUsIGFycm93LWRvd24tbGVmdCwgYXJyb3ctZG93bi1yaWdodCwgYXJyb3ctZG93biwgYXJyb3ctbGVmdC1jaXJjbGUsIGFycm93LWxlZnQsIGFycm93LXJpZ2h0LWNpcmNsZSwgYXJyb3ctcmlnaHQsIGFycm93LXVwLWNpcmNsZSwgYXJyb3ctdXAtbGVmdCwgYXJyb3ctdXAtcmlnaHQsIGFycm93LXVwLCBhdC1zaWduLCBjYWxlbmRhciwgY2FzdCwgY2hlY2ssIGNoZXZyb24tZG93biwgY2hldnJvbi1sZWZ0LCBjaGV2cm9uLXJpZ2h0LCBjaGV2cm9uLXVwLCBjaGV2cm9ucy1kb3duLCBjaGV2cm9ucy1sZWZ0LCBjaGV2cm9ucy1yaWdodCwgY2hldnJvbnMtdXAsIGNpcmNsZSwgY2xpcGJvYXJkLCBjbG9jaywgY29kZSwgY29sdW1ucywgY29tbWFuZCwgY29tcGFzcywgY29ybmVyLWRvd24tbGVmdCwgY29ybmVyLWRvd24tcmlnaHQsIGNvcm5lci1sZWZ0LWRvd24sIGNvcm5lci1sZWZ0LXVwLCBjb3JuZXItcmlnaHQtZG93biwgY29ybmVyLXJpZ2h0LXVwLCBjb3JuZXItdXAtbGVmdCwgY29ybmVyLXVwLXJpZ2h0LCBjcm9zc2hhaXIsIGRhdGFiYXNlLCBkaXZpZGUtY2lyY2xlLCBkaXZpZGUtc3F1YXJlLCBkb2xsYXItc2lnbiwgZG93bmxvYWQsIGV4dGVybmFsLWxpbmssIGZlYXRoZXIsIGZyb3duLCBoYXNoLCBoZWFkcGhvbmVzLCBoZWxwLWNpcmNsZSwgaW5mbywgaXRhbGljLCBrZXksIGxheW91dCwgbGlmZS1idW95LCBsaW5rLTIsIGxpbmssIGxvYWRlciwgbG9jaywgbG9nLWluLCBsb2ctb3V0LCBtYXhpbWl6ZSwgbWVoLCBtaW5pbWl6ZSwgbWluaW1pemUtMiwgbWludXMtY2lyY2xlLCBtaW51cy1zcXVhcmUsIG1pbnVzLCBtb25pdG9yLCBtb29uLCBtb3JlLWhvcml6b250YWwsIG1vcmUtdmVydGljYWwsIG1vdmUsIG11c2ljLCBuYXZpZ2F0aW9uLTIsIG5hdmlnYXRpb24sIG9jdGFnb24sIHBhdXNlLWNpcmNsZSwgcGVyY2VudCwgcGx1cy1jaXJjbGUsIHBsdXMtc3F1YXJlLCBwbHVzLCBwb3dlciwgcmFkaW8sIHJzcywgc2VhcmNoLCBzZXJ2ZXIsIHNoYXJlLCBzaG9wcGluZy1iYWcsIHNpZGViYXIsIHNtYXJ0cGhvbmUsIHNtaWxlLCBzcXVhcmUsIHRhYmxlLTIsIHRhYmxldCwgdGFyZ2V0LCB0ZXJtaW5hbCwgdHJhc2gtMiwgdHJhc2gsIHRyaWFuZ2xlLCB0diwgdHlwZSwgdXBsb2FkLCB4LWNpcmNsZSwgeC1vY3RhZ29uLCB4LXNxdWFyZSwgeCwgem9vbS1pbiwgem9vbS1vdXQKClRoZSBNSVQgTGljZW5zZSAoTUlUKSAoZm9yIHRoZSBpY29ucyBsaXN0ZWQgYWJvdmUpCgpDb3B5cmlnaHQgKGMpIDIwMTMtcHJlc2VudCBDb2xlIEJlbWlzCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo8L21ldGFkYXRhPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_hicon4": { + "mimeType": "image/svg+xml", + "id": "img_hicon4", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiNiNDUzMDkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyIDV2MTYiIC8+CiAgPHBhdGggZD0iTTIwLjAwMSAxOUEyIDIgMCAwMDIyIDE3VjVhMiAyIDAgMDAtMS45OTktMkwxNiAzLjAwMkE1IDUgMCAwMDEyIDVhNSA1IDAgMDAtNC0ySDRhMiAyIDAgMDAtMiAydjEyYTIgMiAwIDAwMS45OTkgMkg4YTUgNSAwIDAxNCAyIDUgNSAwIDAxNC0yeiIgLz4KPG1ldGFkYXRhIGlkPSJsdWNpZGUtbGljZW5zZSI+CklTQyBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMjYgTHVjaWRlIEljb25zIGFuZCBDb250cmlidXRvcnMKClBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueQpwdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlCmNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2UgYXBwZWFyIGluIGFsbCBjb3BpZXMuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiBBTkQgVEhFIEFVVEhPUiBESVNDTEFJTVMgQUxMIFdBUlJBTlRJRVMKV0lUSCBSRUdBUkQgVE8gVEhJUyBTT0ZUV0FSRSBJTkNMVURJTkcgQUxMIElNUExJRUQgV0FSUkFOVElFUyBPRgpNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1IgQkUgTElBQkxFIEZPUgpBTlkgU1BFQ0lBTCwgRElSRUNULCBJTkRJUkVDVCwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIE9SIEFOWSBEQU1BR0VTCldIQVRTT0VWRVIgUkVTVUxUSU5HIEZST00gTE9TUyBPRiBVU0UsIERBVEEgT1IgUFJPRklUUywgV0hFVEhFUiBJTiBBTgpBQ1RJT04gT0YgQ09OVFJBQ1QsIE5FR0xJR0VOQ0UgT1IgT1RIRVIgVE9SVElPVVMgQUNUSU9OLCBBUklTSU5HIE9VVCBPRgpPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFVTRSBPUiBQRVJGT1JNQU5DRSBPRiBUSElTIFNPRlRXQVJFLgoKLS0tCgpUaGUgZm9sbG93aW5nIEx1Y2lkZSBpY29ucyBhcmUgZGVyaXZlZCBmcm9tIHRoZSBGZWF0aGVyIHByb2plY3Q6CgphaXJwbGF5LCBhbGVydC1jaXJjbGUsIGFsZXJ0LW9jdGFnb24sIGFsZXJ0LXRyaWFuZ2xlLCBhcGVydHVyZSwgYXJyb3ctZG93bi1jaXJjbGUsIGFycm93LWRvd24tbGVmdCwgYXJyb3ctZG93bi1yaWdodCwgYXJyb3ctZG93biwgYXJyb3ctbGVmdC1jaXJjbGUsIGFycm93LWxlZnQsIGFycm93LXJpZ2h0LWNpcmNsZSwgYXJyb3ctcmlnaHQsIGFycm93LXVwLWNpcmNsZSwgYXJyb3ctdXAtbGVmdCwgYXJyb3ctdXAtcmlnaHQsIGFycm93LXVwLCBhdC1zaWduLCBjYWxlbmRhciwgY2FzdCwgY2hlY2ssIGNoZXZyb24tZG93biwgY2hldnJvbi1sZWZ0LCBjaGV2cm9uLXJpZ2h0LCBjaGV2cm9uLXVwLCBjaGV2cm9ucy1kb3duLCBjaGV2cm9ucy1sZWZ0LCBjaGV2cm9ucy1yaWdodCwgY2hldnJvbnMtdXAsIGNpcmNsZSwgY2xpcGJvYXJkLCBjbG9jaywgY29kZSwgY29sdW1ucywgY29tbWFuZCwgY29tcGFzcywgY29ybmVyLWRvd24tbGVmdCwgY29ybmVyLWRvd24tcmlnaHQsIGNvcm5lci1sZWZ0LWRvd24sIGNvcm5lci1sZWZ0LXVwLCBjb3JuZXItcmlnaHQtZG93biwgY29ybmVyLXJpZ2h0LXVwLCBjb3JuZXItdXAtbGVmdCwgY29ybmVyLXVwLXJpZ2h0LCBjcm9zc2hhaXIsIGRhdGFiYXNlLCBkaXZpZGUtY2lyY2xlLCBkaXZpZGUtc3F1YXJlLCBkb2xsYXItc2lnbiwgZG93bmxvYWQsIGV4dGVybmFsLWxpbmssIGZlYXRoZXIsIGZyb3duLCBoYXNoLCBoZWFkcGhvbmVzLCBoZWxwLWNpcmNsZSwgaW5mbywgaXRhbGljLCBrZXksIGxheW91dCwgbGlmZS1idW95LCBsaW5rLTIsIGxpbmssIGxvYWRlciwgbG9jaywgbG9nLWluLCBsb2ctb3V0LCBtYXhpbWl6ZSwgbWVoLCBtaW5pbWl6ZSwgbWluaW1pemUtMiwgbWludXMtY2lyY2xlLCBtaW51cy1zcXVhcmUsIG1pbnVzLCBtb25pdG9yLCBtb29uLCBtb3JlLWhvcml6b250YWwsIG1vcmUtdmVydGljYWwsIG1vdmUsIG11c2ljLCBuYXZpZ2F0aW9uLTIsIG5hdmlnYXRpb24sIG9jdGFnb24sIHBhdXNlLWNpcmNsZSwgcGVyY2VudCwgcGx1cy1jaXJjbGUsIHBsdXMtc3F1YXJlLCBwbHVzLCBwb3dlciwgcmFkaW8sIHJzcywgc2VhcmNoLCBzZXJ2ZXIsIHNoYXJlLCBzaG9wcGluZy1iYWcsIHNpZGViYXIsIHNtYXJ0cGhvbmUsIHNtaWxlLCBzcXVhcmUsIHRhYmxlLTIsIHRhYmxldCwgdGFyZ2V0LCB0ZXJtaW5hbCwgdHJhc2gtMiwgdHJhc2gsIHRyaWFuZ2xlLCB0diwgdHlwZSwgdXBsb2FkLCB4LWNpcmNsZSwgeC1vY3RhZ29uLCB4LXNxdWFyZSwgeCwgem9vbS1pbiwgem9vbS1vdXQKClRoZSBNSVQgTGljZW5zZSAoTUlUKSAoZm9yIHRoZSBpY29ucyBsaXN0ZWQgYWJvdmUpCgpDb3B5cmlnaHQgKGMpIDIwMTMtcHJlc2VudCBDb2xlIEJlbWlzCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo8L21ldGFkYXRhPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_ni7": { + "mimeType": "image/svg+xml", + "id": "img_ni7", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwZjc2NmUiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIiAvPgogIDxwYXRoIGQ9Ik0xNiA0aDJhMiAyIDAgMCAxIDIgMnYxNGEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY2YTIgMiAwIDAgMSAyLTJoMiIgLz4KICA8cGF0aCBkPSJNMTIgMTFoNCIgLz4KICA8cGF0aCBkPSJNMTIgMTZoNCIgLz4KICA8cGF0aCBkPSJNOCAxMWguMDEiIC8+CiAgPHBhdGggZD0iTTggMTZoLjAxIiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_ni10": { + "mimeType": "image/svg+xml", + "id": "img_ni10", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwZjc2NmUiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIiAvPgogIDxwYXRoIGQ9Ik0xNiA0aDJhMiAyIDAgMCAxIDIgMnYxNGEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY2YTIgMiAwIDAgMSAyLTJoMiIgLz4KPG1ldGFkYXRhIGlkPSJsdWNpZGUtbGljZW5zZSI+CklTQyBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMjYgTHVjaWRlIEljb25zIGFuZCBDb250cmlidXRvcnMKClBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueQpwdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlCmNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2UgYXBwZWFyIGluIGFsbCBjb3BpZXMuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiBBTkQgVEhFIEFVVEhPUiBESVNDTEFJTVMgQUxMIFdBUlJBTlRJRVMKV0lUSCBSRUdBUkQgVE8gVEhJUyBTT0ZUV0FSRSBJTkNMVURJTkcgQUxMIElNUExJRUQgV0FSUkFOVElFUyBPRgpNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1IgQkUgTElBQkxFIEZPUgpBTlkgU1BFQ0lBTCwgRElSRUNULCBJTkRJUkVDVCwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIE9SIEFOWSBEQU1BR0VTCldIQVRTT0VWRVIgUkVTVUxUSU5HIEZST00gTE9TUyBPRiBVU0UsIERBVEEgT1IgUFJPRklUUywgV0hFVEhFUiBJTiBBTgpBQ1RJT04gT0YgQ09OVFJBQ1QsIE5FR0xJR0VOQ0UgT1IgT1RIRVIgVE9SVElPVVMgQUNUSU9OLCBBUklTSU5HIE9VVCBPRgpPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFVTRSBPUiBQRVJGT1JNQU5DRSBPRiBUSElTIFNPRlRXQVJFLgoKLS0tCgpUaGUgZm9sbG93aW5nIEx1Y2lkZSBpY29ucyBhcmUgZGVyaXZlZCBmcm9tIHRoZSBGZWF0aGVyIHByb2plY3Q6CgphaXJwbGF5LCBhbGVydC1jaXJjbGUsIGFsZXJ0LW9jdGFnb24sIGFsZXJ0LXRyaWFuZ2xlLCBhcGVydHVyZSwgYXJyb3ctZG93bi1jaXJjbGUsIGFycm93LWRvd24tbGVmdCwgYXJyb3ctZG93bi1yaWdodCwgYXJyb3ctZG93biwgYXJyb3ctbGVmdC1jaXJjbGUsIGFycm93LWxlZnQsIGFycm93LXJpZ2h0LWNpcmNsZSwgYXJyb3ctcmlnaHQsIGFycm93LXVwLWNpcmNsZSwgYXJyb3ctdXAtbGVmdCwgYXJyb3ctdXAtcmlnaHQsIGFycm93LXVwLCBhdC1zaWduLCBjYWxlbmRhciwgY2FzdCwgY2hlY2ssIGNoZXZyb24tZG93biwgY2hldnJvbi1sZWZ0LCBjaGV2cm9uLXJpZ2h0LCBjaGV2cm9uLXVwLCBjaGV2cm9ucy1kb3duLCBjaGV2cm9ucy1sZWZ0LCBjaGV2cm9ucy1yaWdodCwgY2hldnJvbnMtdXAsIGNpcmNsZSwgY2xpcGJvYXJkLCBjbG9jaywgY29kZSwgY29sdW1ucywgY29tbWFuZCwgY29tcGFzcywgY29ybmVyLWRvd24tbGVmdCwgY29ybmVyLWRvd24tcmlnaHQsIGNvcm5lci1sZWZ0LWRvd24sIGNvcm5lci1sZWZ0LXVwLCBjb3JuZXItcmlnaHQtZG93biwgY29ybmVyLXJpZ2h0LXVwLCBjb3JuZXItdXAtbGVmdCwgY29ybmVyLXVwLXJpZ2h0LCBjcm9zc2hhaXIsIGRhdGFiYXNlLCBkaXZpZGUtY2lyY2xlLCBkaXZpZGUtc3F1YXJlLCBkb2xsYXItc2lnbiwgZG93bmxvYWQsIGV4dGVybmFsLWxpbmssIGZlYXRoZXIsIGZyb3duLCBoYXNoLCBoZWFkcGhvbmVzLCBoZWxwLWNpcmNsZSwgaW5mbywgaXRhbGljLCBrZXksIGxheW91dCwgbGlmZS1idW95LCBsaW5rLTIsIGxpbmssIGxvYWRlciwgbG9jaywgbG9nLWluLCBsb2ctb3V0LCBtYXhpbWl6ZSwgbWVoLCBtaW5pbWl6ZSwgbWluaW1pemUtMiwgbWludXMtY2lyY2xlLCBtaW51cy1zcXVhcmUsIG1pbnVzLCBtb25pdG9yLCBtb29uLCBtb3JlLWhvcml6b250YWwsIG1vcmUtdmVydGljYWwsIG1vdmUsIG11c2ljLCBuYXZpZ2F0aW9uLTIsIG5hdmlnYXRpb24sIG9jdGFnb24sIHBhdXNlLWNpcmNsZSwgcGVyY2VudCwgcGx1cy1jaXJjbGUsIHBsdXMtc3F1YXJlLCBwbHVzLCBwb3dlciwgcmFkaW8sIHJzcywgc2VhcmNoLCBzZXJ2ZXIsIHNoYXJlLCBzaG9wcGluZy1iYWcsIHNpZGViYXIsIHNtYXJ0cGhvbmUsIHNtaWxlLCBzcXVhcmUsIHRhYmxlLTIsIHRhYmxldCwgdGFyZ2V0LCB0ZXJtaW5hbCwgdHJhc2gtMiwgdHJhc2gsIHRyaWFuZ2xlLCB0diwgdHlwZSwgdXBsb2FkLCB4LWNpcmNsZSwgeC1vY3RhZ29uLCB4LXNxdWFyZSwgeCwgem9vbS1pbiwgem9vbS1vdXQKClRoZSBNSVQgTGljZW5zZSAoTUlUKSAoZm9yIHRoZSBpY29ucyBsaXN0ZWQgYWJvdmUpCgpDb3B5cmlnaHQgKGMpIDIwMTMtcHJlc2VudCBDb2xlIEJlbWlzCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo8L21ldGFkYXRhPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_ni13": { + "mimeType": "image/svg+xml", + "id": "img_ni13", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwZjc2NmUiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIiAvPgogIDxwYXRoIGQ9Ik0xNiA0aDJhMiAyIDAgMCAxIDIgMnYxNGEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY2YTIgMiAwIDAgMSAyLTJoMiIgLz4KPG1ldGFkYXRhIGlkPSJsdWNpZGUtbGljZW5zZSI+CklTQyBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMjYgTHVjaWRlIEljb25zIGFuZCBDb250cmlidXRvcnMKClBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueQpwdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlCmNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2UgYXBwZWFyIGluIGFsbCBjb3BpZXMuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiBBTkQgVEhFIEFVVEhPUiBESVNDTEFJTVMgQUxMIFdBUlJBTlRJRVMKV0lUSCBSRUdBUkQgVE8gVEhJUyBTT0ZUV0FSRSBJTkNMVURJTkcgQUxMIElNUExJRUQgV0FSUkFOVElFUyBPRgpNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1IgQkUgTElBQkxFIEZPUgpBTlkgU1BFQ0lBTCwgRElSRUNULCBJTkRJUkVDVCwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIE9SIEFOWSBEQU1BR0VTCldIQVRTT0VWRVIgUkVTVUxUSU5HIEZST00gTE9TUyBPRiBVU0UsIERBVEEgT1IgUFJPRklUUywgV0hFVEhFUiBJTiBBTgpBQ1RJT04gT0YgQ09OVFJBQ1QsIE5FR0xJR0VOQ0UgT1IgT1RIRVIgVE9SVElPVVMgQUNUSU9OLCBBUklTSU5HIE9VVCBPRgpPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFVTRSBPUiBQRVJGT1JNQU5DRSBPRiBUSElTIFNPRlRXQVJFLgoKLS0tCgpUaGUgZm9sbG93aW5nIEx1Y2lkZSBpY29ucyBhcmUgZGVyaXZlZCBmcm9tIHRoZSBGZWF0aGVyIHByb2plY3Q6CgphaXJwbGF5LCBhbGVydC1jaXJjbGUsIGFsZXJ0LW9jdGFnb24sIGFsZXJ0LXRyaWFuZ2xlLCBhcGVydHVyZSwgYXJyb3ctZG93bi1jaXJjbGUsIGFycm93LWRvd24tbGVmdCwgYXJyb3ctZG93bi1yaWdodCwgYXJyb3ctZG93biwgYXJyb3ctbGVmdC1jaXJjbGUsIGFycm93LWxlZnQsIGFycm93LXJpZ2h0LWNpcmNsZSwgYXJyb3ctcmlnaHQsIGFycm93LXVwLWNpcmNsZSwgYXJyb3ctdXAtbGVmdCwgYXJyb3ctdXAtcmlnaHQsIGFycm93LXVwLCBhdC1zaWduLCBjYWxlbmRhciwgY2FzdCwgY2hlY2ssIGNoZXZyb24tZG93biwgY2hldnJvbi1sZWZ0LCBjaGV2cm9uLXJpZ2h0LCBjaGV2cm9uLXVwLCBjaGV2cm9ucy1kb3duLCBjaGV2cm9ucy1sZWZ0LCBjaGV2cm9ucy1yaWdodCwgY2hldnJvbnMtdXAsIGNpcmNsZSwgY2xpcGJvYXJkLCBjbG9jaywgY29kZSwgY29sdW1ucywgY29tbWFuZCwgY29tcGFzcywgY29ybmVyLWRvd24tbGVmdCwgY29ybmVyLWRvd24tcmlnaHQsIGNvcm5lci1sZWZ0LWRvd24sIGNvcm5lci1sZWZ0LXVwLCBjb3JuZXItcmlnaHQtZG93biwgY29ybmVyLXJpZ2h0LXVwLCBjb3JuZXItdXAtbGVmdCwgY29ybmVyLXVwLXJpZ2h0LCBjcm9zc2hhaXIsIGRhdGFiYXNlLCBkaXZpZGUtY2lyY2xlLCBkaXZpZGUtc3F1YXJlLCBkb2xsYXItc2lnbiwgZG93bmxvYWQsIGV4dGVybmFsLWxpbmssIGZlYXRoZXIsIGZyb3duLCBoYXNoLCBoZWFkcGhvbmVzLCBoZWxwLWNpcmNsZSwgaW5mbywgaXRhbGljLCBrZXksIGxheW91dCwgbGlmZS1idW95LCBsaW5rLTIsIGxpbmssIGxvYWRlciwgbG9jaywgbG9nLWluLCBsb2ctb3V0LCBtYXhpbWl6ZSwgbWVoLCBtaW5pbWl6ZSwgbWluaW1pemUtMiwgbWludXMtY2lyY2xlLCBtaW51cy1zcXVhcmUsIG1pbnVzLCBtb25pdG9yLCBtb29uLCBtb3JlLWhvcml6b250YWwsIG1vcmUtdmVydGljYWwsIG1vdmUsIG11c2ljLCBuYXZpZ2F0aW9uLTIsIG5hdmlnYXRpb24sIG9jdGFnb24sIHBhdXNlLWNpcmNsZSwgcGVyY2VudCwgcGx1cy1jaXJjbGUsIHBsdXMtc3F1YXJlLCBwbHVzLCBwb3dlciwgcmFkaW8sIHJzcywgc2VhcmNoLCBzZXJ2ZXIsIHNoYXJlLCBzaG9wcGluZy1iYWcsIHNpZGViYXIsIHNtYXJ0cGhvbmUsIHNtaWxlLCBzcXVhcmUsIHRhYmxlLTIsIHRhYmxldCwgdGFyZ2V0LCB0ZXJtaW5hbCwgdHJhc2gtMiwgdHJhc2gsIHRyaWFuZ2xlLCB0diwgdHlwZSwgdXBsb2FkLCB4LWNpcmNsZSwgeC1vY3RhZ29uLCB4LXNxdWFyZSwgeCwgem9vbS1pbiwgem9vbS1vdXQKClRoZSBNSVQgTGljZW5zZSAoTUlUKSAoZm9yIHRoZSBpY29ucyBsaXN0ZWQgYWJvdmUpCgpDb3B5cmlnaHQgKGMpIDIwMTMtcHJlc2VudCBDb2xlIEJlbWlzCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo8L21ldGFkYXRhPgo8L3N2Zz4K", + "created": 1700000000000 + }, + "img_ni16": { + "mimeType": "image/svg+xml", + "id": "img_ni16", + "dataURL": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yOSA5QzI5IDExLjc2MTQgMjYuNzYxNCAxNCAyNCAxNEMyMS4yMzg2IDE0IDE5IDExLjc2MTQgMTkgOUMxOSA2LjIzODU4IDIxLjIzODYgNCAyNCA0QzI2Ljc2MTQgNCAyOSA2LjIzODU4IDI5IDlaTTI3IDlDMjcgMTAuNjU2OSAyNS42NTY5IDEyIDI0IDEyQzIyLjM0MzEgMTIgMjEgMTAuNjU2OSAyMSA5QzIxIDcuMzQzMTUgMjIuMzQzMSA2IDI0IDZDMjUuNjU2OSA2IDI3IDcuMzQzMTUgMjcgOVoiIGZpbGw9IiNiNDUzMDkiLz4KPHBhdGggZD0iTTE2Ljk5OTggNDFWMzRIMTguOTk5OFY0MUMxOC45OTk4IDQxLjU1MjMgMTkuNDQ3NSA0MiAxOS45OTk4IDQySDIwLjA4NjVDMjAuNjAzNyA0MiAyMS4wMzU2IDQxLjYwNTYgMjEuMDgyNCA0MS4wOTA1TDIxLjcyNyAzNEgyMy43MzUzTDIzLjA3NDIgNDEuMjcxNkMyMy4wNjkgNDEuMzI4OCAyMy4wNjIyIDQxLjM4NTYgMjMuMDUzOSA0MS40NDE3QzIzLjAzNTcgNDEuNTY0NyAyMy4wMTAxIDQxLjY4NTEgMjIuOTc3NiA0MS44MDI1QzIyLjYyNDIgNDMuMDc5OCAyMS40NTQ5IDQ0IDIwLjA4NjUgNDRIMTkuOTk5OEMxOC4zNDI5IDQ0IDE2Ljk5OTggNDIuNjU2OSAxNi45OTk4IDQxWiIgZmlsbD0iI2I0NTMwOSIvPgo8cGF0aCBkPSJNMjYuOTE3MSA0MS4wOTA1TDI2LjI3MjUgMzRIMjQuMjY0MkwyNC45MjUzIDQxLjI3MTZDMjQuOTMwNSA0MS4zMjg4IDI0LjkzNzMgNDEuMzg1NiAyNC45NDU2IDQxLjQ0MTdDMjQuOTYzOCA0MS41NjQ3IDI0Ljk4OTQgNDEuNjg1MSAyNS4wMjE5IDQxLjgwMjVDMjUuMzc1MyA0My4wNzk4IDI2LjU0NDYgNDQgMjcuOTEzIDQ0SDI3Ljk5OThDMjkuNjU2NiA0NCAzMC45OTk4IDQyLjY1NjkgMzAuOTk5OCA0MVYzNEgyOC45OTk4VjQxQzI4Ljk5OTggNDEuNTUyMyAyOC41NTIgNDIgMjcuOTk5OCA0MkgyNy45MTNDMjcuMzk1OCA0MiAyNi45NjM5IDQxLjYwNTYgMjYuOTE3MSA0MS4wOTA1WiIgZmlsbD0iI2I0NTMwOSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTExIDE1QzEwLjQ0NzcgMTUgMTAgMTUuNDQ3NyAxMCAxNlYzMUMxMCAzMS41NTIzIDEwLjQ0NzcgMzIgMTEgMzJIMzdDMzcuNTUyMyAzMiAzOCAzMS41NTIzIDM4IDMxVjE2QzM4IDE1LjQ0NzcgMzcuNTUyMyAxNSAzNyAxNUgxMVpNMTIgMTdWMzBIMTkuNUMxOS41IDI5LjQ0MjQgMTkuNzU5MSAyOC45NTcyIDIwLjAyNzYgMjguNjAwNkMyMC4zMDg2IDI4LjIyNzYgMjAuNjc5NiAyNy44ODgzIDIxLjA3OTQgMjcuNjAxNkMyMS42MjUyIDI3LjIxMDMgMjIuMjk4NiAyNi44NjUgMjMgMjYuNjY3OFYyNC41SDIyLjJDMjEuNTk3MyAyNC41IDIwLjc1MzUgMjQuODY0MiAxOS45NDMzIDI1LjM1NTJDMTkuNTYzOCAyNS41ODUyIDE5LjIzMzcgMjUuODE2NyAxOC45OTg0IDI1Ljk5MTFDMTguODgxMiAyNi4wNzc4IDE4Ljc4ODggMjYuMTQ5NSAxOC43MjcgMjYuMTk4NEMxOC42OTYxIDI2LjIyMjggMTguNjczIDI2LjI0MTUgMTguNjU4MiAyNi4yNTM1TDE4LjY0MjYgMjYuMjY2M0wxOC42Mzk5IDI2LjI2ODRDMTguNjM5OSAyNi4yNjg0IDE4LjY0MDIgMjYuMjY4MiAxOCAyNS41QzE3LjM1OTggMjQuNzMxOCAxNy4zNjAxIDI0LjczMTUgMTcuMzYwMSAyNC43MzE1TDE3LjM2MTQgMjQuNzMwNUwxNy4zNjM4IDI0LjcyODVMMTcuMzcxMyAyNC43MjIzTDE3LjM5NjYgMjQuNzAxNkMxNy40MTc5IDI0LjY4NDMgMTcuNDQ4IDI0LjY2IDE3LjQ4NjMgMjQuNjI5N0MxNy41NjI3IDI0LjU2OTMgMTcuNjcxOSAyNC40ODQ3IDE3LjgwNzkgMjQuMzgzOUMxOC4wNzg4IDI0LjE4MzMgMTguNDYxMiAyMy45MTQ4IDE4LjkwNjcgMjMuNjQ0OEMxOS43NDY1IDIzLjEzNTggMjEuMDAyNyAyMi41IDIyLjIgMjIuNUgyM1YyMC41SDIyLjVDMjEuOTMwOSAyMC41IDIxLjE5MDkgMjAuNjk5MiAyMC41MjkyIDIwLjkzOThDMjAuMjEyIDIxLjA1NTIgMTkuOTM3IDIxLjE3MDkgMTkuNzQyMSAyMS4yNTc2QzE5LjY0NDkgMjEuMzAwNyAxOS41Njg1IDIxLjMzNjMgMTkuNTE3NiAyMS4zNjA1QzE5LjQ5MjIgMjEuMzcyNSAxOS40NzMyIDIxLjM4MTcgMTkuNDYxMyAyMS4zODc1TDE5LjQ0ODcgMjEuMzkzN0wxOS40NDcgMjEuMzk0NUMxOS40NDcgMjEuMzk0NSAxOS40NDcyIDIxLjM5NDQgMTkgMjAuNUMxOC41NTI4IDE5LjYwNTYgMTguNTUzMSAxOS42MDU0IDE4LjU1MzEgMTkuNjA1NEwxOC41NTY0IDE5LjYwMzhMMTguNTYyOSAxOS42MDA2TDE4LjU4NDQgMTkuNTlDMTguNjAyNSAxOS41ODEyIDE4LjYyNzkgMTkuNTY4OSAxOC42NjAxIDE5LjU1MzZDMTguNzI0NSAxOS41MjMxIDE4LjgxNiAxOS40ODA1IDE4LjkyOTggMTkuNDI5OUMxOS4xNTY3IDE5LjMyOTEgMTkuNDc1NSAxOS4xOTQ4IDE5Ljg0NTggMTkuMDYwMkMyMC41NTkxIDE4LjgwMDggMjEuNTY5MSAxOC41IDIyLjUgMTguNUgyM1YxN0gxMlpNMjUgMjQuNUgyNi4xQzI2Ljg0ODkgMjQuNSAyNy42MTg2IDI0Ljg2NjMgMjguMjYzMSAyNS4zMTg2QzI4LjU3MzUgMjUuNTM2NCAyOC44Mjg1IDI1Ljc1NTYgMjkuMDA0OCAyNS45MTk3QzI5LjA5MjUgMjYuMDAxMiAyOS4xNTk0IDI2LjA2NzkgMjkuMjAyNiAyNi4xMTIyQzI5LjIyNDEgMjYuMTM0MyAyOS4yMzk3IDI2LjE1MDcgMjkuMjQ4OCAyNi4xNjA1TDI5LjI1NzQgMjYuMTY5N0MyOS4yNTc0IDI2LjE2OTcgMjkuMjU2NyAyNi4xNjkgMzAgMjUuNUMzMC43NDMzIDI0LjgzMSAzMC43NDI5IDI0LjgzMDYgMzAuNzQyOSAyNC44MzA2TDMwLjc0MTQgMjQuODI5TDMwLjczODkgMjQuODI2MkwzMC43MzE4IDI0LjgxODRMMzAuNzA5NyAyNC43OTQ2QzMwLjY5MTUgMjQuNzc1MSAzMC42NjYzIDI0Ljc0ODUgMzAuNjM0NiAyNC43MTZDMzAuNTcxMSAyNC42NTA5IDMwLjQ4MDkgMjQuNTYxMyAzMC4zNjcgMjQuNDU1M0MzMC4xNDAzIDI0LjI0NDQgMjkuODE0IDIzLjk2MzYgMjkuNDExOSAyMy42ODE0QzI4LjYzMTQgMjMuMTMzNyAyNy40NTExIDIyLjUgMjYuMSAyMi41SDI1VjIwLjVIMjUuNzVDMjYuNDQ0MyAyMC41IDI3LjExOTMgMjAuNzAxMSAyNy42NDMyIDIwLjkyMTZDMjcuOTAwMyAyMS4wMjk5IDI4LjEwOTIgMjEuMTM3OCAyOC4yNTA5IDIxLjIxNjlDMjguMzIxNSAyMS4yNTY0IDI4LjM3NDcgMjEuMjg4MiAyOC40MDggMjEuMzA4N0MyOC40MjQ3IDIxLjMxODkgMjguNDM2MyAyMS4zMjYzIDI4LjQ0MjcgMjEuMzMwNEwyOC40NDU3IDIxLjMzMjNDMjguNDQ1NyAyMS4zMzIzIDI4LjQ0NTMgMjEuMzMyMSAyOSAyMC41QzI5LjU1NDcgMTkuNjY3OSAyOS41NTQyIDE5LjY2NzYgMjkuNTU0MiAxOS42Njc2TDI5LjU1MjcgMTkuNjY2NkwyOS41NTAxIDE5LjY2NDlMMjkuNTQzMiAxOS42NjA0TDI5LjUyMjggMTkuNjQ3MkMyOS41MDYzIDE5LjYzNjYgMjkuNDg0IDE5LjYyMjUgMjkuNDU2MiAxOS42MDU0QzI5LjQwMDcgMTkuNTcxMiAyOS4zMjMgMTkuNTI0OSAyOS4yMjU2IDE5LjQ3MDZDMjkuMDMxNSAxOS4zNjIyIDI4Ljc1NTkgMTkuMjIwMSAyOC40MTkzIDE5LjA3ODRDMjcuNzU1NyAxOC43OTg5IDI2LjgwNTcgMTguNSAyNS43NSAxOC41SDI1VjE3SDM2VjMwSDI4LjVDMjguNSAyOS40Mzc5IDI4LjIzMjQgMjguOTQ1MiAyNy45NjQxIDI4LjU4NzVDMjcuNjgwMyAyOC4yMDkyIDI3LjMwNTQgMjcuODYwNiAyNi45MDA3IDI3LjU2NjNDMjYuMzY3NSAyNy4xNzg1IDI1LjY5ODkgMjYuODI1NyAyNSAyNi42MzkxVjI0LjVaTTI2LjQ5MjkgMzBDMjYuNDc5NyAyOS45NjcgMjYuNDQ1NyAyOS44OTY0IDI2LjM2NDEgMjkuNzg3NUMyNi4yMjU5IDI5LjYwMzMgMjYuMDA3MSAyOS4zODk0IDI1LjcyNDMgMjkuMTgzN0MyNS4xNDc4IDI4Ljc2NDUgMjQuNDkzNyAyOC41MTE3IDI0LjAzMiAyOC41MDA0QzIzLjQ4NzcgMjguNTQ2NCAyMi44MDUyIDI4LjgyNTIgMjIuMjQ0OSAyOS4yMjdDMjEuOTY1NyAyOS40MjcyIDIxLjc1NTYgMjkuNjMwNyAyMS42MjUyIDI5LjgwMzhDMjEuNTQ5NiAyOS45MDQzIDIxLjUxODQgMjkuOTY5NCAyMS41MDYzIDMwSDI2LjQ5MjlaIiBmaWxsPSIjYjQ1MzA5Ii8+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_ni19": { + "mimeType": "image/svg+xml", + "id": "img_ni19", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwNTk2NjkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyLjU4NiAyLjU4NkEyIDIgMCAwIDAgMTEuMTcyIDJINGEyIDIgMCAwIDAtMiAydjcuMTcyYTIgMiAwIDAgMCAuNTg2IDEuNDE0bDguNzA0IDguNzA0YTIuNDI2IDIuNDI2IDAgMCAwIDMuNDIgMGw2LjU4LTYuNThhMi40MjYgMi40MjYgMCAwIDAgMC0zLjQyeiIgLz4KICA8Y2lyY2xlIGN4PSI3LjUiIGN5PSI3LjUiIHI9Ii41IiBmaWxsPSIjMDU5NjY5IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_ni22": { + "mimeType": "image/svg+xml", + "id": "img_ni22", + "dataURL": "data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9IiMwNTk2NjkiCiAgc3Ryb2tlLXdpZHRoPSIyIgogIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICBzdHJva2UtbGluZWpvaW49InJvdW5kIgo+CiAgPHBhdGggZD0iTTEyLjU4NiAyLjU4NkEyIDIgMCAwIDAgMTEuMTcyIDJINGEyIDIgMCAwIDAtMiAydjcuMTcyYTIgMiAwIDAgMCAuNTg2IDEuNDE0bDguNzA0IDguNzA0YTIuNDI2IDIuNDI2IDAgMCAwIDMuNDIgMGw2LjU4LTYuNThhMi40MjYgMi40MjYgMCAwIDAgMC0zLjQyeiIgLz4KICA8Y2lyY2xlIGN4PSI3LjUiIGN5PSI3LjUiIHI9Ii41IiBmaWxsPSIjMDU5NjY5IiAvPgo8bWV0YWRhdGEgaWQ9Imx1Y2lkZS1saWNlbnNlIj4KSVNDIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBMdWNpZGUgSWNvbnMgYW5kIENvbnRyaWJ1dG9ycwoKUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55CnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gYWxsIGNvcGllcy4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUwpXSVRIIFJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SCkFOWSBTUEVDSUFMLCBESVJFQ1QsIElORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMKV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTSBMT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOCkFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUiBPVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GCk9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SIFBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuCgotLS0KClRoZSBmb2xsb3dpbmcgTHVjaWRlIGljb25zIGFyZSBkZXJpdmVkIGZyb20gdGhlIEZlYXRoZXIgcHJvamVjdDoKCmFpcnBsYXksIGFsZXJ0LWNpcmNsZSwgYWxlcnQtb2N0YWdvbiwgYWxlcnQtdHJpYW5nbGUsIGFwZXJ0dXJlLCBhcnJvdy1kb3duLWNpcmNsZSwgYXJyb3ctZG93bi1sZWZ0LCBhcnJvdy1kb3duLXJpZ2h0LCBhcnJvdy1kb3duLCBhcnJvdy1sZWZ0LWNpcmNsZSwgYXJyb3ctbGVmdCwgYXJyb3ctcmlnaHQtY2lyY2xlLCBhcnJvdy1yaWdodCwgYXJyb3ctdXAtY2lyY2xlLCBhcnJvdy11cC1sZWZ0LCBhcnJvdy11cC1yaWdodCwgYXJyb3ctdXAsIGF0LXNpZ24sIGNhbGVuZGFyLCBjYXN0LCBjaGVjaywgY2hldnJvbi1kb3duLCBjaGV2cm9uLWxlZnQsIGNoZXZyb24tcmlnaHQsIGNoZXZyb24tdXAsIGNoZXZyb25zLWRvd24sIGNoZXZyb25zLWxlZnQsIGNoZXZyb25zLXJpZ2h0LCBjaGV2cm9ucy11cCwgY2lyY2xlLCBjbGlwYm9hcmQsIGNsb2NrLCBjb2RlLCBjb2x1bW5zLCBjb21tYW5kLCBjb21wYXNzLCBjb3JuZXItZG93bi1sZWZ0LCBjb3JuZXItZG93bi1yaWdodCwgY29ybmVyLWxlZnQtZG93biwgY29ybmVyLWxlZnQtdXAsIGNvcm5lci1yaWdodC1kb3duLCBjb3JuZXItcmlnaHQtdXAsIGNvcm5lci11cC1sZWZ0LCBjb3JuZXItdXAtcmlnaHQsIGNyb3NzaGFpciwgZGF0YWJhc2UsIGRpdmlkZS1jaXJjbGUsIGRpdmlkZS1zcXVhcmUsIGRvbGxhci1zaWduLCBkb3dubG9hZCwgZXh0ZXJuYWwtbGluaywgZmVhdGhlciwgZnJvd24sIGhhc2gsIGhlYWRwaG9uZXMsIGhlbHAtY2lyY2xlLCBpbmZvLCBpdGFsaWMsIGtleSwgbGF5b3V0LCBsaWZlLWJ1b3ksIGxpbmstMiwgbGluaywgbG9hZGVyLCBsb2NrLCBsb2ctaW4sIGxvZy1vdXQsIG1heGltaXplLCBtZWgsIG1pbmltaXplLCBtaW5pbWl6ZS0yLCBtaW51cy1jaXJjbGUsIG1pbnVzLXNxdWFyZSwgbWludXMsIG1vbml0b3IsIG1vb24sIG1vcmUtaG9yaXpvbnRhbCwgbW9yZS12ZXJ0aWNhbCwgbW92ZSwgbXVzaWMsIG5hdmlnYXRpb24tMiwgbmF2aWdhdGlvbiwgb2N0YWdvbiwgcGF1c2UtY2lyY2xlLCBwZXJjZW50LCBwbHVzLWNpcmNsZSwgcGx1cy1zcXVhcmUsIHBsdXMsIHBvd2VyLCByYWRpbywgcnNzLCBzZWFyY2gsIHNlcnZlciwgc2hhcmUsIHNob3BwaW5nLWJhZywgc2lkZWJhciwgc21hcnRwaG9uZSwgc21pbGUsIHNxdWFyZSwgdGFibGUtMiwgdGFibGV0LCB0YXJnZXQsIHRlcm1pbmFsLCB0cmFzaC0yLCB0cmFzaCwgdHJpYW5nbGUsIHR2LCB0eXBlLCB1cGxvYWQsIHgtY2lyY2xlLCB4LW9jdGFnb24sIHgtc3F1YXJlLCB4LCB6b29tLWluLCB6b29tLW91dAoKVGhlIE1JVCBMaWNlbnNlIChNSVQpIChmb3IgdGhlIGljb25zIGxpc3RlZCBhYm92ZSkKCkNvcHlyaWdodCAoYykgMjAxMy1wcmVzZW50IENvbGUgQmVtaXMKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCjwvbWV0YWRhdGE+Cjwvc3ZnPgo=", + "created": 1700000000000 + }, + "img_ni31": { + "mimeType": "image/svg+xml", + "id": "img_ni31", + "dataURL": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMyA2LjAwMDEzQzIzIDYuMDAwMDQgMjMgNiAyNCA2SDI0LjE3NjFDMjQuMjUzMyA2IDI0LjMyMzMgNiAyNC4zODY4IDZDMjUgNi4wMDAwMSAyNSA2LjAwMDA1IDI1IDYuMDAwMTNWMjAuNUMyNSAyMS44OTc3IDI1LjU2NDEgMjIuNDQyMSAyNi4wMDM5IDIyLjY5ODdDMjYuMDUzIDIyLjcyNzMgMjYuMTAyNCAyMi43NTMzIDI2LjE1MTYgMjIuNzc3QzI2LjE1MDYgMjIuNjU2OCAyNi4xNDkzIDIyLjUzMTYgMjYuMTQ3OSAyMi40MDE5TDI2LjE0NzkgMjIuNDAwM0MyNi4xMTAxIDE4Ljg3MTYgMjYuMDM2NSAxMS45OTk3IDMxLjQ3NTMgMTEuOTk5N0MzOC45MjU5IDExLjk5OTcgNDQuMjQ3OCAzOC4wMDYyIDQxLjA1NDcgNDAuNjU5OUMzNy44NjE1IDQzLjMxMzYgMzEuNDc1MyA0MS43MjE0IDI4LjI4MjIgMzguNTM2OUMyNS43MDQyIDM1Ljk2NTkgMjUuOTI3MiAyOS45NTgzIDI2LjA4NzMgMjUuNjQ1N0wyNi4wODc0IDI1LjY0NDNDMjYuMDk3MSAyNS4zODE5IDI2LjEwNjcgMjUuMTI1OCAyNi4xMTUzIDI0Ljg3NzFDMjUuNzkgMjQuNzk2MiAyNS4zOTU5IDI0LjY1OTUgMjQuOTk2MiAyNC40MjYzQzI0LjYzOTggMjQuMjE4NCAyNC4yOTc1IDIzLjk0NDIgMjQgMjMuNTkxOUMyMy43MDI2IDIzLjk0NDIgMjMuMzYwMyAyNC4yMTg0IDIzLjAwMzkgMjQuNDI2M0MyMi42MDQxIDI0LjY1OTUgMjIuMjEwMSAyNC43OTYyIDIxLjg4NDggMjQuODc3MUMyMS44OTM0IDI1LjEyNjEgMjEuOTAyOSAyNS4zODI2IDIxLjkxMjcgMjUuNjQ1M0MyMi4wNzI4IDI5Ljk1NzkgMjIuMjk1OSAzNS45NjYyIDE5LjcxNzkgMzguNTM3MkMxNi41MjQ3IDQxLjcyMTcgMTAuMTM4NSA0My4zMTM5IDYuOTQ1MzYgNDAuNjYwMkMzLjc1MjIyIDM4LjAwNjUgOS4wNzQxMyAxMiAxNi41MjQ3IDEyQzIxLjk2MzYgMTIgMjEuODkgMTguODcxNyAyMS44NTIyIDIyLjQwMDNDMjEuODUyIDIyLjQxNjYgMjEuODUxOCAyMi40MzI4IDIxLjg1MTYgMjIuNDQ4OUMyMS44NTA0IDIyLjU2MTkgMjEuODQ5MyAyMi42NzE0IDIxLjg0ODQgMjIuNzc3QzIxLjg5NzcgMjIuNzUzNCAyMS45NDcxIDIyLjcyNzQgMjEuOTk2MiAyMi42OTg3QzIyLjQzNiAyMi40NDIxIDIzIDIxLjg5NzcgMjMgMjAuNVY2LjAwMDEzWk0zMS40NzUzIDEzLjk5OTdDMzAuNTIxMiAxMy45OTk3IDI5Ljk4NDMgMTQuMzAwMiAyOS42MDg2IDE0LjY4NjlDMjkuMTc1MiAxNS4xMzMxIDI4LjgxMzIgMTUuODUwNyAyOC41NTkgMTYuODY0NkMyOC4xMTQxIDE4LjYzOTUgMjguMTMzIDIwLjY4MTkgMjguMTQ4MyAyMi4zMjkzQzI4LjE1MDkgMjIuNjE0MiAyOC4xNTM0IDIyLjg4NzQgMjguMTUzNCAyMy4xNDUzQzI4LjE1MzQgMjMuOTA1MyAyOC4xMTk2IDI0LjgxNTMgMjguMDg0NSAyNS43NjIxQzI4LjA3MDcgMjYuMTM0OSAyOC4wNTY2IDI2LjUxMzMgMjguMDQ0MyAyNi44OTA1QzI3Ljk5OTYgMjguMjY1NyAyNy45NzQxIDI5LjcwNTUgMjguMDQwNSAzMS4wOTgyQzI4LjEwNzIgMzIuNDk4NSAyOC4yNjQyIDMzLjc4MzUgMjguNTU3MyAzNC44NTk2QzI4Ljg1MzggMzUuOTQ4MyAyOS4yNTA5IDM2LjY3ODQgMjkuNjk0NCAzNy4xMjA4QzMwLjk3MjYgMzguMzk1NCAzMi45OTA4IDM5LjQyNTEgMzUuMDQ5MiAzOS44MjQzQzM3LjA4NDIgNDAuMjE4OSAzOC43MjI0IDM5LjkyNjkgMzkuNjgxOCAzOS4xOTY5QzM5LjY4NjggMzkuMTg1NyAzOS42OTIzIDM5LjE3MjYgMzkuNjk4NCAzOS4xNTc0QzM5Ljc1NzYgMzkuMDA5NSAzOS44Mjk0IDM4Ljc1NTQgMzkuODg3OCAzOC4zNjgzQzQwLjAwNDcgMzcuNTkzMyA0MC4wMzUgMzYuNTEyMiAzOS45NTYyIDM1LjE4MDNDMzkuNzk5NSAzMi41MzA1IDM5LjIyODYgMjkuMTY4OSAzOC4zMjY4IDI1Ljg2MDZDMzcuNDIxNiAyMi41Mzk2IDM2LjIxNzMgMTkuMzk5OCAzNC44NTIxIDE3LjEzODNDMzQuMTY4OCAxNi4wMDYyIDMzLjQ5NTEgMTUuMTc4NiAzMi44NzAzIDE0LjY1MzVDMzIuMjUxOCAxNC4xMzM3IDMxLjc5MzUgMTMuOTk5NyAzMS40NzUzIDEzLjk5OTdaTTE2LjUyNDcgMTRDMTcuNDc4OCAxNCAxOC4wMTU4IDE0LjMwMDUgMTguMzkxNCAxNC42ODcyQzE4LjgyNDggMTUuMTMzNCAxOS4xODY4IDE1Ljg1MSAxOS40NDEgMTYuODY0OUMxOS44ODU5IDE4LjYzOTkgMTkuODY3IDIwLjY4MjIgMTkuODUxOCAyMi4zMjk2QzE5Ljg0OTEgMjIuNjE0NiAxOS44NDY2IDIyLjg4NzcgMTkuODQ2NiAyMy4xNDU2QzE5Ljg0NjYgMjMuOTA1NiAxOS44ODA0IDI0LjgxNTYgMTkuOTE1NSAyNS43NjI0QzE5LjkyOTQgMjYuMTM1IDE5Ljk0MzQgMjYuNTEzOCAxOS45NTU3IDI2Ljg5MDhDMjAuMDAwNSAyOC4yNjYgMjAuMDI1OSAyOS43MDU4IDE5Ljk1OTYgMzEuMDk4NUMxOS44OTI5IDMyLjQ5ODggMTkuNzM1OSAzMy43ODM4IDE5LjQ0MjcgMzQuODU5OUMxOS4xNDYyIDM1Ljk0ODYgMTguNzQ5MiAzNi42Nzg3IDE4LjMwNTYgMzcuMTIxMUMxNy4wMjc1IDM4LjM5NTcgMTUuMDA5MyAzOS40MjU0IDEyLjk1MDkgMzkuODI0NkMxMC45MTU5IDQwLjIxOTIgOS4yNzc2OCAzOS45MjcyIDguMzE4MjMgMzkuMTk3MkM4LjMxMzI2IDM5LjE4NiA4LjMwNzcgMzkuMTcyOSA4LjMwMTYyIDM5LjE1NzdDOC4yNDI0MSAzOS4wMDk4IDguMTcwNjEgMzguNzU1NyA4LjExMjI0IDM4LjM2ODZDNy45OTUzOSAzNy41OTM2IDcuOTY1IDM2LjUxMjUgOC4wNDM4IDM1LjE4MDZDOC4yMDA1NiAzMi41MzA4IDguNzcxNDcgMjkuMTY5MiA5LjY3MzI0IDI1Ljg2MDlDMTAuNTc4NSAyMi41Mzk5IDExLjc4MjcgMTkuNDAwMSAxMy4xNDc5IDE3LjEzODZDMTMuODMxMyAxNi4wMDY1IDE0LjUwNDkgMTUuMTc4OSAxNS4xMjk4IDE0LjY1MzhDMTUuNzQ4MiAxNC4xMzQgMTYuMjA2NSAxNCAxNi41MjQ3IDE0WiIgZmlsbD0iIzFkNGVkOCIvPgo8L3N2Zz4K", + "created": 1700000000000 + } + } +} \ No newline at end of file diff --git a/tools/diagrams/figures.toml b/tools/diagrams/figures.toml new file mode 100644 index 0000000..7770879 --- /dev/null +++ b/tools/diagrams/figures.toml @@ -0,0 +1,97 @@ +# Where each Excalidraw figure is written and published. +# +# One table per figure, keyed by figure name. Builders look up their own +# `excalidraw` path here by name (excalib.figure()); render_excalidraw.py +# --figure / --all renders `excalidraw` to `svg` and to a review PNG. +# +# builder builder script (repo-relative) +# args optional extra arguments the builder needs for this figure +# excalidraw where the builder writes the .excalidraw (repo-relative) +# svg where the rendered SVG goes (repo-relative); omit if the +# figure has no published SVG +# +# All paths are relative to the repository root, not the working directory. + +# Review PNGs always go here as .png (ignored directory). +review_png_dir = "scratch/review/renders" + +# ---------------------------------------------------------------- accepted + +[two-planes] +builder = "tools/diagrams/build_two_planes.py" +excalidraw = "tools/diagrams/excalidraw/two-planes.excalidraw" +svg = "site/content/two-planes.svg" + +[three-axes] +builder = "tools/diagrams/build_three_axes.py" +excalidraw = "tools/diagrams/excalidraw/three-axes.excalidraw" +svg = "site/content/foundation-context/three-axes.svg" + +[foundation-network] +builder = "tools/diagrams/build_foundation_network.py" +excalidraw = "tools/diagrams/excalidraw/foundation-network.excalidraw" +svg = "site/content/foundation-context/foundation-network.svg" + +# ------------------------------------- candidates awaiting owner acceptance + +[pillars-b3] +builder = "tools/diagrams/variants/build_pillars_b3.py" +excalidraw = "tools/diagrams/excalidraw/pillars-b3.excalidraw" +svg = "scratch/review/candidates/pillars-b3.svg" + +[two-collections] +builder = "tools/diagrams/build_two_collections.py" +excalidraw = "tools/diagrams/excalidraw/two-collections.excalidraw" +svg = "scratch/review/candidates/two-collections.svg" + +[nodule-neighborhood] +builder = "tools/diagrams/build_nodule_neighborhood.py" +excalidraw = "tools/diagrams/excalidraw/nodule-neighborhood.excalidraw" +svg = "scratch/review/candidates/nodule-neighborhood.svg" + +# ------------------------------------------------- rejected pillars variants + +[pillars-a] +builder = "tools/diagrams/build_pillars.py" +args = ["--seam", "a"] +excalidraw = "scratch/review/candidates/pillars-a.excalidraw" +svg = "scratch/review/candidates/pillars-a.svg" + +[pillars-b] +builder = "tools/diagrams/build_pillars.py" +args = ["--seam", "b"] +excalidraw = "scratch/review/candidates/pillars-b.excalidraw" +svg = "scratch/review/candidates/pillars-b.svg" + +[pillars-c] +builder = "tools/diagrams/build_pillars.py" +args = ["--seam", "c"] +excalidraw = "scratch/review/candidates/pillars-c.excalidraw" +svg = "scratch/review/candidates/pillars-c.svg" + +[pillars-b1] +builder = "tools/diagrams/variants/build_pillars_b1.py" +excalidraw = "scratch/review/candidates/pillars-b1.excalidraw" +svg = "scratch/review/candidates/pillars-b1.svg" + +[pillars-b2] +builder = "tools/diagrams/variants/build_pillars_b2.py" +excalidraw = "scratch/review/candidates/pillars-b2.excalidraw" +svg = "scratch/review/candidates/pillars-b2.svg" + +# ------------------------------- knowledge-graph figures (separate track) + +[architecture] +builder = "tools/diagrams/build_architecture.py" +excalidraw = "knowledge/overview/architecture.excalidraw" +svg = "knowledge/overview/architecture.svg" + +[hierarchy] +builder = "tools/diagrams/build_hierarchy.py" +excalidraw = "knowledge/data-structures/hierarchy.excalidraw" +svg = "knowledge/data-structures/hierarchy.svg" + +[repository-map] +builder = "tools/diagrams/build_repository_map.py" +excalidraw = "knowledge/repositories/repository-map.excalidraw" +svg = "knowledge/repositories/repository-map.svg" diff --git a/tools/diagrams/icons/LICENSES.md b/tools/diagrams/icons/LICENSES.md new file mode 100644 index 0000000..97d7c63 --- /dev/null +++ b/tools/diagrams/icons/LICENSES.md @@ -0,0 +1,72 @@ +# Icon sources + +Icons used by the diagram builders. The source artwork is unchanged. Lucide SVGs +include nonvisual license metadata added here. Recoloring and resizing happen +at build time, preserving that metadata in embedded copies. + +## Health Icons + + — outline style. Source repository: +[`resolvetosavelives/healthicons`](https://github.com/resolvetosavelives/healthicons) +(branch `main`). The project site states the icon designs are released under **CC0** +(public domain); the repository's own `LICENSE` file (covering the repo/tooling) is MIT, +copyright Resolve to Save Lives. Either way, reuse with attribution here is unrestricted. + +| File | Source path | Used for | +|---|---|---| +| `healthicons-lungs.svg` | `public/icons/svg/outline/body/lungs.svg` | Anatomic locations (Foundation Context) | +| `healthicons-xray.svg` | `public/icons/svg/outline/devices/xray.svg` | Exam types (Foundation Context) | +| `healthicons-person.svg` | `public/icons/svg/outline/people/person.svg` | Imaging Persona (Data Structures) | + +Note: Health Icons' outline set has no clipboard/report-style icon (searched the full tree +for "clipboard", "report", "record", "chart", "form", "doc" — nothing fits). Per the brief, +Lucide's `clipboard` and `clipboard-list` are used instead for Observation and Exam Finding +List. No CT-scanner icon exists either; `xray.svg` (x-ray machine) stands in for the exam +types glyph. + +## Lucide + +. Source repository: +[`lucide-icons/lucide`](https://github.com/lucide-icons/lucide) (branch `main`), path +`icons/.svg`. Lucide icons are ISC-licensed by Lucide Icons and Contributors, **except** +a subset carried over from the Feather icon project, which stays under Feather's original MIT +license (Copyright Cole Bemis). Of the icons used here, only `clipboard` is in that +Feather-derived subset. + +The complete upstream copyright, permission, and warranty notices are in +[`LUCIDE-LICENSE.txt`](LUCIDE-LICENSE.txt), copied verbatim from +[Lucide revision `20aef31`](https://github.com/lucide-icons/lucide/blob/20aef31db93fe4d9f398dbb15b120d99c48eef95/LICENSE). +This pins the license copy, not the earlier icon downloads. Each Lucide SVG +also embeds these notices in its `` element. +Keep this metadata when copying or optimizing SVGs. Excalidraw files and SVG +diagrams retain it inside their embedded icon images. Distribute PNG exports +with the accompanying `.LICENSE.txt` generated by the renderer. + +| File | Source path | Used for | License | +|---|---|---|---| +| `lucide-book-open.svg` | `icons/book-open.svg` | Foundation Context band | ISC | +| `lucide-tag.svg` | `icons/tag.svg` | Finding/diagnosis definitions | ISC | +| `lucide-layers.svg` | `icons/layers.svg` | Data Structures band | ISC | +| `lucide-list.svg` | `icons/list.svg` | Imaging Problem List | ISC | +| `lucide-package.svg` | `icons/package.svg` | SDKs band (code-2 has no matching filename in the current icon set; package is the listed alternative) | ISC | +| `lucide-lightbulb.svg` | `icons/lightbulb.svg` | Use Cases | ISC | +| `lucide-app-window.svg` | `icons/app-window.svg` | Sample Applications | ISC | +| `lucide-clipboard.svg` | `icons/clipboard.svg` | Observation (Health Icons has no equivalent) | MIT (Feather) | +| `lucide-clipboard-list.svg` | `icons/clipboard-list.svg` | Exam Finding List (Health Icons has no equivalent) | ISC | + +## Harmonizing style + +- All icons are rendered at the same on-page size (~28px at 900px diagram width). +- Lucide icons are stroke-based (`stroke="currentColor"`, `stroke-width="2"`, 24x24 viewBox) + — the build script replaces `currentColor` with the target band's dark hex. +- Health Icons' outline set is filled-path-based (`fill="currentColor"`, 48x48 viewBox), not + stroke-based; there is no stroke-width to match, but at matched render size the two sets' + line weights read the same. The build script replaces `currentColor` with the target hex + the same way. + +The "relationships" glyph (three dots joined by lines, repeated between the Foundation +Context blocks) is hand-drawn with primitive ellipses/lines in `excalib.py`, not sourced +from an icon set — neither Health Icons nor Lucide's `network` icon (which is box-based, +not three dots) matched the brief's description closely enough. + +Reused without additions by build_three_axes.py (tag, lungs, xray) and build_two_planes.py (layers, book-open, clipboard-list, clipboard, xray, lungs, tag), 2026-09-28. diff --git a/tools/diagrams/icons/LUCIDE-LICENSE.txt b/tools/diagrams/icons/LUCIDE-LICENSE.txt new file mode 100644 index 0000000..718bb3f --- /dev/null +++ b/tools/diagrams/icons/LUCIDE-LICENSE.txt @@ -0,0 +1,43 @@ +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/diagrams/icons/healthicons-lungs.svg b/tools/diagrams/icons/healthicons-lungs.svg new file mode 100644 index 0000000..6651458 --- /dev/null +++ b/tools/diagrams/icons/healthicons-lungs.svg @@ -0,0 +1,3 @@ + + + diff --git a/tools/diagrams/icons/healthicons-person.svg b/tools/diagrams/icons/healthicons-person.svg new file mode 100644 index 0000000..41e9d62 --- /dev/null +++ b/tools/diagrams/icons/healthicons-person.svg @@ -0,0 +1,4 @@ + + + + diff --git a/tools/diagrams/icons/healthicons-xray.svg b/tools/diagrams/icons/healthicons-xray.svg new file mode 100644 index 0000000..2f35233 --- /dev/null +++ b/tools/diagrams/icons/healthicons-xray.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/tools/diagrams/icons/lucide-app-window.svg b/tools/diagrams/icons/lucide-app-window.svg new file mode 100644 index 0000000..5e3d8fb --- /dev/null +++ b/tools/diagrams/icons/lucide-app-window.svg @@ -0,0 +1,61 @@ + + + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/icons/lucide-book-open.svg b/tools/diagrams/icons/lucide-book-open.svg new file mode 100644 index 0000000..603ff55 --- /dev/null +++ b/tools/diagrams/icons/lucide-book-open.svg @@ -0,0 +1,59 @@ + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/icons/lucide-clipboard-list.svg b/tools/diagrams/icons/lucide-clipboard-list.svg new file mode 100644 index 0000000..6dd3d64 --- /dev/null +++ b/tools/diagrams/icons/lucide-clipboard-list.svg @@ -0,0 +1,63 @@ + + + + + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/icons/lucide-clipboard.svg b/tools/diagrams/icons/lucide-clipboard.svg new file mode 100644 index 0000000..f1a63a6 --- /dev/null +++ b/tools/diagrams/icons/lucide-clipboard.svg @@ -0,0 +1,59 @@ + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/icons/lucide-layers.svg b/tools/diagrams/icons/lucide-layers.svg new file mode 100644 index 0000000..c507ae8 --- /dev/null +++ b/tools/diagrams/icons/lucide-layers.svg @@ -0,0 +1,60 @@ + + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/icons/lucide-lightbulb.svg b/tools/diagrams/icons/lucide-lightbulb.svg new file mode 100644 index 0000000..03023ea --- /dev/null +++ b/tools/diagrams/icons/lucide-lightbulb.svg @@ -0,0 +1,60 @@ + + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/icons/lucide-list.svg b/tools/diagrams/icons/lucide-list.svg new file mode 100644 index 0000000..1bca451 --- /dev/null +++ b/tools/diagrams/icons/lucide-list.svg @@ -0,0 +1,63 @@ + + + + + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/icons/lucide-package.svg b/tools/diagrams/icons/lucide-package.svg new file mode 100644 index 0000000..8551163 --- /dev/null +++ b/tools/diagrams/icons/lucide-package.svg @@ -0,0 +1,61 @@ + + + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/icons/lucide-tag.svg b/tools/diagrams/icons/lucide-tag.svg new file mode 100644 index 0000000..b0be741 --- /dev/null +++ b/tools/diagrams/icons/lucide-tag.svg @@ -0,0 +1,59 @@ + + + + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/tools/diagrams/render_excalidraw.py b/tools/diagrams/render_excalidraw.py new file mode 100644 index 0000000..d3902b0 --- /dev/null +++ b/tools/diagrams/render_excalidraw.py @@ -0,0 +1,265 @@ +"""Render Excalidraw JSON to PNG using Playwright + headless Chromium. + +Usage: + cd .claude/skills/excalidraw-diagram/references + uv run python render_excalidraw.py [--output path.png] [--svg path.svg] [--scale 2] [--width 1920] + uv run python render_excalidraw.py --figure [--figure ...] [--build] + uv run python render_excalidraw.py --all [--build] + +--figure/--all render figures configured in figures.toml: the SVG goes to the +figure's `svg` path (made responsive: fixed width/height dropped, max-width +style added) and the PNG to the configured review directory as .png. +--build first runs each figure's builder (with its configured args). + +First-time setup: + cd .claude/skills/excalidraw-diagram/references + uv sync + uv run playwright install chromium +""" + +from __future__ import annotations + +import argparse +import base64 +import json +import re +import subprocess +import sys +from pathlib import Path + + +def validate_excalidraw(data: dict) -> list[str]: + """Validate Excalidraw JSON structure. Returns list of errors (empty = valid).""" + errors: list[str] = [] + + if data.get("type") != "excalidraw": + errors.append(f"Expected type 'excalidraw', got '{data.get('type')}'") + + if "elements" not in data: + errors.append("Missing 'elements' array") + elif not isinstance(data["elements"], list): + errors.append("'elements' must be an array") + elif len(data["elements"]) == 0: + errors.append("'elements' array is empty — nothing to render") + + return errors + + +def compute_bounding_box(elements: list[dict]) -> tuple[float, float, float, float]: + """Compute bounding box (min_x, min_y, max_x, max_y) across all elements.""" + min_x = float("inf") + min_y = float("inf") + max_x = float("-inf") + max_y = float("-inf") + + for el in elements: + if el.get("isDeleted"): + continue + x = el.get("x", 0) + y = el.get("y", 0) + w = el.get("width", 0) + h = el.get("height", 0) + + # For arrows/lines, points array defines the shape relative to x,y + if el.get("type") in ("arrow", "line") and "points" in el: + for px, py in el["points"]: + min_x = min(min_x, x + px) + min_y = min(min_y, y + py) + max_x = max(max_x, x + px) + max_y = max(max_y, y + py) + else: + min_x = min(min_x, x) + min_y = min(min_y, y) + max_x = max(max_x, x + abs(w)) + max_y = max(max_y, y + abs(h)) + + if min_x == float("inf"): + return (0, 0, 800, 600) + + return (min_x, min_y, max_x, max_y) + + +def render( + excalidraw_path: Path, + output_path: Path | None = None, + scale: int = 2, + max_width: int = 1920, + svg_out: Path | None = None, +) -> Path: + """Render an .excalidraw file to PNG. Returns the output PNG path.""" + # Import playwright here so validation errors show before import errors + try: + from playwright.sync_api import sync_playwright + except ImportError: + print("ERROR: playwright not installed.", file=sys.stderr) + print("Run: cd .claude/skills/excalidraw-diagram/references && uv sync && uv run playwright install chromium", file=sys.stderr) + sys.exit(1) + + # Read and validate + raw = excalidraw_path.read_text(encoding="utf-8") + try: + data = json.loads(raw) + except json.JSONDecodeError as e: + print(f"ERROR: Invalid JSON in {excalidraw_path}: {e}", file=sys.stderr) + sys.exit(1) + + errors = validate_excalidraw(data) + if errors: + print(f"ERROR: Invalid Excalidraw file:", file=sys.stderr) + for err in errors: + print(f" - {err}", file=sys.stderr) + sys.exit(1) + + # Compute viewport size from element bounding box + elements = [e for e in data["elements"] if not e.get("isDeleted")] + min_x, min_y, max_x, max_y = compute_bounding_box(elements) + padding = 80 + diagram_w = max_x - min_x + padding * 2 + diagram_h = max_y - min_y + padding * 2 + + # Cap viewport width, let height be natural + vp_width = min(int(diagram_w), max_width) + vp_height = max(int(diagram_h), 600) + + # Output path + if output_path is None: + output_path = excalidraw_path.with_suffix(".png") + + # Template path (same directory as this script) + template_path = Path(__file__).parent / "render_template.html" + if not template_path.exists(): + print(f"ERROR: Template not found at {template_path}", file=sys.stderr) + sys.exit(1) + + template_url = template_path.as_uri() + + with sync_playwright() as p: + try: + browser = p.chromium.launch(headless=True) + except Exception as e: + if "Executable doesn't exist" in str(e) or "browserType.launch" in str(e): + print("ERROR: Chromium not installed for Playwright.", file=sys.stderr) + print("Run: cd .claude/skills/excalidraw-diagram/references && uv run playwright install chromium", file=sys.stderr) + sys.exit(1) + raise + + page = browser.new_page( + viewport={"width": vp_width, "height": vp_height}, + device_scale_factor=scale, + ) + + # Load the template + page.goto(template_url) + + # Wait for the ES module to load (imports from esm.sh) + page.wait_for_function("window.__moduleReady === true", timeout=30000) + + # Inject the diagram data and render + json_str = json.dumps(data) + result = page.evaluate(f"window.renderDiagram({json_str})") + + if not result or not result.get("success"): + error_msg = result.get("error", "Unknown render error") if result else "renderDiagram returned null" + print(f"ERROR: Render failed: {error_msg}", file=sys.stderr) + browser.close() + sys.exit(1) + + # Wait for render completion signal + page.wait_for_function("window.__renderComplete === true", timeout=15000) + + # Screenshot the SVG element + svg_el = page.query_selector("#root svg") + if svg_el is None: + print("ERROR: No SVG element found after render.", file=sys.stderr) + browser.close() + sys.exit(1) + + svg_el.screenshot(path=str(output_path)) + # PNG screenshots cannot retain the SVGs' embedded license metadata. + # Keep the full notices alongside PNGs that contain Lucide icons. + if any( + file.get("dataURL", "").startswith("data:image/svg+xml;base64,") + and b'id="lucide-license"' in base64.b64decode(file["dataURL"].split(",", 1)[1]) + for file in data.get("files", {}).values() + ): + license_path = Path(__file__).parent / "icons" / "LUCIDE-LICENSE.txt" + output_path.with_suffix(".LICENSE.txt").write_text( + license_path.read_text(encoding="utf-8"), encoding="utf-8" + ) + if svg_out is not None: + svg_markup = svg_el.evaluate("el => el.outerHTML") + Path(svg_out).write_text(svg_markup, encoding="utf-8") + browser.close() + + return output_path + + +def make_responsive(svg_path: Path) -> None: + """Drop the fixed width/height on the root and let it scale to its container.""" + svg = svg_path.read_text(encoding="utf-8") + head_end = svg.index(">") + head = re.sub(r' width="[^"]*" height="[^"]*"', ' style="max-width:100%;height:auto"', svg[:head_end], count=1) + svg_path.write_text(head + svg[head_end:], encoding="utf-8") + + +def render_figure(name: str, scale: int, max_width: int, build: bool = False) -> None: + """Render a figure configured in figures.toml to its SVG and review PNG paths.""" + sys.path.insert(0, str(Path(__file__).resolve().parent)) + from excalib import REPO_ROOT, figure + + fig = figure(name) + if build: + subprocess.run([sys.executable, str(fig.builder), *fig.args], cwd=REPO_ROOT, check=True) + if not fig.excalidraw.exists(): + print(f"ERROR: {name}: {fig.excalidraw} not found (run its builder, or pass --build)", file=sys.stderr) + sys.exit(1) + fig.png.parent.mkdir(parents=True, exist_ok=True) + if fig.svg is not None: + fig.svg.parent.mkdir(parents=True, exist_ok=True) + render(fig.excalidraw, fig.png, scale, max_width, svg_out=fig.svg) + if fig.svg is not None: + make_responsive(fig.svg) + print(f"{name}: {fig.svg}") + print(f"{name}: {fig.png}") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Render Excalidraw JSON to PNG") + parser.add_argument("input", type=Path, nargs="?", help="Path to .excalidraw JSON file") + parser.add_argument("--output", "-o", type=Path, default=None, help="Output PNG path (default: same name with .png)") + parser.add_argument("--scale", "-s", type=int, default=2, help="Device scale factor (default: 2)") + parser.add_argument("--width", "-w", type=int, default=1920, help="Max viewport width (default: 1920)") + parser.add_argument("--svg", type=Path, default=None, help="Also write the rendered SVG markup to this path") + parser.add_argument("--figure", "-f", action="append", default=[], + help="Render a figure configured in figures.toml by name (repeatable)") + parser.add_argument("--all", action="store_true", help="Render every figure in figures.toml") + parser.add_argument("--build", action="store_true", + help="With --figure/--all: run each figure's builder before rendering") + args = parser.parse_args() + + if args.figure or args.all: + if args.input or args.output or args.svg: + parser.error("--figure/--all take their paths from figures.toml; omit input, --output and --svg") + names = args.figure + if args.all: + sys.path.insert(0, str(Path(__file__).resolve().parent)) + from excalib import load_figures + names = list(load_figures()) + for name in names: + render_figure(name, args.scale, args.width, build=args.build) + return + + if args.input is None: + parser.error("give an .excalidraw path, --figure NAME, or --all") + if args.build: + parser.error("--build needs --figure or --all") + if not args.input.exists(): + print(f"ERROR: File not found: {args.input}", file=sys.stderr) + sys.exit(1) + + png_path = render(args.input, args.output, args.scale, args.width, svg_out=args.svg) + print(str(png_path)) + + +if __name__ == "__main__": + main() diff --git a/tools/diagrams/render_template.html b/tools/diagrams/render_template.html new file mode 100644 index 0000000..877e33d --- /dev/null +++ b/tools/diagrams/render_template.html @@ -0,0 +1,57 @@ + + + + + + + +
+ + + + diff --git a/tools/diagrams/variants/build_pillars_b1.py b/tools/diagrams/variants/build_pillars_b1.py new file mode 100644 index 0000000..6941ba6 --- /dev/null +++ b/tools/diagrams/variants/build_pillars_b1.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python3 +"""Build the OIDM "five pillars" stack diagram -- variant B1, a true finger joint. + +This is a variant of ../build_pillars.py (seam b, the dovetail) and exists so +three seam explorations can run without stepping on each other. It differs +from the parent in three ways: + + 1. The seam is a finger joint, not a dovetail. In seam b the Data + Structures band grew four tabs that dropped into the Foundation band -- + one band sitting on the other. Here both bands grow fingers into a + single joint zone JOINT_D deep: four labelled teal tabs descend from + Data Structures, and five unlabelled amber fingers rise from Foundation + Context between and outside them, so the boundary is one square wave and + neither band is simply on top. The fingers are square, not rounded: a + finger joint reads as a finger joint because the fingers are square. + 2. The "relationships" plate and the two double-headed arrows between the + Foundation blocks are gone. + 3. The Foundation band is tightened so everything below the joint -- header, + subtitle, block row, bottom padding -- fits in under 200px. + +Everything else (icons, palette, block content, the bands above) is v4b. + +Joint geometry. JOINT_TOP is where the joint zone starts and JOINT_BOT = +JOINT_TOP + JOINT_D where it ends. The teal band rectangle runs down to +JOINT_BOT and the amber band rectangle starts at JOINT_TOP, so the two +overlap by exactly the joint depth; the teal rectangle is in front, so teal +is what shows in the zone by default, and the amber fingers are painted back +over it. That overlap also hides both rectangles' rounded corners inside the +zone, which is why the outermost fingers are amber and at least CORNER_R +wide -- they cover the curves, and two short vertical strokes redraw the +stack's outer edge straight through the joint. + +Output: the "pillars-b1" entry in figures.toml (override with --out). +""" +from __future__ import annotations + +import argparse +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) + +from excalib import els, base, text, image, save, figure, AMBER, TEAL, VIOLET, ROSE, GREEN, CAPTION # noqa: E402 + +# ---------------------------------------------------------------- layout constants +CONTENT_W = 880 +BAND_GAP = 24 # the vertical gap between bands above the seam +PAD_BAND = 20 # a band's own inner padding on all sides +GAP = 20 # gap between sibling blocks inside a band +GAP_F = 24 # gap between the three Foundation blocks (no arrows between them now) +ICON = 28 # harmonized icon render size +ICON_FILLED = 32 # filled-path icon sets need a bigger box to match optically +CORNER_R = 32 # Excalidraw's adaptive corner radius for a band-sized rectangle + +# ---------------------------------------------------------------- the finger joint +JOINT_D = 40.0 # how deep the joint zone is: how far each set of fingers reaches +TAB_W = 118.0 # width of a labelled teal tab +JOINT_WORDS = ["finding", "diagnosis", "location", "exam type"] +FINGER_W = (CONTENT_W - len(JOINT_WORDS) * TAB_W) / (len(JOINT_WORDS) + 1) # amber, unlabelled +CAPTION_SPACE = 38.0 # teal room above the joint for the lead caption +LEAD = "woven together at every:" + +_uid = [0] + + +def uid(prefix: str) -> str: + _uid[0] += 1 + return f"{prefix}{_uid[0]}" + + +# ---------------------------------------------------------------- primitives +def band_rect(id_: str, x: float, y: float, w: float, h: float, pal: dict, at_front=False) -> dict: + """Draw a band's background rectangle. Pass at_front=True when the band's + true height is only known after laying out its header/blocks (they were + already appended to els) -- this inserts the rect at index 0 so it still + paints behind its own contents instead of covering them. Note the order + that matters at the joint: whichever band rect is inserted at index 0 last + ends up furthest back, so Foundation Context must be built last.""" + r = base("rectangle", id_, x, y, w, h, pal["stroke"], pal["band"]) + r["roundness"] = {"type": 3} + if at_front: + els.insert(0, r) + else: + els.append(r) + return r + + +def band_header(x: float, y: float, icon_path: str, pal: dict, title: str, subtitle: str, + size=20, filled_icon=False) -> float: + """Bold band title with its icon to the left, subtitle below. Returns bottom y.""" + s = ICON_FILLED if filled_icon else ICON + image(uid("bicon"), x, y - (s - ICON) / 2 - 2, s, s, icon_path, color=pal["stroke"]) + els.append(text(uid("btitle"), x + ICON + 12, y, title, size=size, color=pal["text"])) + y2 = y + size * 1.3 + els.append(text(uid("bsub"), x + ICON + 12, y2, subtitle, size=13, color=pal["mid"])) + return y2 + 13 * 1.25 + + +def icon_block_h(lines: list[str], desc: str | None = None) -> float: + """Height an icon_block needs for this many label lines/descriptor.""" + n_title = len(lines) + return 12 + ICON + 8 + n_title * 15 * 1.25 + (13 * 1.25 + 2 if desc else 0) + 10 + + +def icon_block(x: float, y: float, w: float, h: float, icon_path: str, pal: dict, + lines: list[str], desc: str | None = None, filled_icon=False) -> dict: + """Icon centered on top, label line(s) centered below, optional lighter + descriptor line last. Background is the band's own deeper pastel so the + block reads as 'inside' the band, not a separate object.""" + r = base("rectangle", uid("blk"), x, y, w, h, pal["stroke"], pal["block"], sw=1) + r["roundness"] = {"type": 3} + els.append(r) + cx = x + w / 2 + content_h = icon_block_h(lines, desc) - 22 # minus this fn's fixed 12+10 top/bottom pad + top = y + (h - content_h) / 2 + s = ICON_FILLED if filled_icon else ICON + image(uid("blki"), cx - s / 2, top - (s - ICON) / 2, s, s, icon_path, color=pal["stroke"]) + ty = top + ICON + 6 + for ln in lines: + els.append(text(uid("blkt"), x + 6, ty, ln, size=15, color=pal["text"], align="center", w=w - 12)) + ty += 15 * 1.25 + if desc: + els.append(text(uid("blkd"), x + 6, ty + 2, desc, size=13, color=pal["mid"], align="center", w=w - 12)) + return r + + +def chip(cx: float, y_top: float, s: str, fill: str, stroke: str, color: str, + size=13, pad_x=9, pad_y=3) -> float: + """A short caption on its own opaque rounded plate, centered on cx, so it + reads as a label for what it sits over and never mixes with a line or a + band fill behind it. Returns bottom y.""" + w = len(s) * size * 0.58 + 2 * pad_x + h = size * 1.25 + 2 * pad_y + r = base("rectangle", uid("chip"), cx - w / 2, y_top, w, h, stroke, fill, sw=1) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(uid("chipt"), cx - w / 2 + pad_x, y_top + pad_y, s, size=size, color=color, + align="center", w=w - 2 * pad_x)) + return y_top + h + + +def hconn(x1: float, x2: float, y: float, color: str, sw=1, both=False) -> None: + """A plain horizontal connector; both=True gives it two arrowheads.""" + a = base("arrow" if both else "line", uid("conn"), x1, y, x2 - x1, 0, color, "transparent", sw=sw) + a.update({"points": [[0, 0], [x2 - x1, 0]], "boundElements": None}) + if both: + a.update({"startArrowhead": "arrow", "endArrowhead": "arrow"}) + els.append(a) + + +def fill_rect(x: float, y: float, w: float, h: float, fill: str, rounded=False) -> dict: + """An unstroked patch of colour -- used here to paint an amber finger back + over the teal band rectangle inside the joint zone.""" + r = base("rectangle", uid("fill"), x, y, w, h, "transparent", fill, sw=1) + r["roundness"] = {"type": 3} if rounded else None + els.append(r) + return r + + +def stroke_line(pts: list[tuple[float, float]], color: str, sw=2, opacity=100) -> dict: + """A polyline through pts (absolute coordinates), sharp corners.""" + x0, y0 = pts[0] + xs = [p[0] for p in pts] + ys = [p[1] for p in pts] + ln = base("line", uid("ln"), x0, y0, max(xs) - min(xs), max(ys) - min(ys), color, "transparent", sw=sw) + ln.update({"points": [[p[0] - x0, p[1] - y0] for p in pts], "boundElements": None, "roundness": None}) + ln["opacity"] = opacity + els.append(ln) + return ln + + +def tab_x(i: int) -> float: + """Left edge of labelled teal tab i. The run starts and ends with an amber + finger, so the pattern is A T A T A T A T A across the full width.""" + return FINGER_W + i * (TAB_W + FINGER_W) + + +def draw_finger_joint(g: dict) -> None: + top = g["joint_top"] + bot = top + JOINT_D + + # 1. the amber fingers, painted back over the teal band rectangle. Each runs + # a few px past the joint's bottom so it also covers the teal rectangle's + # own bottom stroke; below the zone it is amber on amber, so nothing shows. + finger_xs = [0.0] + [tab_x(i) + TAB_W for i in range(len(JOINT_WORDS))] + for fx in finger_xs: + fill_rect(fx, top, FINGER_W, JOINT_D + 3, AMBER["band"]) + + # 2. the interlocking boundary as one square wave, left edge to right edge + pts: list[tuple[float, float]] = [(0.0, top)] + for i in range(len(JOINT_WORDS)): + x = tab_x(i) + pts += [(x, top), (x, bot), (x + TAB_W, bot), (x + TAB_W, top)] + pts.append((CONTENT_W, top)) + stroke_line(pts, TEAL["stroke"], sw=2) + + # 3. the stack's outer edge through the joint. Both band rectangles curve + # away inside the zone; the amber fingers at each end cover those curves, + # so the edge is redrawn straight in the amber that is actually there. + for x in (0.0, CONTENT_W): + stroke_line([(x, top), (x, bot + 3)], AMBER["stroke"], sw=2) + + # 4. the four words, one per teal tab + for i, word in enumerate(JOINT_WORDS): + els.append(text(uid("jw"), tab_x(i), top + (JOINT_D - 13 * 1.25) / 2, word, + size=13, color=TEAL["text"], align="center", w=TAB_W)) + + # 5. the lead caption, in the teal room kept above the joint + els.append(text(uid("lead"), 0, top - CAPTION_SPACE + (CAPTION_SPACE - 13 * 1.25) / 2, LEAD, + size=13, color=TEAL["mid"], align="center", w=CONTENT_W)) + + +# ============================================================ the stack +def build(out_path: str) -> str: + # ---------------------------------------- 5/4. Use Cases | Sample Applications + Y_TOP = 0 + GAP_MID = 104 # wide enough for "illustrates" to sit clear of both panels + PANEL_PAD = 14 + PANEL_INSET = PAD_BAND - 2 # same left inset as the full-width band headers + PANEL_TITLE = 18 + PANEL_H = PANEL_PAD + PANEL_TITLE * 1.3 + 13 * 1.25 + PANEL_PAD + panel_w = (CONTENT_W - GAP_MID) / 2 + + band_rect(uid("panel"), 0, Y_TOP, panel_w, PANEL_H, ROSE) + band_rect(uid("panel"), panel_w + GAP_MID, Y_TOP, panel_w, PANEL_H, GREEN) + band_header(PANEL_INSET, Y_TOP + PANEL_PAD, "icons/lucide-lightbulb.svg", ROSE, + "Use Cases", "what the project wants built", size=PANEL_TITLE) + band_header(panel_w + GAP_MID + PANEL_INSET, Y_TOP + PANEL_PAD, "icons/lucide-app-window.svg", GREEN, + "Sample Applications", "what has been built", size=PANEL_TITLE) + conn_y = Y_TOP + PANEL_H / 2 + hconn(panel_w + 2, panel_w + GAP_MID - 2, conn_y, CAPTION) + chip(panel_w + GAP_MID / 2, conn_y - 7 - (13 * 1.25 + 6), "illustrates", + "#ffffff", "transparent", CAPTION) + + # ---------------------------------------- 3. SDKs (thin full-width band) + Y_SDK = Y_TOP + PANEL_H + BAND_GAP + sdk_hdr_bottom = band_header(PAD_BAND - 2, Y_SDK + 16, "icons/lucide-package.svg", VIOLET, + "SDKs", "wrap the structures · attach the shared knowledge · support authoring", + size=18) + SDK_BOTTOM = sdk_hdr_bottom + 16 + band_rect(uid("band"), 0, Y_SDK, CONTENT_W, SDK_BOTTOM - Y_SDK, VIOLET, at_front=True) + + # ---------------------------------------- 2. Data Structures + Y_DATA = SDK_BOTTOM + BAND_GAP + hdr_bottom = band_header(PAD_BAND - 2, Y_DATA + 18, "icons/lucide-layers.svg", TEAL, + "Data Structures", "this patient · Patient Context", size=20) + BLOCK_ROW_Y = hdr_bottom + 14 + W4 = (CONTENT_W - 2 * PAD_BAND - 3 * GAP) / 4 + d_positions = [PAD_BAND + i * (W4 + GAP) for i in range(4)] + d_specs = [ + ("icons/lucide-clipboard.svg", ["Observation"], False), + ("icons/lucide-clipboard-list.svg", ["Exam Finding", "List"], False), + ("icons/lucide-list.svg", ["Imaging Problem", "List"], False), + ("icons/healthicons-person.svg", ["Imaging Persona"], True), + ] + ROW_H_DATA = max(icon_block_h(lines) for _, lines, _ in d_specs) + for xpos, (ipath, lines, filled) in zip(d_positions, d_specs): + icon_block(xpos, BLOCK_ROW_Y, W4, ROW_H_DATA, ipath, TEAL, lines, filled_icon=filled) + DATA_BLOCKS_BOTTOM = BLOCK_ROW_Y + ROW_H_DATA + + # the joint zone: the two band rectangles overlap across exactly its depth + JOINT_TOP = DATA_BLOCKS_BOTTOM + CAPTION_SPACE + JOINT_BOT = JOINT_TOP + JOINT_D + band_rect(uid("band"), 0, Y_DATA, CONTENT_W, JOINT_BOT - Y_DATA, TEAL, at_front=True) + + # ---------------------------------------- 1. Foundation Context (bottom) + # Built last so its rect lands behind the Data Structures band in the joint. + FOUND_TOP = JOINT_TOP + hdr_bottom_f = band_header(PAD_BAND - 2, JOINT_BOT + 14, + "icons/lucide-book-open.svg", AMBER, + "Foundation Context", "shared, curated knowledge", size=20) + F_ROW_Y = hdr_bottom_f + 10 + W3 = (CONTENT_W - 2 * PAD_BAND - 2 * GAP_F) / 3 + f_positions = [PAD_BAND + i * (W3 + GAP_F) for i in range(3)] + f_specs = [ + ("icons/lucide-tag.svg", ["Finding/diagnosis definitions"], "finding models · CDEs", False), + ("icons/healthicons-lungs.svg", ["Anatomic locations"], "anchored in RadLex", True), + ("icons/healthicons-xray.svg", ["Exam types"], "LOINC/RSNA Playbook", True), + ] + ROW_H_FOUND = max(icon_block_h(lines, desc) for _, lines, desc, _ in f_specs) + for xpos, (ipath, lines, desc, filled) in zip(f_positions, f_specs): + icon_block(xpos, F_ROW_Y, W3, ROW_H_FOUND, ipath, AMBER, lines, desc=desc, filled_icon=filled) + FOUND_BAND_BOTTOM = F_ROW_Y + ROW_H_FOUND + PAD_BAND + band_rect(uid("band"), 0, FOUND_TOP, CONTENT_W, FOUND_BAND_BOTTOM - FOUND_TOP, AMBER, at_front=True) + + print(f"foundation band below the joint: {FOUND_BAND_BOTTOM - JOINT_BOT:.1f}px") + + # ---------------------------------------- the joint, drawn over both bands + draw_finger_joint({"joint_top": JOINT_TOP}) + + return save(out_path) + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + ap.add_argument("--out", default=None, + help="output path relative to the repo root " + "(default: the pillars-b1 entry in figures.toml)") + args = ap.parse_args() + build(args.out or figure("pillars-b1").excalidraw) + + +if __name__ == "__main__": + main() diff --git a/tools/diagrams/variants/build_pillars_b2.py b/tools/diagrams/variants/build_pillars_b2.py new file mode 100644 index 0000000..734bed9 --- /dev/null +++ b/tools/diagrams/variants/build_pillars_b2.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python3 +"""Build the OIDM "five pillars" stack diagram -- seam variant B2, "tabs plug +into blocks". + +Variant of tools/diagrams/build_pillars.py --seam b. In v4b the four teal tabs +dropped out of the Data Structures band into empty amber ground: the joint was +band-to-band, and the three Foundation blocks sat well below it with no visible +relation to any particular tab. B2 makes the joint block-to-block instead. + + * The three Foundation Context blocks rise until their top edges *are* the + seam, so there is no amber ground between the joint and the blocks. + * Each teal tab lands in a named block: "finding" and "diagnosis" both plug + into "Finding/diagnosis definitions", "location" into "Anatomic locations", + "exam type" into "Exam types". A tab is drawn over the block's top edge, so + the edge reads as a notch the tab fills. + * The Foundation band header moves to a left column beside the blocks (icon + + wrapped title + subtitle), with the "woven together at every:" lead above + it, on the tab row, where it was in v4b. + * The "relationships" plate and the two double-headed arrows between the + Foundation blocks are gone, and the band is 151px tall including its header. + +Everything above the seam (Use Cases / Sample Applications, SDKs, the four Data +Structures cards) is unchanged from v4b. + +Because the Foundation band is laid out for this seam, the a/b/c seam switch of +the parent builder is not carried over: this file draws B2 and nothing else. + +Block and tab widths here are sized from measured Helvetica advance widths (see +tools/diagrams/README.md, "Measuring text instead of estimating it"), not from +excalib's 0.58-per-character estimate. + +Icons: unchanged from build_pillars.py -- Health Icons (CC0/MIT, outline) for +anatomy/exam/person glyphs, Lucide (ISC) for the rest; see icons/LICENSES.md. + +Output: the "pillars-b2" entry in figures.toml (override with --out). +""" +from __future__ import annotations + +import argparse +import math +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) + +from excalib import els, base, text, image, save, figure, AMBER, TEAL, VIOLET, ROSE, GREEN, CAPTION + +# ---------------------------------------------------------------- layout constants +CONTENT_W = 880 +BAND_GAP = 24 # the vertical gap between bands above the seam +PAD_BAND = 20 # a band's own inner padding on all sides +GAP = 20 # gap between sibling blocks inside a band +ICON = 28 # harmonized icon render size +ICON_FILLED = 32 # filled-path icon sets need a bigger box to match optically +CORNER_R = 32 # Excalidraw's adaptive corner radius for a band-sized rectangle + +# ---- the seam +TAB_W = 92.0 # a tab is sized to hold "diagnosis"/"exam type" (60px measured) with air +TAB_D = 30.0 # how far a tab descends past the seam into its block +TAB_R = 10.0 # the tab's bottom corner radius +TAB_GAP = 24.0 # between the two tabs that share the first block +TAB_RISE = 14.0 # how far the tab's fill runs up past the seam, to break the seam stroke +LEAD = "woven together at every:" + +# ---- the Foundation band's left header column +LEFT_W = 196.0 # holds "shared, curated knowledge" (156 measured) beside the icon, on one line +LEFT_GAP = 20.0 +PAD_BOTTOM = 26 # a little more air under the blocks than PAD_BAND, so their + # bottom corner radius does not crowd the band's own + +_uid = [0] + + +def uid(prefix: str) -> str: + _uid[0] += 1 + return f"{prefix}{_uid[0]}" + + +# ---------------------------------------------------------------- primitives +def band_rect(id_: str, x: float, y: float, w: float, h: float, pal: dict, at_front=False) -> dict: + """Draw a band's background rectangle. Pass at_front=True when the band's + true height is only known after laying out its header/blocks (they were + already appended to els) -- this inserts the rect at index 0 so it still + paints behind its own contents instead of covering them. Note the order + that matters at the seam: whichever band rect is inserted at index 0 last + ends up furthest back, so Foundation Context must be built last.""" + r = base("rectangle", id_, x, y, w, h, pal["stroke"], pal["band"]) + r["roundness"] = {"type": 3} + if at_front: + els.insert(0, r) + else: + els.append(r) + return r + + +def band_header(x: float, y: float, icon_path: str, pal: dict, title: str, subtitle: str, + size=20, filled_icon=False) -> float: + """Bold band title with its icon to the left, subtitle below. Returns bottom y.""" + s = ICON_FILLED if filled_icon else ICON + image(uid("bicon"), x, y - (s - ICON) / 2 - 2, s, s, icon_path, color=pal["stroke"]) + els.append(text(uid("btitle"), x + ICON + 12, y, title, size=size, color=pal["text"])) + y2 = y + size * 1.3 + els.append(text(uid("bsub"), x + ICON + 12, y2, subtitle, size=13, color=pal["mid"])) + return y2 + 13 * 1.25 + + +def left_header(x: float, y: float, icon_path: str, pal: dict, title_lines: list[str], + sub_lines: list[str], size=20) -> float: + """The band header as a narrow left column: the icon hangs at the left + margin beside the first title line, and every line of the title and the + subtitle shares one text column indented past it -- the same icon/text + relationship as the full-width band headers, just wrapped. Returns bottom y.""" + image(uid("bicon"), x, y + (size * 1.3 - ICON) / 2 - 1, ICON, ICON, icon_path, color=pal["stroke"]) + tx, yy = x + ICON + 12, y + for ln in title_lines: + els.append(text(uid("btitle"), tx, yy, ln, size=size, color=pal["text"])) + yy += size * 1.3 + yy += 4 + for ln in sub_lines: + els.append(text(uid("bsub"), tx, yy, ln, size=13, color=pal["mid"])) + yy += 13 * 1.25 + return yy + + +def left_header_h(n_title: int, n_sub: int, size=20) -> float: + return n_title * size * 1.3 + 4 + n_sub * 13 * 1.25 + + +def icon_block_h(lines: list[str], desc: str | None = None) -> float: + """Height an icon_block needs for this many label lines/descriptor.""" + n_title = len(lines) + return 12 + ICON + 8 + n_title * 15 * 1.25 + (13 * 1.25 + 2 if desc else 0) + 10 + + +def icon_block(x: float, y: float, w: float, h: float, icon_path: str, pal: dict, + lines: list[str], desc: str | None = None, filled_icon=False, + top_inset: float = 0.0) -> dict: + """Icon centered on top, label line(s) centered below, optional lighter + descriptor line last. Background is the band's own deeper pastel so the + block reads as 'inside' the band, not a separate object. + + top_inset reserves that much of the block's top edge for something else -- + here, the notch a seam tab plugs into -- and centres the contents in what + is left, so a tab never lands on the icon.""" + r = base("rectangle", uid("blk"), x, y, w, h, pal["stroke"], pal["block"], sw=1) + r["roundness"] = {"type": 3} + els.append(r) + cx = x + w / 2 + content_h = icon_block_h(lines, desc) - 22 # minus this fn's fixed 12+10 top/bottom pad + top = y + top_inset + (h - top_inset - content_h) / 2 + s = ICON_FILLED if filled_icon else ICON + image(uid("blki"), cx - s / 2, top - (s - ICON) / 2, s, s, icon_path, color=pal["stroke"]) + ty = top + ICON + 6 + for ln in lines: + els.append(text(uid("blkt"), x + 6, ty, ln, size=15, color=pal["text"], align="center", w=w - 12)) + ty += 15 * 1.25 + if desc: + els.append(text(uid("blkd"), x + 6, ty + 2, desc, size=13, color=pal["mid"], align="center", w=w - 12)) + return r + + +def chip(cx: float, y_top: float, s: str, fill: str, stroke: str, color: str, + size=13, pad_x=9, pad_y=3) -> float: + """A short caption on its own opaque rounded plate, centered on cx, so it + reads as a label for what it sits over and never mixes with a line or a + band fill behind it. Returns bottom y.""" + w = len(s) * size * 0.58 + 2 * pad_x + h = size * 1.25 + 2 * pad_y + r = base("rectangle", uid("chip"), cx - w / 2, y_top, w, h, stroke, fill, sw=1) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(uid("chipt"), cx - w / 2 + pad_x, y_top + pad_y, s, size=size, color=color, + align="center", w=w - 2 * pad_x)) + return y_top + h + + +def hconn(x1: float, x2: float, y: float, color: str, sw=1, both=False) -> None: + """A plain horizontal connector; both=True gives it two arrowheads.""" + a = base("arrow" if both else "line", uid("conn"), x1, y, x2 - x1, 0, color, "transparent", sw=sw) + a.update({"points": [[0, 0], [x2 - x1, 0]], "boundElements": None}) + if both: + a.update({"startArrowhead": "arrow", "endArrowhead": "arrow"}) + els.append(a) + + +def fill_rect(x: float, y: float, w: float, h: float, fill: str, rounded=False) -> dict: + """An unstroked patch of colour -- used to close the notch two rounded + bands leave where they meet, to square off a block's rounded top corners + where that top edge has become the seam, and to let a tab sit across a + band edge without that edge's stroke running through it.""" + r = base("rectangle", uid("fill"), x, y, w, h, "transparent", fill, sw=1) + r["roundness"] = {"type": 3} if rounded else None + els.append(r) + return r + + +def stroke_line(pts: list[tuple[float, float]], color: str, sw=2, opacity=100) -> dict: + """A polyline through pts (absolute coordinates), sharp corners.""" + x0, y0 = pts[0] + xs = [p[0] for p in pts] + ys = [p[1] for p in pts] + ln = base("line", uid("ln"), x0, y0, max(xs) - min(xs), max(ys) - min(ys), color, "transparent", sw=sw) + ln.update({"points": [[p[0] - x0, p[1] - y0] for p in pts], "boundElements": None, "roundness": None}) + ln["opacity"] = opacity + els.append(ln) + return ln + + +def arc_pts(cx: float, cy: float, r: float, a0: float, a1: float, n=4) -> list[tuple[float, float]]: + """Points along a circular arc, for rounding a corner of a polyline.""" + return [(cx + r * math.cos(a0 + (a1 - a0) * i / n), cy + r * math.sin(a0 + (a1 - a0) * i / n)) + for i in range(n + 1)] + + +def corner_patch(y_top: float, y_bot: float, pal: dict, anchor: dict) -> None: + """Two rounded bands that meet edge to edge leave a white notch at each + end, where both corner radii curve away from the seam. Fill those notches + with the band's own colour and redraw its outer border straight, so the + stack keeps one silhouette through the seam. + + A notch is CORNER_R deep, which reaches back into the band far enough to + cover a card corner or a header icon, so the patch is restacked to sit + directly above `anchor` (its own band rectangle) instead of on top of + everything the seam is drawn after.""" + made = [ + fill_rect(-1, y_top, CORNER_R + 1, y_bot - y_top, pal["band"]), + fill_rect(CONTENT_W - CORNER_R, y_top, CORNER_R + 1, y_bot - y_top, pal["band"]), + stroke_line([(0, y_top), (0, y_bot)], pal["stroke"], sw=2), + stroke_line([(CONTENT_W, y_top), (CONTENT_W, y_bot)], pal["stroke"], sw=2), + ] + for e in reversed(made): + els.remove(e) + els.insert(els.index(anchor) + 1, e) + + +# ============================================================ the seam +def tab_xs(bx: float, bw: float, n: int) -> list[float]: + """Left edges of the n tabs that plug into a block of width bw at bx.""" + total = n * TAB_W + (n - 1) * TAB_GAP + x0 = bx + (bw - total) / 2 + return [x0 + i * (TAB_W + TAB_GAP) for i in range(n)] + + +def draw_seam(g: dict) -> None: + """The joint: the seam line, then one teal tab per attachment kind cut + into the top edge of the Foundation block it attaches to.""" + seam = g["found_top"] + corner_patch(seam - CORNER_R, seam, TEAL, g["teal_band"]) + corner_patch(seam, seam + CORNER_R, AMBER, g["amber_band"]) + + # The Foundation blocks' top edges are the seam now, so square off their + # rounded top corners -- otherwise each block reads as a lozenge tucked + # under the band rather than a block the band rests on. + for bx, bw in zip(g["f_positions"], g["f_widths"]): + fill_rect(bx, seam, bw, CORNER_R, AMBER["block"]) + stroke_line([(bx, seam), (bx, seam + CORNER_R)], AMBER["stroke"], sw=1) + stroke_line([(bx + bw, seam), (bx + bw, seam + CORNER_R)], AMBER["stroke"], sw=1) + + # Over a block, the seam is that block's own top edge, so it is drawn in + # the block's stroke at the block's own weight: the teal tab then visibly + # interrupts an amber edge, which is what makes the joint read as a notch + # cut into this block rather than a scallop in the band above it. Each tab + # paints over its own stretch of that edge next. Everywhere else the seam + # is the Data Structures band's own bottom edge, in teal at band weight. + edges: list[float] = [0.0] + for bx, bw in zip(g["f_positions"], g["f_widths"]): + stroke_line([(bx, seam), (bx + bw, seam)], AMBER["stroke"], sw=1) + edges += [bx, bx + bw] + edges.append(CONTENT_W) + for x1, x2 in zip(edges[0::2], edges[1::2]): + if x2 - x1 > 0.5: + stroke_line([(x1, seam), (x2, seam)], TEAL["stroke"], sw=2) + + els.append(text(uid("lead"), PAD_BAND, seam + (TAB_D - 13 * 1.25) / 2, LEAD, + size=13, color=AMBER["mid"])) + + for x, word in g["tabs"]: + # the tab's fill runs up past the seam and down over the block's top + # edge, so both strokes stop at the tab: what is left reads as a notch + # cut into the block, filled by the tab. + fill_rect(x, seam - TAB_RISE, TAB_W, TAB_D + TAB_RISE, TEAL["band"], rounded=True) + pts = [(x, seam)] + pts += arc_pts(x + TAB_R, seam + TAB_D - TAB_R, TAB_R, math.pi, math.pi / 2) + pts += arc_pts(x + TAB_W - TAB_R, seam + TAB_D - TAB_R, TAB_R, math.pi / 2, 0.0) + pts += [(x + TAB_W, seam)] + stroke_line(pts, TEAL["stroke"], sw=2) + els.append(text(uid("tabw"), x, seam + (TAB_D - 13 * 1.25) / 2, word, + size=13, color=TEAL["text"], align="center", w=TAB_W)) + + +# ============================================================ the stack +def build(out_path: str) -> str: + # ---------------------------------------- 5/4. Use Cases | Sample Applications + Y_TOP = 0 + GAP_MID = 104 # wide enough for "illustrates" to sit clear of both panels + PANEL_PAD = 14 + PANEL_INSET = PAD_BAND - 2 # same left inset as the full-width band headers + PANEL_TITLE = 18 + PANEL_H = PANEL_PAD + PANEL_TITLE * 1.3 + 13 * 1.25 + PANEL_PAD + panel_w = (CONTENT_W - GAP_MID) / 2 + + band_rect(uid("panel"), 0, Y_TOP, panel_w, PANEL_H, ROSE) + band_rect(uid("panel"), panel_w + GAP_MID, Y_TOP, panel_w, PANEL_H, GREEN) + band_header(PANEL_INSET, Y_TOP + PANEL_PAD, "icons/lucide-lightbulb.svg", ROSE, + "Use Cases", "what the project wants built", size=PANEL_TITLE) + band_header(panel_w + GAP_MID + PANEL_INSET, Y_TOP + PANEL_PAD, "icons/lucide-app-window.svg", GREEN, + "Sample Applications", "what has been built", size=PANEL_TITLE) + conn_y = Y_TOP + PANEL_H / 2 + hconn(panel_w + 2, panel_w + GAP_MID - 2, conn_y, CAPTION) + chip(panel_w + GAP_MID / 2, conn_y - 7 - (13 * 1.25 + 6), "illustrates", + "#ffffff", "transparent", CAPTION) + + # ---------------------------------------- 3. SDKs (thin full-width band) + Y_SDK = Y_TOP + PANEL_H + BAND_GAP + sdk_hdr_bottom = band_header(PAD_BAND - 2, Y_SDK + 16, "icons/lucide-package.svg", VIOLET, + "SDKs", "wrap the structures · attach the shared knowledge · support authoring", + size=18) + SDK_BOTTOM = sdk_hdr_bottom + 16 + band_rect(uid("band"), 0, Y_SDK, CONTENT_W, SDK_BOTTOM - Y_SDK, VIOLET, at_front=True) + + # ---------------------------------------- 2. Data Structures + Y_DATA = SDK_BOTTOM + BAND_GAP + hdr_bottom = band_header(PAD_BAND - 2, Y_DATA + 18, "icons/lucide-layers.svg", TEAL, + "Data Structures", "this patient · Patient Context", size=20) + BLOCK_ROW_Y = hdr_bottom + 14 + W4 = (CONTENT_W - 2 * PAD_BAND - 3 * GAP) / 4 + d_positions = [PAD_BAND + i * (W4 + GAP) for i in range(4)] + d_specs = [ + ("icons/lucide-clipboard.svg", ["Observation"], False), + ("icons/lucide-clipboard-list.svg", ["Exam Finding", "List"], False), + ("icons/lucide-list.svg", ["Imaging Problem", "List"], False), + ("icons/healthicons-person.svg", ["Imaging Persona"], True), + ] + ROW_H_DATA = max(icon_block_h(lines) for _, lines, _ in d_specs) + for xpos, (ipath, lines, filled) in zip(d_positions, d_specs): + icon_block(xpos, BLOCK_ROW_Y, W4, ROW_H_DATA, ipath, TEAL, lines, filled_icon=filled) + DATA_BLOCKS_BOTTOM = BLOCK_ROW_Y + ROW_H_DATA + + DATA_BAND_BOTTOM = DATA_BLOCKS_BOTTOM + PAD_BAND + teal_band = band_rect(uid("band"), 0, Y_DATA, CONTENT_W, DATA_BAND_BOTTOM - Y_DATA, TEAL, at_front=True) + + # ---------------------------------------- 1. Foundation Context (bottom) + # Built last so its rect lands behind the Data Structures band at the seam. + # The two bands are flush: the seam is this band's top edge, and it is also + # the top edge of all three blocks, which start at it with no amber ground + # in between. The header sits in a left column beside them. + FOUND_TOP = DATA_BAND_BOTTOM + + f_specs = [ + ("icons/lucide-tag.svg", ["Finding/diagnosis definitions"], "finding models · CDEs", False, + ["finding", "diagnosis"]), + ("icons/healthicons-lungs.svg", ["Anatomic locations"], "anchored in RadLex", True, + ["location"]), + ("icons/healthicons-xray.svg", ["Exam types"], "LOINC/RSNA Playbook", True, + ["exam type"]), + ] + # The first block holds two tabs, so it is the widest; it also holds the + # longest label ("Finding/diagnosis definitions", 188px measured). + F_WIDTHS = [236.0, 174.0, 174.0] + BX0 = PAD_BAND + LEFT_W + LEFT_GAP + f_positions = [] + x = BX0 + for w in F_WIDTHS: + f_positions.append(x) + x += w + GAP + + ROW_H_FOUND = TAB_D + max(icon_block_h(lines, desc) for _, lines, desc, _, _ in f_specs) + tabs: list[tuple[float, str]] = [] + for xpos, bw, (ipath, lines, desc, filled, words) in zip(f_positions, F_WIDTHS, f_specs): + icon_block(xpos, FOUND_TOP, bw, ROW_H_FOUND, ipath, AMBER, lines, desc=desc, + filled_icon=filled, top_inset=TAB_D) + tabs += list(zip(tab_xs(xpos, bw, len(words)), words)) + FOUND_BLOCKS_BOTTOM = FOUND_TOP + ROW_H_FOUND + + hdr_top = FOUND_TOP + TAB_D + (ROW_H_FOUND - TAB_D - left_header_h(2, 1)) / 2 + left_header(PAD_BAND, hdr_top, "icons/lucide-book-open.svg", AMBER, + ["Foundation", "Context"], ["shared, curated knowledge"], size=20) + + FOUND_BAND_BOTTOM = FOUND_BLOCKS_BOTTOM + PAD_BOTTOM + amber_band = band_rect(uid("band"), 0, FOUND_TOP, CONTENT_W, FOUND_BAND_BOTTOM - FOUND_TOP, + AMBER, at_front=True) + + # ---------------------------------------- the seam, drawn over both bands + draw_seam({ + "found_top": FOUND_TOP, + "f_positions": f_positions, "f_widths": F_WIDTHS, + "tabs": tabs, + "teal_band": teal_band, "amber_band": amber_band, + }) + print(f"foundation band height: {FOUND_BAND_BOTTOM - FOUND_TOP:.0f}px") + + return save(out_path) + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + ap.add_argument("--out", default=None, + help="output path relative to the repo root " + "(default: the pillars-b2 entry in figures.toml)") + args = ap.parse_args() + build(args.out or figure("pillars-b2").excalidraw) + + +if __name__ == "__main__": + main() diff --git a/tools/diagrams/variants/build_pillars_b3.py b/tools/diagrams/variants/build_pillars_b3.py new file mode 100644 index 0000000..8fde82a --- /dev/null +++ b/tools/diagrams/variants/build_pillars_b3.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python3 +"""Build the OIDM "five pillars" stack diagram -- seam variant B3, v7. + +Descended from ../build_pillars.py seam b. Where that file offered three +alternative treatments of one seam, this one uses a single treatment -- a +castellated edge -- at every place two bands meet, so the stack's joints are +one visual language instead of three unrelated devices. + +A castellated joint is a square wave drawn once along the boundary of two +bands that overlap by the wave's swing. The upper band is painted over the +lower one throughout the overlap; castellate() then restores the lower band's +fill in the gaps between the teeth and strokes the wave in the upper band's +colour, so the two bands share one interlocking edge with no gap, no tab +shapes and no connectors. Teeth are always twice as wide as the gaps between +them, whatever their count or depth, which is what makes the three joints read +as a family. + +The three joints, top to bottom: + + Sample Applications -> SDKs four green teeth, JOINT_DEPTH deep, labelled + with what an application gets: create, read, + resolve, author. Only the right half of the + SDK band's top edge is joined; Use Cases + keeps a plain BAND_GAP above it, because it + is what the project wants built rather than + something running on the SDKs. + SDKs -> Data Structures two wide violet teeth (wraps, manipulates + instances) plus one narrow tooth at the + right margin that does not close: it carries + on down a reserved lane beside the cards. + Data Structures -> Foundation one castellated line across the whole of the + Foundation band's top edge, SEAM_DEPTH deep: + four teal teeth carrying the attachment + kinds, then, flush right, one violet tooth + where the lane lands, reading what the SDKs + do to the foundation. The lane stops at that + edge rather than running on into the band, + so the band keeps an even margin both sides, + and the SDKs' reach into the foundation is + shown without an arrow. + +Two depths, belonging to the edge rather than to the joint: JOINT_DEPTH for the +two joints above, SEAM_DEPTH for the Foundation band's top edge, which the seam +and the lane's foot share. + +Each joint whose upper band has room for it carries a lead line in that band's +gutter, left-aligned on the first tooth so its colon runs into the list; the +SDK/Data joint has no gutter and so no lead. + +Icons: Health Icons (CC0/MIT, outline style) for anatomy/exam/person glyphs, +Lucide (ISC, with one Feather-derived MIT icon) for everything else -- see +icons/LICENSES.md. ICON_FILLED compensates for Health Icons' filled-path art +rendering optically smaller than Lucide's stroke art at the same box size. + +Output: the "pillars-b3" entry in figures.toml (override with --out); render +with render_excalidraw.py. +""" +from __future__ import annotations + +import argparse +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from excalib import els, base, text, image, save, figure, AMBER, TEAL, VIOLET, ROSE, GREEN, CAPTION # noqa: E402 + +# ---------------------------------------------------------------- layout constants +CONTENT_W = 880 +BAND_GAP = 24 # the vertical gap wherever two bands do NOT join +PAD_BAND = 20 # a band's own inner padding on all sides +GAP = 20 # gap between sibling blocks inside a band, both rows +ICON = 28 # harmonized icon render size +ICON_FILLED = 32 # filled-path icon sets need a bigger box to match optically +CORNER_R = 32 # Excalidraw's adaptive corner radius for a band-sized rectangle + +# ---------------------------------------------------------------- joint constants +TOOTH_RATIO = 2 # every joint: a tooth is this many gaps wide +JOINT_OVERRUN = 3.0 # a gap's fill runs this far past the lower level, to bury + # the upper band's own border where it crosses the gap +LEAD_GAP = 10.0 # clearance between a lead line and the wave's upper level + +SEAM_DEPTH = 36.0 # Data Structures -> Foundation Context +SEAM_WORDS = ["finding", "diagnosis", "location", "exam type"] +SEAM_LEAD = "woven together at every:" + +JOINT_DEPTH = 24.0 # the two smaller joints +APP_WORDS = ["create", "read", "resolve", "author"] +APP_LEAD = "an application can:" +SDK_WORDS = ["wraps", "manipulates instances"] + +LANE_W = 34.0 # the narrow violet tooth that carries on down. 28 read as a + # wire rather than as structure, so this is a little wider. +LANE_CLEAR = 12.0 # clear space between the Data Structures cards and the lane +# The lane's foot: one wide violet tooth cut into the Foundation band's top edge, +# at the same depth as the seam's own teeth, so the whole of that edge is one +# castellated line -- four teal teeth and, where the lane lands, a violet one. +# Two separate teeth were the brief; they cannot be drawn, because a tooth wide +# enough for the longer phrase does not sit under a 34px lane, so the second +# would float unattached. The middot carries the two roles instead. +FOOT_LABEL = "attaches to · authors & maintains" +FOOT_PAD = 20.0 # horizontal padding inside the foot, as in the other wide teeth + +DATA_TOP_PAD = 34.0 # Data Structures' header, clear of the violet teeth +FOUND_TOP_PAD = 45.0 # Foundation Context's header, clear of the teal teeth +FOUND_HDR_GAP = 12.0 +FOUND_ROW_SHRINK = 11.0 +FOUND_BOTTOM_PAD = 15.0 + +# Measured, not estimated: excalib.text()'s 0.58 * fontSize per character is +# 25-40% wide for these strings, which blew the "illustrates" plate out to twice +# the width of its own text and swallowed the connector either side of it. Only +# strings positioned or sized by their width need an entry; a word centred in a +# tooth does not. Widths in px for Helvetica as headless Chromium resolves it -- +# see "Measuring text instead of estimating it" in tools/diagrams/README.md. +MEASURED = { + ("illustrates", 13): 54.9, + (SEAM_LEAD, 13): 142.4, + (APP_LEAD, 13): 108.4, + (FOOT_LABEL, 13): 190.0, +} +FOOT_W = MEASURED[(FOOT_LABEL, 13)] + 2 * FOOT_PAD + +_uid = [0] + + +def uid(prefix: str) -> str: + _uid[0] += 1 + return f"{prefix}{_uid[0]}" + + +def even_teeth(x0: float, x1: float, n: int) -> list[tuple[float, float]]: + """n teeth evenly spread between x0 and x1, each TOOTH_RATIO units wide with + a one-unit gap between them and at both ends, filling the span exactly.""" + unit = (x1 - x0) / (n * TOOTH_RATIO + n + 1) + return [(x0 + ((TOOTH_RATIO + 1) * i + 1) * unit, + x0 + ((TOOTH_RATIO + 1) * i + 1 + TOOTH_RATIO) * unit) for i in range(n)] + + +# ---------------------------------------------------------------- primitives +def band_rect(id_: str, x: float, y: float, w: float, h: float, pal: dict, at_front=False) -> dict: + """Draw a band's background rectangle. Pass at_front=True when the band's + true height is only known after laying out its header/blocks (they were + already appended to els) -- this inserts the rect at index 0 so it still + paints behind its own contents instead of covering them. + + The order this produces is what makes the joints work: each band rect is + inserted at 0 after the one above it, so the stack paints back to front, + bottom band first, and every band covers the one below it wherever they + overlap. castellate() then only has to put the lower band's colour back.""" + r = base("rectangle", id_, x, y, w, h, pal["stroke"], pal["band"]) + r["roundness"] = {"type": 3} + if at_front: + els.insert(0, r) + else: + els.append(r) + return r + + +def band_header(x: float, y: float, icon_path: str, pal: dict, title: str, + subtitle: str | None, size=20, filled_icon=False) -> float: + """Bold band title with its icon to the left, subtitle below. A band whose + edges already say what it does takes subtitle=None. Returns bottom y.""" + s = ICON_FILLED if filled_icon else ICON + image(uid("bicon"), x, y - (s - ICON) / 2 - 2, s, s, icon_path, color=pal["stroke"]) + els.append(text(uid("btitle"), x + ICON + 12, y, title, size=size, color=pal["text"])) + y2 = y + size * 1.3 + if subtitle is None: + return y2 + els.append(text(uid("bsub"), x + ICON + 12, y2, subtitle, size=13, color=pal["mid"])) + return y2 + 13 * 1.25 + + +def icon_block_h(lines: list[str], desc: str | None = None) -> float: + """Height an icon_block needs for this many label lines/descriptor.""" + n_title = len(lines) + return 12 + ICON + 8 + n_title * 15 * 1.25 + (13 * 1.25 + 2 if desc else 0) + 10 + + +def icon_block(x: float, y: float, w: float, h: float, icon_path: str, pal: dict, + lines: list[str], desc: str | None = None, filled_icon=False) -> dict: + """Icon centered on top, label line(s) centered below, optional lighter + descriptor line last. Background is the band's own deeper pastel so the + block reads as 'inside' the band, not a separate object. Content is + centered in h, so passing an h below icon_block_h() trims the block's own + padding without touching the icon or the type.""" + r = base("rectangle", uid("blk"), x, y, w, h, pal["stroke"], pal["block"], sw=1) + r["roundness"] = {"type": 3} + els.append(r) + cx = x + w / 2 + content_h = icon_block_h(lines, desc) - 22 # minus this fn's fixed 12+10 top/bottom pad + top = y + (h - content_h) / 2 + s = ICON_FILLED if filled_icon else ICON + image(uid("blki"), cx - s / 2, top - (s - ICON) / 2, s, s, icon_path, color=pal["stroke"]) + ty = top + ICON + 6 + for ln in lines: + els.append(text(uid("blkt"), x + 6, ty, ln, size=15, color=pal["text"], align="center", w=w - 12)) + ty += 15 * 1.25 + if desc: + els.append(text(uid("blkd"), x + 6, ty + 2, desc, size=13, color=pal["mid"], align="center", w=w - 12)) + return r + + +def chip(cx: float, y_top: float, s: str, fill: str, stroke: str, color: str, + size=13, pad_x=9, pad_y=3) -> float: + """A short caption on its own opaque rounded plate, centered on cx, so it + reads as a label for what it sits over and never mixes with a line or a + band fill behind it. Returns bottom y.""" + w = MEASURED.get((s, size), len(s) * size * 0.58) + 2 * pad_x + h = size * 1.25 + 2 * pad_y + r = base("rectangle", uid("chip"), cx - w / 2, y_top, w, h, stroke, fill, sw=1) + r["roundness"] = {"type": 3} + els.append(r) + els.append(text(uid("chipt"), cx - w / 2 + pad_x, y_top + pad_y, s, size=size, color=color, + align="center", w=w - 2 * pad_x)) + return y_top + h + + +def hconn(x1: float, x2: float, y: float, color: str, sw=1) -> None: + """A plain horizontal connector.""" + a = base("line", uid("conn"), x1, y, x2 - x1, 0, color, "transparent", sw=sw) + a.update({"points": [[0, 0], [x2 - x1, 0]], "boundElements": None}) + els.append(a) + + +def fill_rect(x: float, y: float, w: float, h: float, fill: str) -> dict: + """An unstroked patch of colour -- used to carry one band's fill back + across the other band's, without either border's stroke showing through.""" + r = base("rectangle", uid("fill"), x, y, w, h, "transparent", fill, sw=1) + r["roundness"] = None + els.append(r) + return r + + +def stroke_line(pts: list[tuple[float, float]], color: str, sw=2, opacity=100) -> dict: + """A polyline through pts (absolute coordinates), sharp corners.""" + x0, y0 = pts[0] + xs = [p[0] for p in pts] + ys = [p[1] for p in pts] + ln = base("line", uid("ln"), x0, y0, max(xs) - min(xs), max(ys) - min(ys), color, "transparent", sw=sw) + ln.update({"points": [[p[0] - x0, p[1] - y0] for p in pts], "boundElements": None, "roundness": None}) + ln["opacity"] = opacity + els.append(ln) + return ln + + +def corner_fix(y_top: float, y_bot: float, pal: dict, anchor: dict, + x_from: float, x_to: float) -> None: + """A rounded rectangle's corner curves away from its own edge, so where a + band is overlapped at a joint its corner leaves a notch of bare page. Fill + the notch with that band's own colour. + + The patch is restacked to sit directly above `anchor` -- the band rect it + belongs to -- rather than on top of everything the joint is drawn after, so + it can be as deep as the corner radius without covering a header or a + card.""" + made = [fill_rect(x_from - 1, y_top, CORNER_R + 1, y_bot - y_top, pal["band"]), + fill_rect(x_to - CORNER_R, y_top, CORNER_R + 1, y_bot - y_top, pal["band"])] + for e in reversed(made): + els.remove(e) + els.insert(els.index(anchor) + 1, e) + + +# ============================================================ the castellated joint +def castellate(hi: float, lo: float, teeth: list[tuple[float, float]], words: list[str | None], + upper: dict, lower: dict, upper_band: dict, lower_band: dict, + x_from: float = 0.0, x_to: float = CONTENT_W, + lower_borders: tuple[bool, bool] = (True, True), + open_teeth: tuple[int, ...] = (), lead: str | None = None) -> None: + """Draw one interlocking edge between two overlapping bands. + + hi/lo are the wave's upper and lower levels: the lower band's rect starts at + hi, the upper band's ends at lo, and the upper band owns the whole overlap + until this runs. x_from/x_to bound the joint, which need not be the full + width. lower_borders says whether the lower band actually has a side border + at each end (it does not where the joint stops short of the band's own + edge). A tooth listed in open_teeth is left unclosed, for something else to + carry on downwards from. + """ + corner_fix(lo - CORNER_R, lo, upper, upper_band, x_from, x_to) + corner_fix(hi, hi + CORNER_R, lower, lower_band, x_from, x_to) + + # The lower band's colour back over the gaps: flush with the upper level, + # since the wave is stroked along it last, and past the lower one to bury + # the upper band's border. The end gaps overshoot the side borders too. + for x1, x2 in zip([x_from] + [t[1] for t in teeth], [t[0] for t in teeth] + [x_to]): + a = x1 - 1 if x1 == x_from else x1 + b = x2 + 1 if x2 == x_to else x2 + if b - a > 2: # a tooth flush with the end leaves no gap to fill + fill_rect(a, hi, b - a, lo - hi + JOINT_OVERRUN, lower["band"]) + for x, has_lower in ((x_from, lower_borders[0]), (x_to, lower_borders[1])): + stroke_line([(x, hi - CORNER_R), (x, hi)], upper["stroke"], sw=2) + if has_lower: + stroke_line([(x, hi), (x, lo + CORNER_R)], lower["stroke"], sw=2) + + # The wave itself, stroked once. An open tooth breaks it into two runs. + runs: list[list[tuple[float, float]]] = [] + cur: list[tuple[float, float]] = [(x_from, hi)] + for j, (x1, x2) in enumerate(teeth): + if j in open_teeth: + cur.append((x1, hi)) + runs.append(cur) + cur = [(x2, hi)] + else: + cur += [(x1, hi), (x1, lo), (x2, lo), (x2, hi)] + cur.append((x_to, hi)) + runs.append(cur) + for run in runs: + if len(set(run)) > 1: # an open tooth flush with the end leaves no run + stroke_line(run, upper["stroke"], sw=2) + + for word, (x1, x2) in zip(words, teeth): + if word: + els.append(text(uid("jw"), x1, hi + (lo - hi - 13 * 1.25) / 2, word, + size=13, color=upper["text"], align="center", w=x2 - x1)) + + # The lead's colon runs into the list, so it starts where the first tooth + # does; anywhere left of that it reads as a label for the first gap instead. + if lead: + els.append(text(uid("jl"), teeth[0][0], hi - LEAD_GAP - 13 * 1.25, lead, + size=13, color=upper["mid"])) + + +def draw_lane(x1: float, x2: float, y_top: float, foot_top: float, depth: float, + foot_w: float, label: str) -> None: + """The narrow violet tooth carried on down its lane, ending where the + Foundation band's top edge begins by widening into one tooth cut into it. + Drawn last, so it passes in front of every band and joint it crosses.""" + foot_left = x2 - foot_w + fill_rect(x1, y_top, x2 - x1, foot_top - y_top, VIOLET["band"]) + fill_rect(foot_left, foot_top, foot_w, depth, VIOLET["band"]) + stroke_line([(x1, y_top), (x1, foot_top), (foot_left, foot_top), + (foot_left, foot_top + depth), (x2, foot_top + depth), (x2, y_top)], + VIOLET["stroke"], sw=2) + els.append(text(uid("lanet"), foot_left, foot_top + (depth - 13 * 1.25) / 2, label, + size=13, color=VIOLET["text"], align="center", w=foot_w)) + + +# ============================================================ the stack +def build(out_path: str) -> str: + # ---------------------------------------- 5/4. Use Cases | Sample Applications + Y_TOP = 0 + GAP_MID = 120 # wide enough for "illustrates" to sit on the connector + # with a readable stub of line showing each side + PANEL_PAD = 16 # same as the SDK band's, so the two headers' + # identically built bands come out the same height + PANEL_INSET = PAD_BAND - 2 # same left inset as the full-width band headers + PANEL_TITLE = 18 + PANEL_H = PANEL_PAD + PANEL_TITLE * 1.3 + 13 * 1.25 + PANEL_PAD + # Sample Applications takes the larger share, so its four teeth get closer to + # the proportions of the seam's; Use Cases only has to hold its own subtitle. + use_w = (CONTENT_W - GAP_MID) * 0.4 + app_w = (CONTENT_W - GAP_MID) - use_w + APP_X = use_w + GAP_MID + + # The SDK band's top edge is where Sample Applications comes down to meet it; + # Use Cases keeps a plain gap above that same edge. + SDK_TOP = Y_TOP + PANEL_H + BAND_GAP + APP_LO = SDK_TOP + JOINT_DEPTH + + band_rect(uid("panel"), 0, Y_TOP, use_w, PANEL_H, ROSE) + app_panel = band_rect(uid("panel"), APP_X, Y_TOP, app_w, APP_LO - Y_TOP, GREEN) + band_header(PANEL_INSET, Y_TOP + PANEL_PAD, "icons/lucide-lightbulb.svg", ROSE, + "Use Cases", "what the project wants built", size=PANEL_TITLE) + band_header(APP_X + PANEL_INSET, Y_TOP + PANEL_PAD, "icons/lucide-app-window.svg", GREEN, + "Sample Applications", "what has been built", size=PANEL_TITLE) + conn_y = Y_TOP + PANEL_PAD + (PANEL_TITLE * 1.3 + 13 * 1.25) / 2 + hconn(use_w + 2, APP_X - 2, conn_y, CAPTION) + chip(use_w + GAP_MID / 2, conn_y - (13 * 1.25 + 6) / 2, "illustrates", + "#ffffff", "transparent", CAPTION) + + # ---------------------------------------- 3. SDKs (thin full-width band) + # No subtitle: the three joints on this band's two edges now say everything + # the subtitle used to, word for word. + sdk_hdr_bottom = band_header(PAD_BAND - 2, SDK_TOP + 16, "icons/lucide-package.svg", VIOLET, + "SDKs", None, size=18) + SDK_BOTTOM = sdk_hdr_bottom + 16 + SDK_LO = SDK_BOTTOM + JOINT_DEPTH + sdk_band = band_rect(uid("band"), 0, SDK_TOP, CONTENT_W, SDK_LO - SDK_TOP, VIOLET, at_front=True) + + # ---------------------------------------- 2. Data Structures + # A lane down the right margin is left clear in the card row below, for the + # narrow violet tooth to travel in. It runs flush with the bands' own right + # border: inset from it, the strip of band colour left outside the lane reads + # as a sliver rather than as a lane. It stops at the Foundation band's top + # edge, so that band keeps an even PAD_BAND margin on both sides. + LANE_X2 = CONTENT_W + LANE_X1 = LANE_X2 - LANE_W + ROW_RIGHT = LANE_X1 - LANE_CLEAR + + Y_DATA = SDK_BOTTOM + hdr_bottom = band_header(PAD_BAND - 2, Y_DATA + DATA_TOP_PAD, "icons/lucide-layers.svg", TEAL, + "Data Structures", "this patient · Patient Context", size=20) + BLOCK_ROW_Y = hdr_bottom + 14 + W4 = (ROW_RIGHT - PAD_BAND - 3 * GAP) / 4 + d_positions = [PAD_BAND + i * (W4 + GAP) for i in range(4)] + d_specs = [ + ("icons/lucide-clipboard.svg", ["Observation"], False), + ("icons/lucide-clipboard-list.svg", ["Exam Finding", "List"], False), + ("icons/lucide-list.svg", ["Imaging Problem", "List"], False), + ("icons/healthicons-person.svg", ["Imaging Persona"], True), + ] + ROW_H_DATA = max(icon_block_h(lines) for _, lines, _ in d_specs) + for xpos, (ipath, lines, filled) in zip(d_positions, d_specs): + icon_block(xpos, BLOCK_ROW_Y, W4, ROW_H_DATA, ipath, TEAL, lines, filled_icon=filled) + DATA_BLOCKS_BOTTOM = BLOCK_ROW_Y + ROW_H_DATA + + # room below the cards for the seam's lead line and the seam's own swing + DATA_BAND_BOTTOM = DATA_BLOCKS_BOTTOM + SEAM_DEPTH + 2 * LEAD_GAP + 13 * 1.25 + teal_band = band_rect(uid("band"), 0, Y_DATA, CONTENT_W, DATA_BAND_BOTTOM - Y_DATA, TEAL, at_front=True) + + # ---------------------------------------- 1. Foundation Context (bottom) + FOUND_TOP = DATA_BAND_BOTTOM - SEAM_DEPTH + hdr_bottom_f = band_header(PAD_BAND - 2, FOUND_TOP + FOUND_TOP_PAD, + "icons/lucide-book-open.svg", AMBER, + "Foundation Context", "shared, curated knowledge", size=20) + F_ROW_Y = hdr_bottom_f + FOUND_HDR_GAP + W3 = (CONTENT_W - 2 * PAD_BAND - 2 * GAP) / 3 + f_positions = [PAD_BAND + i * (W3 + GAP) for i in range(3)] + f_specs = [ + ("icons/lucide-tag.svg", ["Finding/diagnosis definitions"], "finding models · CDEs", False), + ("icons/healthicons-lungs.svg", ["Anatomic locations"], "anchored in RadLex", True), + ("icons/healthicons-xray.svg", ["Exam types"], "LOINC/RSNA Playbook", True), + ] + ROW_H_FOUND = max(icon_block_h(lines, desc) for _, lines, desc, _ in f_specs) - FOUND_ROW_SHRINK + for xpos, (ipath, lines, desc, filled) in zip(f_positions, f_specs): + icon_block(xpos, F_ROW_Y, W3, ROW_H_FOUND, ipath, AMBER, lines, desc=desc, filled_icon=filled) + FOUND_BAND_BOTTOM = F_ROW_Y + ROW_H_FOUND + FOUND_BOTTOM_PAD + amber_band = band_rect(uid("band"), 0, FOUND_TOP, CONTENT_W, FOUND_BAND_BOTTOM - FOUND_TOP, + AMBER, at_front=True) + + # ---------------------------------------- the three joints, top to bottom + castellate(SDK_TOP, APP_LO, even_teeth(APP_X, CONTENT_W, len(APP_WORDS)), APP_WORDS, + GREEN, VIOLET, app_panel, sdk_band, x_from=APP_X, x_to=CONTENT_W, + lower_borders=(False, True), lead=APP_LEAD) + + sdk_teeth = even_teeth(0, LANE_X1, len(SDK_WORDS)) + [(LANE_X1, LANE_X2)] + castellate(SDK_BOTTOM, SDK_LO, sdk_teeth, SDK_WORDS + [None], VIOLET, TEAL, + sdk_band, teal_band, open_teeth=(len(SDK_WORDS),)) + + # The seam's own teeth share this edge with the lane's foot, so they are laid + # out in the width the foot leaves: one more gap, then the foot, flush right. + castellate(FOUND_TOP, DATA_BAND_BOTTOM, even_teeth(0, CONTENT_W - FOOT_W, len(SEAM_WORDS)), + SEAM_WORDS, TEAL, AMBER, teal_band, amber_band, lead=SEAM_LEAD) + + draw_lane(LANE_X1, LANE_X2, SDK_BOTTOM, FOUND_TOP, SEAM_DEPTH, FOOT_W, FOOT_LABEL) + + print(f"foundation band below its edge: {FOUND_BAND_BOTTOM - FOUND_TOP:.0f}px; " + f"figure {FOUND_BAND_BOTTOM:.0f}px tall") + return save(out_path) + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + ap.add_argument("--out", default=None, + help="output path relative to the repo root " + "(default: the pillars-b3 entry in figures.toml)") + args = ap.parse_args() + build(args.out or figure("pillars-b3").excalidraw) + + +if __name__ == "__main__": + main() diff --git a/tools/prepare_site_content.py b/tools/prepare_site_content.py deleted file mode 100644 index fafe0c0..0000000 --- a/tools/prepare_site_content.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -"""Stage the OKF bundle for the site build. - -Copies `knowledge/` to a staging directory and rewrites every internal markdown -link to the bundle-absolute form (`/dir/file.md`). The bundle itself uses two -link styles the OKF spec allows: bundle-absolute links in concept documents and -`./file.md` links in directory indexes. Quartz resolves either style correctly -on its own but not both at once, so the site builds from this normalized copy. -The bundle in git is untouched. - -Usage: python3 tools/prepare_site_content.py [--src knowledge] [--dest site/staged-content] -""" -from __future__ import annotations - -import argparse -import os -import re -import shutil - -LINK = re.compile(r"(\]\()(?!(?:https?:|mailto:|#|/))([^)\s]+)(\s+\"[^\"]*\")?(\))") -FENCE = re.compile(r"```.*?```", re.S) - - -def rewrite(text: str, rel_dir: str) -> str: - # Leave fenced code untouched by splitting on fences. - parts, last, out = [], 0, [] - for m in FENCE.finditer(text): - parts.append((text[last:m.start()], False)) - parts.append((m.group(0), True)) - last = m.end() - parts.append((text[last:], False)) - for chunk, is_fence in parts: - if is_fence: - out.append(chunk) - continue - - def sub(m: re.Match) -> str: - target = m.group(2) - path, _, frag = target.partition("#") - resolved = os.path.normpath(os.path.join(rel_dir, path)) if path else "" - if resolved in (".", ""): - resolved = "" - new = "/" + resolved.replace(os.sep, "/") - if path.endswith("/") and not new.endswith("/"): - new += "/" - if frag: - new += "#" + frag - return f"{m.group(1)}{new}{m.group(3) or ''}{m.group(4)}" - - out.append(LINK.sub(sub, chunk)) - return "".join(out) - - -def main() -> int: - ap = argparse.ArgumentParser() - ap.add_argument("--src", default="knowledge") - ap.add_argument("--dest", default="site/staged-content") - args = ap.parse_args() - root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - src, dest = os.path.join(root, args.src), os.path.join(root, args.dest) - if os.path.isdir(dest): - shutil.rmtree(dest) - shutil.copytree(src, dest) - n = 0 - for dirpath, _, files in os.walk(dest): - for name in files: - if not name.endswith(".md"): - continue - p = os.path.join(dirpath, name) - rel_dir = os.path.relpath(dirpath, dest) - rel_dir = "" if rel_dir == "." else rel_dir - with open(p, encoding="utf-8") as fh: - text = fh.read() - new = rewrite(text, rel_dir) - if new != text: - with open(p, "w", encoding="utf-8") as fh: - fh.write(new) - n += 1 - print(f"staged {args.src} -> {args.dest}; rewrote links in {n} files") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tools/sources.py b/tools/sources.py new file mode 100644 index 0000000..342e41c --- /dev/null +++ b/tools/sources.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +"""Recover and verify source artifacts from a reviewed manifest using the Tigris CLI.""" +from __future__ import annotations + +import argparse +from concurrent.futures import ThreadPoolExecutor +import hashlib +import json +import os +from pathlib import Path, PurePosixPath +import re +import shutil +import subprocess +import sys +import tempfile + +REPO = Path(__file__).resolve().parents[1] +DEFAULT_MANIFEST = REPO / 'docs/sources-manifest.json' + + +class SourceError(Exception): + pass + + +def relative_path(value: object) -> str: + if not isinstance(value, str) or not value or '\\' in value: + raise SourceError('Artifact paths must be nonempty POSIX relative paths') + p = PurePosixPath(value) + if p.is_absolute() or any(s in ('', '.', '..') for s in value.split('/')): + raise SourceError('Artifact paths must not escape the source directory') + if any(c in value for c in '*?[]{}\x00\r\n') or value.startswith('-'): + raise SourceError('Artifact paths must be literal, not CLI patterns') + return value + + +def load_manifest(path: Path) -> dict: + try: + data = json.loads(path.read_text()) + except (OSError, ValueError) as exc: + raise SourceError(f'Cannot read manifest: {path}') from exc + if not isinstance(data, dict) or data.get('schema_version') != 1: + raise SourceError('Unsupported manifest schema_version') + if not re.fullmatch(r'[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]', str(data.get('bucket', ''))): + raise SourceError('Invalid bucket name') + if not isinstance(data.get('sources'), list) or not isinstance(data.get('files'), list): + raise SourceError('Manifest requires sources and files lists') + source_ids = set() + for source in data['sources']: + if not isinstance(source, dict) or not re.fullmatch(r'[a-z0-9][a-z0-9-]*', str(source.get('id', ''))): + raise SourceError('Invalid source ID') + if source['id'] in source_ids: + raise SourceError('Duplicate source ID') + source_ids.add(source['id']) + if source.get('sensitivity') not in ('public', 'restricted-until-published', 'internal'): + raise SourceError(f'Invalid sensitivity for {source["id"]}') + if not source.get('title') or not source.get('handling'): + raise SourceError(f'Missing title or handling for {source["id"]}') + ids, paths, keys = set(), set(), set() + for item in data['files']: + if not isinstance(item, dict): + raise SourceError('Invalid artifact entry') + if not re.fullmatch(r'[a-z0-9][a-z0-9-]*', str(item.get('id', ''))): + raise SourceError('Invalid artifact ID') + path_value = relative_path(item.get('path')) + key = relative_path(item.get('bucket_key')) + if item['id'] in ids or path_value in paths or key in keys: + raise SourceError('Duplicate artifact ID, path, or bucket key') + ids.add(item['id']); paths.add(path_value); keys.add(key) + if item.get('source_id') not in source_ids: + raise SourceError(f'Unknown source for {item["id"]}') + if type(item.get('size')) is not int or item['size'] < 0: + raise SourceError(f'Invalid size for {item["id"]}') + if not re.fullmatch(r'[0-9a-f]{64}', str(item.get('sha256', ''))): + raise SourceError(f'Invalid SHA-256 for {item["id"]}') + if item.get('role') not in ('original', 'capture', 'extracted-text', 'extracted-figure', 'normalized-figure', 'capture-metadata'): + raise SourceError(f'Invalid role for {item["id"]}') + if not item.get('description') or not item.get('added_at'): + raise SourceError(f'Missing description or added_at for {item["id"]}') + # Parent artifacts are explicit and must belong to the same source. + by_id = {item['id']: item for item in data['files']} + for item in data['files']: + parents = item.get('derived_from', []) + if not isinstance(parents, list) or not all(isinstance(p, str) for p in parents): + raise SourceError('derived_from must be an artifact-ID list') + for parent in parents: + if parent not in ids or parent == item['id'] or by_id[parent]['source_id'] != item['source_id']: + raise SourceError(f'Invalid parent for {item["id"]}') + if item['role'] not in ('original', 'capture') and not parents: + raise SourceError(f'Derivative has no parent: {item["id"]}') + def visit(id_: str, stack: set[str], done: set[str]) -> None: + if id_ in stack: + raise SourceError('Derivative cycle') + if id_ in done: + return + for parent in by_id[id_].get('derived_from', []): + visit(parent, stack | {id_}, done) + done.add(id_) + done: set[str] = set() + for id_ in ids: + visit(id_, set(), done) + if not ids or source_ids != {item['source_id'] for item in data['files']}: + raise SourceError('Every source must have at least one artifact') + return data + + +def target_path(root: Path, item: dict) -> Path: + path = root / item['path'] + # The configured root may itself be a symlink; artifact paths inside it may not. + current = root + for part in PurePosixPath(item['path']).parts: + current = current / part + if current.is_symlink(): + raise SourceError(f'Artifact path contains a symlink: {item["id"]}') + return path + + +def matches(path: Path, item: dict) -> bool: + if not path.is_file() or path.stat().st_size != item['size']: + return False + h = hashlib.sha256() + with path.open('rb') as f: + for chunk in iter(lambda: f.read(1024 * 1024), b''): + h.update(chunk) + return h.hexdigest() == item['sha256'] + + +def download(bucket: str, item: dict, destination: Path) -> None: + try: + result = subprocess.run( + ['t3', 'cp', f't3://{bucket}/{item["bucket_key"]}', str(destination)], + capture_output=True, text=True, timeout=300, + ) + except (OSError, subprocess.TimeoutExpired) as exc: + raise SourceError(f'Download failed for {item["id"]}; check t3 credentials and connectivity') from exc + if result.returncode: + raise SourceError(f'Download failed for {item["id"]}; check t3 credentials and bucket access') + + +def recover(root: Path, bucket: str, item: dict) -> str: + target = target_path(root, item) + if target.exists(): + if matches(target, item): + return 'present' + raise SourceError(f'Existing file differs; refusing to overwrite {item["id"]}') + target.parent.mkdir(parents=True, exist_ok=True) + target_path(root, item) + fd, temporary = tempfile.mkstemp(prefix='.source-download-', dir=target.parent) + os.close(fd) + temp = Path(temporary) + try: + download(bucket, item, temp) + if not matches(temp, item): + raise SourceError(f'Download checksum mismatch for {item["id"]}') + # Publish only verified bytes, without replacing a file created concurrently. + try: + os.link(temp, target) + except FileExistsError as exc: + if not target.is_symlink() and matches(target, item): + return 'present' + raise SourceError(f'Destination appeared during download: {item["id"]}') from exc + return 'downloaded' + finally: + temp.unlink(missing_ok=True) + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('action', choices=['manifest', 'check', 'pull']) + ap.add_argument('--manifest', type=Path, default=DEFAULT_MANIFEST) + ap.add_argument('--root', type=Path, default=Path(os.environ.get('OIDM_SOURCES_DIR', '~/data/oidm-knowledge-sources')).expanduser()) + ap.add_argument('--source', action='append', default=[], help='Select a source ID; repeat for several sources') + ap.add_argument('--jobs', type=int, choices=range(1, 17), default=4, metavar='1..16') + args = ap.parse_args() + try: + data = load_manifest(args.manifest) + source_ids = {s['id'] for s in data['sources']} + if set(args.source) - source_ids: + raise SourceError('Unknown source ID: ' + ', '.join(sorted(set(args.source) - source_ids))) + items = [f for f in data['files'] if not args.source or f['source_id'] in args.source] + if args.action == 'manifest': + print(f'Manifest valid: {len(data["sources"])} sources, {len(data["files"])} artifacts') + return 0 + root = args.root.expanduser().resolve() + missing = [] + for item in items: + target = target_path(root, item) + if target.exists(): + if not matches(target, item): + raise SourceError(f'Existing file differs; refusing to overwrite {item["id"]}') + else: + missing.append(item) + if args.action == 'check': + if missing: + raise SourceError(f'{len(missing)} missing artifacts: ' + ', '.join(i['id'] for i in missing)) + print(f'Verified {len(items)} artifacts by size and SHA-256') + return 0 + if missing and not shutil.which('t3'): + raise SourceError('Install the Tigris CLI (t3) before pulling sources') + with ThreadPoolExecutor(max_workers=args.jobs) as pool: + list(pool.map(lambda item: recover(root, data['bucket'], item), missing)) + print(f'Verified {len(items)} artifacts; downloaded {len(missing)}, already present {len(items) - len(missing)}') + return 0 + except (SourceError, OSError) as exc: + print(f'Source error: {exc}', file=sys.stderr) + return 1 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tools/verify.py b/tools/verify.py new file mode 100644 index 0000000..54406cf --- /dev/null +++ b/tools/verify.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = ["pyyaml"] +# /// +"""Mark bundle documents as verified by a human, or report review status. + + uv run tools/verify.py knowledge/glossary/oifm.md [more paths...] mark verified + uv run tools/verify.py --by human:someone knowledge/... different reviewer + uv run tools/verify.py --status counts per directory + uv run tools/verify.py --list knowledge/glossary draft documents under a directory + +Marking adds a `verified` entry with the current UTC time, sets `status: stable`, +and leaves everything else in the frontmatter untouched (text edit, not a YAML +round-trip, so key order and quoting survive). +""" +from __future__ import annotations + +import argparse +import datetime as dt +import glob +import os +import re +import sys + +import yaml + +REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +FM = re.compile(r"^---\n(.*?)\n---\n", re.S) + + +def load(path: str) -> tuple[str, dict, str]: + text = open(path, encoding="utf-8").read() + m = FM.match(text) + if not m: + raise SystemExit(f"{path}: no frontmatter") + return text, yaml.safe_load(m.group(1)) or {}, m.group(1) + + +def mark(path: str, by: str) -> None: + text, data, fm = load(path) + now = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + entry = f" - {{ by: {by}, at: {now} }}" + if "verified" in data: + # append to an existing list (normalize a single mapping to a list) + if isinstance(data["verified"], dict): + fm = re.sub(r"^verified:.*$", f"verified:\n - {{ by: {data['verified']['by']}, at: {data['verified']['at']} }}\n{entry}", fm, count=1, flags=re.M) + else: + fm = re.sub(r"^(verified:\n(?: - .*\n?)*)", lambda m: m.group(1).rstrip("\n") + "\n" + entry + "\n", fm, count=1, flags=re.M) + else: + # insert after generated, else at the end + if re.search(r"^generated:.*$", fm, flags=re.M): + fm = re.sub(r"^(generated:.*)$", r"\1\nverified:\n" + entry, fm, count=1, flags=re.M) + else: + fm = fm.rstrip("\n") + "\nverified:\n" + entry + if re.search(r"^status:.*$", fm, flags=re.M): + fm = re.sub(r"^status:.*$", "status: stable", fm, count=1, flags=re.M) + else: + fm += "\nstatus: stable" + head = FM.match(text) + assert head is not None + new = "---\n" + fm + "\n---\n" + text[head.end():] + open(path, "w", encoding="utf-8").write(new) + print(f"verified {os.path.relpath(path, REPO)} by {by} at {now}") + + +def concepts(root: str) -> list[str]: + out = [] + for p in sorted(glob.glob(os.path.join(root, "**", "*.md"), recursive=True)): + if os.path.basename(p) in ("index.md", "log.md"): + continue + out.append(p) + return out + + +def status_report(bundle: str) -> None: + rows: dict[str, list[int]] = {} + for p in concepts(bundle): + _, data, _ = load(p) + d = os.path.relpath(os.path.dirname(p), bundle) + d = d.split(os.sep)[0] if d != "." else "." + row = rows.setdefault(d, [0, 0]) + row[1] += 1 + if data.get("status", "stable") == "stable" and data.get("verified"): + row[0] += 1 + total_v = sum(r[0] for r in rows.values()); total = sum(r[1] for r in rows.values()) + print(f"{'directory':28} verified / total") + for d, (v, n) in sorted(rows.items()): + print(f"{d:28} {v:3} / {n}") + print(f"{'all':28} {total_v:3} / {total}") + + +def list_drafts(root: str) -> None: + for p in concepts(root): + _, data, _ = load(p) + if not data.get("verified"): + print(os.path.relpath(p, REPO)) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("paths", nargs="*") + ap.add_argument("--by", default="human:talkasab") + ap.add_argument("--status", action="store_true") + ap.add_argument("--list", metavar="DIR") + a = ap.parse_args() + if a.status: + status_report(os.path.join(REPO, "knowledge")); return 0 + if a.list: + list_drafts(os.path.join(REPO, a.list)); return 0 + if not a.paths: + ap.print_help(); return 2 + for p in a.paths: + mark(os.path.join(REPO, p) if not os.path.isabs(p) else p, a.by) + return 0 + + +if __name__ == "__main__": + sys.exit(main())