medOS ultra

Data Activation & Integration Center

HDAP as the one center for data-in + AI-analysis: connector catalog, vertical presets, spreadsheet ingest, smart intake inbox.

8 min read diagramsUpdated 2026-06-11docs/architecture/data-activation-integration-center.md

Status: P0 shipped + sandbox-verified 2026-06-10. Lives at HDAP · HORUS — Data & Decision Hub (/admin/hdap), surfaced as its own category + shortcuts on the Super-Admin Dashboard.

One center that groups everything about getting data into the platform and making it decide-able: external-system connectors, disparate-source activation (Excel / Confluence / folders of files), local-AI analysis over the activated data, and per-archetype vertical presets (Teaching Hospital, Wellness Center). Built so the AI is recommender-first everywhere: it proposes, a human files/accepts — that gesture is the training label.

1. The hub groups (web/src/pages/admin/hdap/index.tsx)

Group Tiles What it answers
Connect & activate Connector Store · Sync-Health Upload · Cost Capture · Migration Review “How does data get IN?”
Document Intelligence & AI analysis Smart Intake Inbox · Document Activation · AI Analysis · RAG Corpora “How do disparate files become usable + analyzable?”
Deployment verticals Verticals (Teaching Hospital / Wellness Center / …) “Which sources/corpora/analyses does THIS deployment need?”
Monitor Connector Activity & Topology “Is the data flowing?”
Platform integrations Webhooks · Automation Engine · FHIR Integrations · Auth Data Studio · Coding Connectors internal/event surface
Decide Decision Twin · HORUS Command Center · Decision Protocols “What do we do about it?”

Routes all under /admin/hdap/* (web/src/routes/AdminRoutes.tsx); each page is fail-soft and renders demo data with zero backend.

2. Pieces shipped this pass

Piece Files Notes
Connector catalog extension web/packages/integration-kit/src/types/connector-catalog.ts +6 categories (documents/knowledge, spreadsheet file-drop, wellness wearables, member CRM, research registry, learning) + 21 connectors with honest statuses; ConnectorStore icon/color maps extended
Vertical presets (data-driven) web/packages/integration-kit/src/verticalPresets.ts VERTICAL_PRESETS keyed by archetype: recommended connectors + corpora + AI analysis use-cases. Teaching Hospital + Wellness Center are rows, not forks. Resolver web/src/config/dataActivationVertical.ts maps FacilityProfile → vertical with a localStorage super-admin override (region.config.ts already ships a wellness profile + his-wellness deployment)
Hospital-systems topics web/packages/integration-kit/src/intake/topics.ts DATA_TOPICS — the taxonomy every activated document files under: Finance & Budget · Billing & RCM · HR & Workforce · Supply & Inventory · Quality & Safety · Clinical Ops · Policies & SOPs · Committees & M&M · Research & Education · Wellness & Members. Each topic = corpus code + the platform surface it feeds (Cost Capture, Billing Admin, HORUS KPIs, Inventory…) + bilingual classifier keywords. classifyTopic() is the deterministic AI-sort; adding a topic = one row. Both modes ship: manual pick (topic grid) AND AI-sort (suggested chip, human confirms)
Spreadsheet ingest web/packages/integration-kit/src/components/SpreadsheetIngestPanel.tsx xlsx/CSV parsed in-browser (raw:false for faithful dates) → “File under — hospital system” topic grid with the AI-sorted suggestion preselected (corpus override demoted to advanced) → row→text records → ingestCorpusManual (RAG). Preview-only without a client
AI Analysis web/packages/integration-kit/src/components/DataAnalysisPanel.tsx Local Ollama RAG via llmChat, cited sources, Accept/Edit/Reject disposition (the label). Demo answer when the model is down
Smart Intake Inbox see §3 bulk folder drop → AI reads → proposes patient/corpus filing
Sandbox targets ?target=ConnectorStore · VerticalPresets · SpreadsheetIngest · DataAnalysis · DocumentIntake · HdapHub all verified

3. Smart Intake Inbox (/admin/hdap/intake-inbox)

The ask: “throw a folder of files at it; the AI reads each one and assigns it to the right patient.”

Pipeline (per file)

parse (browser) → identity signals → patient match → doc-type classify → PROPOSE
folder name  >  filename  >  content regex  >  AI guess     (priority order)
  • Identity regexes (web/packages/integration-kit/src/intake/extract.ts): labeled HN (HN/H.N./เอชเอ็น + value), bare 8–9-digit tokens (trusted only in folder/filename segments — too noisy in content), AN-prefixed admission numbers, 13-digit Thai citizen ID (checksum-validated), Thai honorific names (นาย/นาง/นางสาว/ด.ช./ด.ญ.) + labeled EN names. HN shapes ground-truthed against global-sequence (hn.func.ts: prefix + 2-digit BE/AD year + padded sequence → 660015778, P67000123).
  • Text extraction: xlsx/xls via XLSX (formatted text), txt/md/csv/json/xml/html/hl7 natively. PDF/images/docx = binary → no content text in P0 (OCR/pdf-parse is the P1 ingest-documents edge fn); their folder + filename signals still match — the most common real case (scan_HN680027056.pdf) files correctly.
  • AI pass (only when the visible “AI read assist” toggle is on, regex left gaps, and text exists): extractWithAi → local LLM with a strict-JSON prompt (buildAiExtractionPrompt), refines {hn, name, docType}. Output is validated/coerced (numeric hn → string, docType whitelisted). Fail-soft: model down ⇒ regex-only.
  • Matching: injected searchPatients — live page wires searchPatientByText (GET /v2/administration/patients/searchByText), exact-HN filtered client-side because that endpoint is fuzzy (and it degrades to “no match” rather than erroring rows when the REST gateway is down); sandbox falls back to a bilingual demo roster. Search order HN → AN → citizen-ID → name. Confidence: deterministic exact HN = 1.0 → auto_matched; AI-derived HN is capped at 0.85 and can never auto-file; AN 0.9; citizen-ID 0.85 single-hit / 0.6 multi (fuzzy search can’t verify the ID client-side — never presented as near-certain); single name hit 0.75 → suggested; multi-hit → ambiguous (candidate dropdown); none → no_match. Non-patient content (budgets/rosters/minutes) → institutional → proposed into a RAG corpus instead of a chart.
  • Modes (mirrors auto_assign_configs.mode): suggest (default — human accepts each row) and auto (pre-accepts only deterministic exact-HN matches — confidence 1 is unreachable for AI-guessed HNs by construction in rankCandidates). Decisions: accepted / edited (user re-assigned — the strongest label) / rejected; double-accept is guarded (no duplicate ingest).
  • Institutional docs are AI-sorted into hospital-systems topics (intake/topics.ts): a budget sheet proposes 💰 Finance & Budget (“feeds Cost Capture”), a roster 🧑‍⚕️ HR & Workforce, P&T minutes 🗂️ Committees & M&M — never a generic knowledge-base pocket. The topic routes to its corpus (topic.corpusCode), falling back to the first active corpus with an on-row note when that corpus isn’t seeded yet.
  • Commit path (P0, honest): institutional rows really embed via ingestCorpusManual(corpus.mongo_id ‖ id, …) (backend resolves the Mongo id, not the Supabase UUID); patient-chart rows return committed:false from assign so the chip reads “Confirmed” (never “Filed”) until the P1 commit path exists. Assign failures keep the decision but surface “Filing failed — decision recorded only”. Decisions persist to a local rolling queue (medos.intakeInbox.decisionLog.v1, 500 cap) until the llm_audit_log endpoint lands.

Files

File Role
web/packages/integration-kit/src/intake/types.ts contracts: IntakeRow, IntakeClient, statuses, decisions
web/packages/integration-kit/src/intake/extract.ts pure logic: regexes, doc-type classifier, ranking, demo roster
web/packages/integration-kit/src/intake/topics.ts hospital-systems topic registry + classifyTopic (the AI-sort)
web/packages/integration-kit/src/intake/__tests__/extract.test.ts Jest suite (15 tests): checksum, signal priority, honorific strip, AI-HN auto-file cap, citizen-id honesty, topic sort
web/packages/integration-kit/src/components/DocumentIntakeInbox.tsx panel: folder drop (webkitGetAsEntry traversal — first folder-drop in the codebase), bounded-concurrency pipeline (3 workers), review queue, bulk accept
web/src/pages/admin/hdap/intake-inbox.tsx live wiring: patient search + local-LLM extraction + corpus ingest, offline fallback
web/sandbox/targets/DocumentIntakeTarget.tsx ?target=DocumentIntake demo

4. Invariants

  1. Recommender-first: AI proposes; only a human (or a deterministic exact-HN rule the human switched on) files. The AI’s guessed HN is structurally barred from auto-filing (rankCandidates caps it below the auto threshold). Accept/edit/reject is logged as the training label — never written to the chart (matches cowork_proposals / clinical-AI label-in-workflow).
  2. Visible data path: files are parsed client-side; when the labelled “AI read assist” toggle is on, a short snippet (first 1.5k chars) goes to the local self-hosted model for identity reading — stated on the drop zone, switchable off. Nothing is filed anywhere until a human accepts (or enabled deterministic auto-file fires).
  3. Fail-soft everywhere: no backend ⇒ demo roster / demo answer / preview-only, always labelled.
  4. Honest statuses: nothing claims “filed to chart” until the P1 commit path exists; connector catalog keeps live/available/planned truthful.
  5. Verticals are data rows: a new archetype (rehab center, dialysis chain…) = one VERTICAL_PRESETS entry, zero new pages.
  6. Bilingual (TH/EN) labels and seed data throughout.

5. Review-hardening pass (2026-06-10, 15-agent adversarial review → all verified findings fixed)

  • llmPlatform.service.ts REST layer was dead: it called the strictly-positional callAPI(method, route, params, body) object-style, so llmChat / ingestCorpusManual / pullModel / all 13 REST paths silently hit the wrong URL. Fixed via a rest() shim that awaits the [dataPromise, abort] tuple. (The backend endpoints are real: NestJS public-api @Controller('api/admin/llm') + @Controller('api/llm') — the moleculer gateway has no llm routes; that “llm” grep matches “Installment”.)
  • AI-guessed HN could previously reach confidence 1 → auto-file; now capped at 0.85 inside rankCandidates (provenance checked against identity.signals).
  • Patient-chart accepts showed “Filed” while committing nothing → assign now returns committed:false → “Confirmed” chip + note.
  • ingestCorpusManual was passed the Supabase view UUID → now mongo_id ‖ id.
  • Folder-drop: one unreadable entry no longer rejects the whole drop (skipped + counted); the flat-file fallback is snapshotted before the first await (drag data store goes protected after it).
  • AUTO-mode auto-accept passed a stale closure row (no textPreview) → fully merged row passed to decide.
  • LLM JSON validated (numeric hn, junk docType); Thai name regex used match[0] (honorific included) → [1]; HN compare case-insensitive both sides; AN now searched + counted in hasIdentity; in-flight workers read client via ref (demo→live swap mid-batch); one batch at a time (no 2× worker pool).
  • Known-open (accepted for P0): document-intake use-case code isn’t seeded in llm_use_cases yet (AI pass fail-softs to regex until the P1 seed); decision labels queue in localStorage until the audit endpoint lands.

6. P1 roadmap

  • ingest-documents edge fn: PDF/DOCX/PPTX text + OCR → de-identify → chunk → embed (per twin-data-advantage.md medallion rules); wires both Document Activation and the Intake Inbox binary gap.
  • Patient-chart commit: accepted patient rows → filestore upload (reqIpfsUploadFile) + document.service link + AcknowledgementRequest to the owning department.
  • document_intake order_type row in auto_assign_configs + decisions logged to llm_audit_log / assignment_recommendations (naming already mirrored).
  • Confluence/SharePoint connector packs from catalog plannedavailable.
  • Per-tenant vertical resolution from tenants.subscription_tier-style config instead of env/localStorage.
Ask Anything