Files
memind/health-page-data-schema.mjs
T
john 2baf29b3ae feat(health): complete P0 health channel — baseline engine, page-data, MindSpace UI
Deliver encrypted health zone, observation API, baseline maturity pipeline,
page-data bindings, and H5/WeChat channel integration for health P0.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 18:04:57 +08:00

315 lines
9.6 KiB
JavaScript

/** Page Data schema + dataset registry for MeMind Health (architecture §8). */
export const HEALTH_OBSERVATIONS_DATASET = Object.freeze({
name: 'health_observations',
table: 'health_observations',
description: 'MeMind Health 个人健康观测记录',
actions: ['read', 'insert', 'update', 'softDelete'],
columns: {
read: [
'id',
'observed_at',
'metric_type',
'value_num',
'value_text',
'unit',
'context',
'source',
'quality_flag',
'created_at',
],
insert: [
'observed_at',
'metric_type',
'value_num',
'value_text',
'unit',
'context',
'source',
'source_ref',
'time_source',
'confidence',
'quality_flag',
'raw_payload',
],
update: ['observed_at', 'value_num', 'value_text', 'context', 'quality_flag'],
},
});
export const HEALTH_DOCUMENTS_DATASET = Object.freeze({
name: 'health_documents',
table: 'health_documents',
description: 'MeMind Health 医疗报告归档',
actions: ['read', 'insert', 'update', 'softDelete'],
columns: {
read: ['id', 'doc_type', 'report_date', 'institution', 'title', 'asset_id', 'extraction_status', 'created_at'],
insert: ['doc_type', 'report_date', 'institution', 'title', 'asset_id', 'ocr_text', 'extracted_metrics', 'extraction_status'],
update: ['report_date', 'institution', 'title', 'ocr_text', 'extracted_metrics', 'extraction_status'],
},
});
export const HEALTH_OBSERVATION_DRAFTS_DATASET = Object.freeze({
name: 'health_observation_drafts',
table: 'health_observation_drafts',
description: 'MeMind Health 待确认观测草稿',
actions: ['read', 'insert', 'update'],
columns: {
read: ['id', 'draft_key', 'metric_set', 'extracted', 'validation', 'status', 'channel', 'created_at', 'expires_at', 'committed_at'],
insert: ['draft_key', 'metric_set', 'extracted', 'validation', 'status', 'channel', 'expires_at'],
update: ['extracted', 'validation', 'status', 'committed_at'],
},
});
export const HEALTH_BASELINES_DATASET = Object.freeze({
name: 'health_baselines',
table: 'health_baselines',
description: 'MeMind Health 个人基线(引擎写入)',
actions: ['read', 'insert', 'update'],
columns: {
read: [
'id',
'metric_type',
'context',
'window_days',
'computed_at',
'sample_count',
'mean_val',
'std_val',
'p25_val',
'p75_val',
'typical_low',
'typical_high',
'maturity',
],
insert: [
'metric_type',
'context',
'window_days',
'computed_at',
'sample_count',
'mean_val',
'std_val',
'p25_val',
'p75_val',
'typical_low',
'typical_high',
'maturity',
],
update: [
'computed_at',
'sample_count',
'mean_val',
'std_val',
'p25_val',
'p75_val',
'typical_low',
'typical_high',
'maturity',
],
},
});
export const HEALTH_EVENTS_DATASET = Object.freeze({
name: 'health_events',
table: 'health_events',
description: 'MeMind Health 偏离与阈值事件',
actions: ['read', 'insert', 'update'],
columns: {
read: [
'id',
'event_type',
'severity',
'detected_at',
'rule_id',
'metrics_involved',
'evidence_observation_ids',
'agent_summary',
'status',
'created_at',
],
insert: [
'event_type',
'severity',
'detected_at',
'rule_id',
'metrics_involved',
'evidence_observation_ids',
'agent_summary',
'status',
],
update: ['severity', 'agent_summary', 'status'],
},
});
export const HEALTH_PAGE_DATA_DATASETS = Object.freeze([
HEALTH_OBSERVATIONS_DATASET,
HEALTH_DOCUMENTS_DATASET,
HEALTH_OBSERVATION_DRAFTS_DATASET,
HEALTH_BASELINES_DATASET,
HEALTH_EVENTS_DATASET,
]);
export const HEALTH_PAGE_DATA_DDL = Object.freeze([
`CREATE TABLE IF NOT EXISTS health_observations (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
observed_at TIMESTAMPTZ NOT NULL,
metric_type TEXT NOT NULL,
value_num NUMERIC(10,2) NULL,
value_text TEXT NULL,
unit TEXT NULL,
context TEXT NULL,
source TEXT NOT NULL,
source_ref TEXT NULL,
time_source TEXT NOT NULL DEFAULT 'user_specified',
confidence NUMERIC(4,3) NULL,
quality_flag TEXT NULL,
raw_payload JSONB NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ NULL
)`,
`CREATE INDEX IF NOT EXISTS idx_health_obs_metric_time
ON health_observations (metric_type, observed_at DESC)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uq_health_obs_dedup
ON health_observations (metric_type, observed_at, source_ref)
WHERE source_ref IS NOT NULL AND deleted_at IS NULL`,
`CREATE TABLE IF NOT EXISTS health_documents (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
doc_type TEXT NOT NULL DEFAULT 'other',
report_date DATE NULL,
institution TEXT NULL,
title TEXT NOT NULL,
asset_id TEXT NULL,
ocr_text TEXT NULL,
extracted_metrics JSONB NOT NULL DEFAULT '[]'::jsonb,
extraction_status TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ NULL
)`,
`CREATE TABLE IF NOT EXISTS health_observation_drafts (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
draft_key TEXT NOT NULL UNIQUE,
metric_set TEXT NOT NULL,
extracted JSONB NOT NULL,
validation JSONB NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
channel TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMPTZ NOT NULL,
committed_at TIMESTAMPTZ NULL
)`,
`CREATE TABLE IF NOT EXISTS health_baselines (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
metric_type TEXT NOT NULL,
context TEXT NOT NULL,
window_days INT NOT NULL,
computed_at TIMESTAMPTZ NOT NULL,
sample_count INT NOT NULL,
mean_val NUMERIC(10,2) NULL,
std_val NUMERIC(10,2) NULL,
p25_val NUMERIC(10,2) NULL,
p75_val NUMERIC(10,2) NULL,
typical_low NUMERIC(10,2) NULL,
typical_high NUMERIC(10,2) NULL,
maturity TEXT NOT NULL,
UNIQUE (metric_type, context, window_days)
)`,
`CREATE TABLE IF NOT EXISTS health_events (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
event_type TEXT NOT NULL,
severity TEXT NOT NULL,
detected_at TIMESTAMPTZ NOT NULL,
rule_id TEXT NULL,
metrics_involved JSONB NOT NULL DEFAULT '[]'::jsonb,
evidence_observation_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
agent_summary TEXT NULL,
status TEXT NOT NULL DEFAULT 'open',
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE INDEX IF NOT EXISTS idx_health_events_open
ON health_events (status, detected_at DESC)`,
]);
/** SQLite test backend (NODE_TEST_CONTEXT) — same columns, portable types. */
export const HEALTH_PAGE_DATA_DDL_SQLITE = Object.freeze([
`CREATE TABLE IF NOT EXISTS health_observations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
observed_at TEXT NOT NULL,
metric_type TEXT NOT NULL,
value_num REAL NULL,
value_text TEXT NULL,
unit TEXT NULL,
context TEXT NULL,
source TEXT NOT NULL,
source_ref TEXT NULL,
time_source TEXT NOT NULL DEFAULT 'user_specified',
confidence REAL NULL,
quality_flag TEXT NULL,
raw_payload TEXT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
deleted_at TEXT NULL
)`,
`CREATE INDEX IF NOT EXISTS idx_health_obs_metric_time
ON health_observations (metric_type, observed_at)`,
`CREATE TABLE IF NOT EXISTS health_documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
doc_type TEXT NOT NULL DEFAULT 'other',
report_date TEXT NULL,
institution TEXT NULL,
title TEXT NOT NULL,
asset_id TEXT NULL,
ocr_text TEXT NULL,
extracted_metrics TEXT NOT NULL DEFAULT '[]',
extraction_status TEXT NOT NULL DEFAULT 'pending',
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
deleted_at TEXT NULL
)`,
`CREATE TABLE IF NOT EXISTS health_observation_drafts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
draft_key TEXT NOT NULL UNIQUE,
metric_set TEXT NOT NULL,
extracted TEXT NOT NULL,
validation TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
channel TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
expires_at TEXT NOT NULL,
committed_at TEXT NULL
)`,
`CREATE TABLE IF NOT EXISTS health_baselines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
metric_type TEXT NOT NULL,
context TEXT NOT NULL,
window_days INTEGER NOT NULL,
computed_at TEXT NOT NULL,
sample_count INTEGER NOT NULL,
mean_val REAL NULL,
std_val REAL NULL,
p25_val REAL NULL,
p75_val REAL NULL,
typical_low REAL NULL,
typical_high REAL NULL,
maturity TEXT NOT NULL,
UNIQUE (metric_type, context, window_days)
)`,
`CREATE TABLE IF NOT EXISTS health_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type TEXT NOT NULL,
severity TEXT NOT NULL,
detected_at TEXT NOT NULL,
rule_id TEXT NULL,
metrics_involved TEXT NOT NULL DEFAULT '[]',
evidence_observation_ids TEXT NOT NULL DEFAULT '[]',
agent_summary TEXT NULL,
status TEXT NOT NULL DEFAULT 'open',
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
)`,
`CREATE INDEX IF NOT EXISTS idx_health_events_open
ON health_events (status, detected_at)`,
]);
export function healthPageDataDdlForBackend(backend = 'postgres') {
return String(backend).toLowerCase() === 'sqlite'
? HEALTH_PAGE_DATA_DDL_SQLITE
: HEALTH_PAGE_DATA_DDL;
}