Data Activation & Integration Center
HDAP as the one center for data-in + AI-analysis: connector catalog, vertical presets, spreadsheet ingest, smart intake inbox.
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 P1ingest-documentsedge 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 (numerichn→ string, docType whitelisted). Fail-soft: model down ⇒ regex-only. - Matching: injected
searchPatients— live page wiressearchPatientByText(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) andauto(pre-accepts only deterministic exact-HN matches — confidence 1 is unreachable for AI-guessed HNs by construction inrankCandidates). 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 returncommitted:falsefromassignso 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 thellm_audit_logendpoint 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
- 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 (
rankCandidatescaps it below the auto threshold). Accept/edit/reject is logged as the training label — never written to the chart (matchescowork_proposals/ clinical-AI label-in-workflow). - 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).
- Fail-soft everywhere: no backend ⇒ demo roster / demo answer / preview-only, always labelled.
- Honest statuses: nothing claims “filed to chart” until the P1 commit path exists; connector catalog keeps live/available/planned truthful.
- Verticals are data rows: a new archetype (rehab center, dialysis chain…) = one
VERTICAL_PRESETSentry, zero new pages. - 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.tsREST layer was dead: it called the strictly-positionalcallAPI(method, route, params, body)object-style, sollmChat/ingestCorpusManual/pullModel/ all 13 REST paths silently hit the wrong URL. Fixed via arest()shim that awaits the[dataPromise, abort]tuple. (The backend endpoints are real: NestJSpublic-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 againstidentity.signals). - Patient-chart accepts showed “Filed” while committing nothing →
assignnow returnscommitted:false→ “Confirmed” chip + note. ingestCorpusManualwas passed the Supabase view UUID → nowmongo_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 todecide. - LLM JSON validated (numeric
hn, junk docType); Thai name regex usedmatch[0](honorific included) →[1]; HN compare case-insensitive both sides; AN now searched + counted inhasIdentity; in-flight workers readclientvia ref (demo→live swap mid-batch); one batch at a time (no 2× worker pool). - Known-open (accepted for P0):
document-intakeuse-case code isn’t seeded inllm_use_casesyet (AI pass fail-softs to regex until the P1 seed); decision labels queue in localStorage until the audit endpoint lands.
6. P1 roadmap
ingest-documentsedge fn: PDF/DOCX/PPTX text + OCR → de-identify → chunk → embed (pertwin-data-advantage.mdmedallion rules); wires both Document Activation and the Intake Inbox binary gap.- Patient-chart commit: accepted patient rows → filestore upload (
reqIpfsUploadFile) +document.servicelink +AcknowledgementRequestto the owning department. document_intakeorder_type row inauto_assign_configs+ decisions logged tollm_audit_log/assignment_recommendations(naming already mirrored).- Confluence/SharePoint connector packs from catalog
planned→available. - Per-tenant vertical resolution from
tenants.subscription_tier-style config instead of env/localStorage.