Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View File

@@ -0,0 +1,328 @@
-- =====================================================
-- 运动传感设备时序数据表设计PostgreSQL/TimescaleDB
-- 表名前缀均为 ss_
-- 适用于高吞吐量、高并发的生命体征与位置数据存储
-- =====================================================
-- 1. 扩展与准备
-- -------------------------------------
-- 需要 uuid-ossp 与 timescaledb若使用时序库
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS timescaledb; -- 启用 TimescaleDB 扩展
-- Enable PostGIS extension for spatial data
CREATE EXTENSION IF NOT EXISTS postgis;
-- 2. 核心设备关联表
-- -------------------------------------
-- 设备管理表引用 ak_devices 表
-- 传感器测量数据均关联到已有设备管理
-- 3. 基础时序数据表:通用测量记录
-- -------------------------------------
CREATE TABLE IF NOT EXISTS public.ss_sensor_measurements (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
device_id UUID NOT NULL REFERENCES public.ak_devices(id) ON DELETE CASCADE,
user_id UUID REFERENCES public.ak_users(id) ON DELETE SET NULL,
measurement_type TEXT NOT NULL, -- 如 steps, heart_rate, spo2, stride, bp, temp, location, ecg
measured_at TIMESTAMPTZ NOT NULL,
unit TEXT, -- 单位,如 count, bpm, %, cm, mmHg, ℃, geojson
raw_data JSONB, -- 原始数据或扩展字段
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 高并发场景下用 TimescaleDB 创建 hypertable
-- SELECT create_hypertable('public.ss_sensor_measurements', 'measured_at', chunk_time_interval => interval '1 day');
CREATE INDEX ON public.ss_sensor_measurements(device_id, measured_at DESC);
CREATE INDEX ON public.ss_sensor_measurements(measurement_type, measured_at DESC);
-- 4. 分专题数据表:性能优化(可选)
-- -------------------------------------
-- 对于最常见的生命体征,可划分专项表,以提高查询效率
-- 步数
CREATE TABLE IF NOT EXISTS public.ss_steps (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
step_count BIGINT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_steps(step_count);
-- 心率
CREATE TABLE IF NOT EXISTS public.ss_heart_rate (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
heart_rate_bpm INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_heart_rate(heart_rate_bpm);
-- 血氧 (SpO2)
CREATE TABLE IF NOT EXISTS public.ss_spo2 (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
spo2_percent INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_spo2(spo2_percent);
-- 血压
CREATE TABLE IF NOT EXISTS public.ss_blood_pressure (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
systolic INT NOT NULL,
diastolic INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_blood_pressure(systolic, diastolic);
-- 体温
CREATE TABLE IF NOT EXISTS public.ss_temperature (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
temperature_c FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_temperature(temperature_c);
-- 步幅
CREATE TABLE IF NOT EXISTS public.ss_stride_length (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
stride_cm FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_stride_length(stride_cm);
-- 位置GeoJSON使用经纬度替代 PostGIS 类型)
CREATE TABLE IF NOT EXISTS public.ss_location (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
latitude DOUBLE PRECISION NOT NULL, -- 纬度
longitude DOUBLE PRECISION NOT NULL, -- 经度
altitude FLOAT,
speed FLOAT,
heading FLOAT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_location(latitude, longitude);
-- 心电图 (ECG) 波形数据
-- 分两表:测量元信息 + 样本点
CREATE TABLE IF NOT EXISTS public.ss_ecg_measurements (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
duration_sec FLOAT NOT NULL,
sample_rate_hz FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.ss_ecg_samples (
ecg_id UUID NOT NULL REFERENCES public.ss_ecg_measurements(id) ON DELETE CASCADE,
sample_index INT NOT NULL,
voltage_mv FLOAT NOT NULL,
PRIMARY KEY(ecg_id, sample_index)
);
CREATE INDEX ON public.ss_ecg_samples(sample_index);
-- 呼吸率
CREATE TABLE IF NOT EXISTS public.ss_respiratory_rate (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
respiratory_rate INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_respiratory_rate(respiratory_rate);
-- 潮气量
CREATE TABLE IF NOT EXISTS public.ss_tidal_volume (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
tidal_volume_ml FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_tidal_volume(tidal_volume_ml);
-- 呼吸音
CREATE TABLE IF NOT EXISTS public.ss_respiratory_sounds (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
audio_data BYTEA NOT NULL, -- 原始音频
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 心率变异性
CREATE TABLE IF NOT EXISTS public.ss_hrv (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
hrv_rmssd FLOAT,
hrv_sdnn FLOAT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_hrv(hrv_rmssd);
CREATE INDEX ON public.ss_hrv(hrv_sdnn);
-- 血糖
CREATE TABLE IF NOT EXISTS public.ss_glucose (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
glucose_mg_dl FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_glucose(glucose_mg_dl);
-- 皮肤电反应 (GSR)
CREATE TABLE IF NOT EXISTS public.ss_gsr (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
gsr_us FLOAT NOT NULL, -- 微西门子
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_gsr(gsr_us);
-- 皮肤温度
CREATE TABLE IF NOT EXISTS public.ss_skin_temperature (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
skin_temp_c FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_skin_temperature(skin_temp_c);
-- 卡路里消耗
CREATE TABLE IF NOT EXISTS public.ss_energy_expenditure (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
calories_kcal FLOAT NOT NULL,
met FLOAT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_energy_expenditure(calories_kcal);
-- VO2 / VO2max
CREATE TABLE IF NOT EXISTS public.ss_vo2 (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
vo2_ml_per_min FLOAT,
vo2max_ml_per_min FLOAT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_vo2(vo2_ml_per_min);
CREATE INDEX ON public.ss_vo2(vo2max_ml_per_min);
-- 睡眠指标
CREATE TABLE IF NOT EXISTS public.ss_sleep_metrics (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
sleep_stage VARCHAR(32) NOT NULL, -- awake, light, deep, rem
duration_sec INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_sleep_metrics(sleep_stage);
-- 跌倒事件
CREATE TABLE IF NOT EXISTS public.ss_fall_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
fall_detected BOOLEAN NOT NULL,
event_time TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_fall_events(fall_detected);
-- 姿态数据
CREATE TABLE IF NOT EXISTS public.ss_posture (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
posture VARCHAR(32) NOT NULL, -- standing, sitting, lying, walking
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_posture(posture);
-- 肌电 (EMG)
CREATE TABLE IF NOT EXISTS public.ss_emg_measurements (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
duration_sec FLOAT NOT NULL,
sample_rate_hz FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.ss_emg_samples (
emg_id UUID NOT NULL REFERENCES public.ss_emg_measurements(id) ON DELETE CASCADE,
sample_index INT NOT NULL,
voltage_mv FLOAT NOT NULL,
PRIMARY KEY(emg_id, sample_index)
);
CREATE INDEX ON public.ss_emg_samples(sample_index);
-- 脑电 (EEG)
CREATE TABLE IF NOT EXISTS public.ss_eeg_measurements (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
duration_sec FLOAT NOT NULL,
sample_rate_hz FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.ss_eeg_samples (
eeg_id UUID NOT NULL REFERENCES public.ss_eeg_measurements(id) ON DELETE CASCADE,
sample_index INT NOT NULL,
voltage_uv FLOAT NOT NULL,
PRIMARY KEY(eeg_id, sample_index)
);
CREATE INDEX ON public.ss_eeg_samples(sample_index);
-- 血乳酸
CREATE TABLE IF NOT EXISTS public.ss_lactate (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
lactate_mmol_l FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON public.ss_lactate(lactate_mmol_l);
-- PPG 波形
CREATE TABLE IF NOT EXISTS public.ss_ppg_measurements (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE,
duration_sec FLOAT NOT NULL,
sample_rate_hz FLOAT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.ss_ppg_samples (
ppg_id UUID NOT NULL REFERENCES public.ss_ppg_measurements(id) ON DELETE CASCADE,
sample_index INT NOT NULL,
amplitude FLOAT NOT NULL,
PRIMARY KEY(ppg_id, sample_index)
);
CREATE INDEX ON public.ss_ppg_samples(sample_index);
-- 5. 数据保留与归档(示例)
-- -------------------------------------
-- 定期归档或删除旧数据,可使用 TimescaleDB retention policy
-- SELECT add_retention_policy('public.ss_sensor_measurements', INTERVAL '90 days');
-- 6. 视图和物化视图示例
-- -------------------------------------
-- 最近 1 小时心率聚合
CREATE MATERIALIZED VIEW public.ss_hr_last_hour_summary AS
SELECT device_id,
minute_bucket,
avg(heart_rate_bpm) AS avg_bpm,
min(heart_rate_bpm) AS min_bpm,
max(heart_rate_bpm) AS max_bpm
FROM (
SELECT date_trunc('minute', sm.measured_at) AS minute_bucket,
hr.heart_rate_bpm,
sm.device_id
FROM public.ss_sensor_measurements sm
JOIN public.ss_heart_rate hr ON hr.measurement_id = sm.id
WHERE sm.measured_at > now() - INTERVAL '1 hour'
) sub
GROUP BY device_id, minute_bucket;
-- =====================================================
-- 使用说明:
-- • 通用写入:将任意指标写入 ss_sensor_measurements随后写入专项表
-- • 查询优化:针对常用指标查询专项表
-- • 时序特性:开启 TimescaleDB hypertable 以提高写入与查询性能
-- =====================================================