2baf29b3ae
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>
315 lines
9.6 KiB
JavaScript
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;
|
|
}
|