chore: add processed data, outputs, and proposal
- Add processed/ ML features and GCN model inputs - Add Outputs/ GIS analysis, deploy configs, lit review - Add proposal document for 湖北省卫生健康科技项目 - Enable full data portability for cross-machine development
This commit is contained in:
15
Outputs/deploy-backend/.env.example
Normal file
15
Outputs/deploy-backend/.env.example
Normal file
@@ -0,0 +1,15 @@
|
||||
# Database
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=wuhan_user
|
||||
POSTGRES_PASSWORD=CHANGE_ME
|
||||
POSTGRES_DB=wuhan_disease
|
||||
|
||||
# Auth
|
||||
AUTH_SECRET_KEY=CHANGE_ME_TO_RANDOM_STRING
|
||||
AUTH_DEFAULT_USER=admin
|
||||
AUTH_DEFAULT_PASSWORD=admin123
|
||||
AUTH_TOKEN_EXPIRE_MINUTES=480
|
||||
|
||||
# CORS (add your frontend domain)
|
||||
CORS_ORIGINS=http://localhost,http://YOUR_SERVER_IP
|
||||
14
Outputs/deploy-backend/cbpoa-backend.service
Normal file
14
Outputs/deploy-backend/cbpoa-backend.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=CBPOA Backend API
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
WorkingDirectory=/opt/cbpoa/backend
|
||||
ExecStart=/opt/cbpoa/backend/venv/bin/python3 -m uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
30
Outputs/deploy-backend/install.sh
Executable file
30
Outputs/deploy-backend/install.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== CBPOA Backend Deployment ==="
|
||||
|
||||
INSTALL_DIR=/opt/cbpoa/backend
|
||||
|
||||
# Copy backend files
|
||||
sudo mkdir -p $INSTALL_DIR
|
||||
sudo cp -r . $INSTALL_DIR/
|
||||
|
||||
# Fix venv shebangs (hardcoded to build machine path)
|
||||
echo "Fixing venv shebangs..."
|
||||
VENV_PYTHON="$INSTALL_DIR/venv/bin/python3"
|
||||
find "$INSTALL_DIR/venv/bin" -type f -exec grep -l '#!/home/akiba' {} \; 2>/dev/null | while read f; do
|
||||
sudo sed -i "s|#!/home/akiba/CA/backend/venv/bin/python3|#!$VENV_PYTHON|g" "$f"
|
||||
done
|
||||
|
||||
# Fix permissions
|
||||
sudo chown -R www-data:www-data $INSTALL_DIR
|
||||
|
||||
# Install systemd service
|
||||
sudo cp cbpoa-backend.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable cbpoa-backend
|
||||
|
||||
echo ""
|
||||
echo "=== Done! Start with: ==="
|
||||
echo " sudo systemctl start cbpoa-backend"
|
||||
echo " curl http://localhost:8000/docs"
|
||||
277
Outputs/deploy-backend/scripts/deploy_schema.sql
Normal file
277
Outputs/deploy-backend/scripts/deploy_schema.sql
Normal file
@@ -0,0 +1,277 @@
|
||||
-- Wuhan Children's Respiratory Disease Risk Prediction - PostGIS Schema
|
||||
-- Database: wuhan_risk
|
||||
-- Created: 2026-04-25
|
||||
|
||||
-- Enable PostGIS extension
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
CREATE EXTENSION IF NOT EXISTS postgis_topology;
|
||||
|
||||
-- Drop existing tables if they exist (for re-deployment)
|
||||
DROP TABLE IF EXISTS alerts CASCADE;
|
||||
DROP TABLE IF EXISTS risk_predictions CASCADE;
|
||||
DROP TABLE IF EXISTS medical_daily CASCADE;
|
||||
DROP TABLE IF EXISTS weather_daily CASCADE;
|
||||
DROP TABLE IF EXISTS road_edges CASCADE;
|
||||
DROP TABLE IF EXISTS road_nodes CASCADE;
|
||||
DROP TABLE IF EXISTS wuhan_districts CASCADE;
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: wuhan_districts
|
||||
-- Description: Wuhan administrative district boundaries
|
||||
-- Source: Datas/武汉市.geojson
|
||||
-- ============================================================================
|
||||
CREATE TABLE wuhan_districts (
|
||||
district_code VARCHAR(6) PRIMARY KEY,
|
||||
district_name VARCHAR(100) NOT NULL,
|
||||
adcode VARCHAR(6) NOT NULL,
|
||||
geom GEOMETRY(MultiPolygon, 4326) NOT NULL,
|
||||
area_km2 NUMERIC(10, 2),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Spatial index on district boundaries
|
||||
CREATE INDEX idx_wuhan_districts_geom ON wuhan_districts USING GIST (geom);
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: road_nodes
|
||||
-- Description: Road network nodes (intersections + segment midpoints)
|
||||
-- Source: OSM Hubei extract, filtered to Wuhan boundary
|
||||
-- ============================================================================
|
||||
CREATE TABLE road_nodes (
|
||||
osmid BIGINT PRIMARY KEY,
|
||||
node_type VARCHAR(20) NOT NULL CHECK (node_type IN ('intersection', 'midpoint')),
|
||||
lat NUMERIC(10, 8) NOT NULL,
|
||||
lon NUMERIC(11, 8) NOT NULL,
|
||||
elevation_m NUMERIC(8, 2),
|
||||
pop_density NUMERIC(10, 2),
|
||||
district_code VARCHAR(6),
|
||||
highway_tag VARCHAR(50),
|
||||
node_degree INTEGER DEFAULT 0,
|
||||
geom GEOMETRY(Point, 4326) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Spatial index on road nodes
|
||||
CREATE INDEX idx_road_nodes_geom ON road_nodes USING GIST (geom);
|
||||
CREATE INDEX idx_road_nodes_district ON road_nodes (district_code);
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: road_edges
|
||||
-- Description: Road network edges (road segments between nodes)
|
||||
-- Source: OSM Hubei extract
|
||||
-- ============================================================================
|
||||
CREATE TABLE road_edges (
|
||||
edge_id BIGINT PRIMARY KEY,
|
||||
source_osmid BIGINT NOT NULL REFERENCES road_nodes(osmid),
|
||||
target_osmid BIGINT NOT NULL REFERENCES road_nodes(osmid),
|
||||
road_type VARCHAR(50) NOT NULL,
|
||||
road_type_abbrev VARCHAR(10),
|
||||
length_m NUMERIC(10, 2) NOT NULL,
|
||||
speed_limit_kmh INTEGER,
|
||||
weight NUMERIC(10, 6) NOT NULL,
|
||||
geometry GEOMETRY(LineString, 4326) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Spatial index on road edges
|
||||
CREATE INDEX idx_road_edges_geometry ON road_edges USING GIST (geometry);
|
||||
CREATE INDEX idx_road_edges_source ON road_edges (source_osmid);
|
||||
CREATE INDEX idx_road_edges_target ON road_edges (target_osmid);
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: weather_daily
|
||||
-- Description: Daily aggregated weather and air quality data per station
|
||||
-- Source: Datas/气象 + 空气/站点_YYYYMMDD-YYYYMMDD/*.csv
|
||||
-- ============================================================================
|
||||
CREATE TABLE weather_daily (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
station_id VARCHAR(10) NOT NULL,
|
||||
district_code VARCHAR(6),
|
||||
lat NUMERIC(10, 8),
|
||||
lon NUMERIC(11, 8),
|
||||
aqi NUMERIC(6, 2),
|
||||
pm25 NUMERIC(8, 2),
|
||||
pm10 NUMERIC(8, 2),
|
||||
so2 NUMERIC(8, 2),
|
||||
no2 NUMERIC(8, 2),
|
||||
o3 NUMERIC(8, 2),
|
||||
co NUMERIC(8, 2),
|
||||
nox NUMERIC(8, 2),
|
||||
so2_24h NUMERIC(8, 2),
|
||||
no2_24h NUMERIC(8, 2),
|
||||
o3_8h NUMERIC(8, 2),
|
||||
co_24h NUMERIC(8, 2),
|
||||
pm10_24h NUMERIC(8, 2),
|
||||
pm25_24h NUMERIC(8, 2),
|
||||
primary_pollutant VARCHAR(50),
|
||||
air_quality_level VARCHAR(20),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(date, station_id)
|
||||
);
|
||||
|
||||
-- Indexes for efficient querying
|
||||
CREATE INDEX idx_weather_daily_date ON weather_daily (date);
|
||||
CREATE INDEX idx_weather_daily_station ON weather_daily (station_id);
|
||||
CREATE INDEX idx_weather_daily_district ON weather_daily (district_code);
|
||||
CREATE INDEX idx_weather_daily_date_station ON weather_daily (date, station_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: medical_daily
|
||||
-- Description: Daily aggregated medical visits per district
|
||||
-- Source: Datas/view_门诊.xlsx, Datas/view_住院.xlsx
|
||||
-- ============================================================================
|
||||
CREATE TABLE medical_daily (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
district_code VARCHAR(6) NOT NULL,
|
||||
outpatient_count INTEGER NOT NULL DEFAULT 0,
|
||||
inpatient_count INTEGER NOT NULL DEFAULT 0,
|
||||
respiratory_outpatient INTEGER NOT NULL DEFAULT 0,
|
||||
respiratory_inpatient INTEGER NOT NULL DEFAULT 0,
|
||||
total_visits INTEGER GENERATED ALWAYS AS (outpatient_count + inpatient_count) STORED,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(date, district_code)
|
||||
);
|
||||
|
||||
-- Indexes for efficient querying
|
||||
CREATE INDEX idx_medical_daily_date ON medical_daily (date);
|
||||
CREATE INDEX idx_medical_daily_district ON medical_daily (district_code);
|
||||
CREATE INDEX idx_medical_daily_date_district ON medical_daily (date, district_code);
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: risk_predictions
|
||||
-- Description: Model predictions for disease risk per road node
|
||||
-- Source: Model inference output
|
||||
-- ============================================================================
|
||||
CREATE TABLE risk_predictions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
osmid BIGINT NOT NULL REFERENCES road_nodes(osmid),
|
||||
district_code VARCHAR(6) NOT NULL,
|
||||
risk_1d NUMERIC(5, 4) NOT NULL CHECK (risk_1d >= 0 AND risk_1d <= 1),
|
||||
risk_3d NUMERIC(5, 4) NOT NULL CHECK (risk_3d >= 0 AND risk_3d <= 1),
|
||||
risk_7d NUMERIC(5, 4) NOT NULL CHECK (risk_7d >= 0 AND risk_7d <= 1),
|
||||
risk_level VARCHAR(10) NOT NULL CHECK (risk_level IN ('green', 'yellow', 'orange', 'red')),
|
||||
lat NUMERIC(10, 8) NOT NULL,
|
||||
lon NUMERIC(11, 8) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(date, osmid)
|
||||
);
|
||||
|
||||
-- Indexes for efficient querying
|
||||
CREATE INDEX idx_risk_predictions_date ON risk_predictions (date);
|
||||
CREATE INDEX idx_risk_predictions_osmid ON risk_predictions (osmid);
|
||||
CREATE INDEX idx_risk_predictions_district ON risk_predictions (district_code);
|
||||
CREATE INDEX idx_risk_predictions_level ON risk_predictions (risk_level);
|
||||
CREATE INDEX idx_risk_predictions_date_district ON risk_predictions (date, district_code);
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: alerts
|
||||
-- Description: Generated alerts based on risk predictions and medical data
|
||||
-- Source: Alert engine
|
||||
-- ============================================================================
|
||||
CREATE TABLE alerts (
|
||||
alert_id BIGSERIAL PRIMARY KEY,
|
||||
alert_type VARCHAR(20) NOT NULL CHECK (alert_type IN ('monitoring', 'warning')),
|
||||
alert_level VARCHAR(10) NOT NULL CHECK (alert_level IN ('yellow', 'orange', 'red')),
|
||||
date DATE NOT NULL,
|
||||
district_code VARCHAR(6) NOT NULL,
|
||||
osmid BIGINT REFERENCES road_nodes(osmid),
|
||||
trigger_source VARCHAR(50) NOT NULL,
|
||||
trigger_value NUMERIC(10, 4),
|
||||
threshold NUMERIC(10, 4),
|
||||
description TEXT,
|
||||
acknowledged BOOLEAN DEFAULT FALSE,
|
||||
acknowledged_at TIMESTAMP,
|
||||
acknowledged_by VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Indexes for efficient querying
|
||||
CREATE INDEX idx_alerts_date ON alerts (date);
|
||||
CREATE INDEX idx_alerts_district ON alerts (district_code);
|
||||
CREATE INDEX idx_alerts_level ON alerts (alert_level);
|
||||
CREATE INDEX idx_alerts_type ON alerts (alert_type);
|
||||
CREATE INDEX idx_alerts_acknowledged ON alerts (acknowledged);
|
||||
CREATE INDEX idx_alerts_date_district ON alerts (date, district_code);
|
||||
|
||||
-- ============================================================================
|
||||
-- Comments for documentation
|
||||
-- ============================================================================
|
||||
COMMENT ON TABLE wuhan_districts IS 'Wuhan administrative district boundaries from GeoJSON';
|
||||
COMMENT ON TABLE road_nodes IS 'Road network nodes (intersections and segment midpoints) from OSM';
|
||||
COMMENT ON TABLE road_edges IS 'Road network edges with weights for graph traversal';
|
||||
COMMENT ON TABLE weather_daily IS 'Daily aggregated weather and air quality data per monitoring station';
|
||||
COMMENT ON TABLE medical_daily IS 'Daily aggregated outpatient and inpatient counts per district';
|
||||
COMMENT ON TABLE risk_predictions IS 'GCN+Transformer model predictions for 1/3/7 day disease risk';
|
||||
COMMENT ON TABLE alerts IS 'Generated alerts from monitoring (medical) and warning (risk prediction) systems';
|
||||
|
||||
COMMENT ON COLUMN road_nodes.node_type IS 'intersection: OSM node where roads meet; midpoint: center point of road segment';
|
||||
COMMENT ON COLUMN road_edges.weight IS 'Edge weight: 1/length_km for road segments, 60/speed_limit for highways';
|
||||
COMMENT ON COLUMN weather_daily.station_id IS 'Monitoring station ID (e.g., 1001A, 1002A)';
|
||||
COMMENT ON COLUMN risk_predictions.risk_level IS 'Risk level: green (<0.3), yellow (0.3-0.5), orange (0.5-0.7), red (>0.7)';
|
||||
COMMENT ON COLUMN alerts.alert_type IS 'monitoring: triggered by medical data z-scores; warning: triggered by risk predictions';
|
||||
COMMENT ON COLUMN alerts.trigger_source IS 'Source of alert trigger (e.g., outpatient_z, inpatient_z, risk_3d, risk_7d)';
|
||||
|
||||
-- ============================================================================
|
||||
-- Load Wuhan districts from GeoJSON (requires ogr2ogr or manual import)
|
||||
-- Alternative: Use COPY command with pre-processed CSV
|
||||
-- ============================================================================
|
||||
-- Example: Import districts (run after processing GeoJSON to CSV)
|
||||
-- COPY wuhan_districts (district_code, district_name, adcode, geom)
|
||||
-- FROM '/path/to/wuhan_districts.csv' WITH (FORMAT csv, HEADER true);
|
||||
|
||||
-- ============================================================================
|
||||
-- Helper Views
|
||||
-- ============================================================================
|
||||
|
||||
-- View: Latest risk predictions per node
|
||||
CREATE OR REPLACE VIEW v_latest_risk AS
|
||||
SELECT rp.*
|
||||
FROM risk_predictions rp
|
||||
INNER JOIN (
|
||||
SELECT osmid, MAX(date) as max_date
|
||||
FROM risk_predictions
|
||||
GROUP BY osmid
|
||||
) latest ON rp.osmid = latest.osmid AND rp.date = latest.max_date;
|
||||
|
||||
-- View: Active alerts (unacknowledged)
|
||||
CREATE OR REPLACE VIEW v_active_alerts AS
|
||||
SELECT *
|
||||
FROM alerts
|
||||
WHERE acknowledged = FALSE
|
||||
ORDER BY
|
||||
CASE alert_level
|
||||
WHEN 'red' THEN 1
|
||||
WHEN 'orange' THEN 2
|
||||
WHEN 'yellow' THEN 3
|
||||
END,
|
||||
date DESC;
|
||||
|
||||
-- View: District-level risk summary
|
||||
CREATE OR REPLACE VIEW v_district_risk_summary AS
|
||||
SELECT
|
||||
date,
|
||||
district_code,
|
||||
COUNT(*) as node_count,
|
||||
AVG(risk_1d) as avg_risk_1d,
|
||||
AVG(risk_3d) as avg_risk_3d,
|
||||
AVG(risk_7d) as avg_risk_7d,
|
||||
SUM(CASE WHEN risk_level = 'green' THEN 1 ELSE 0 END) as green_count,
|
||||
SUM(CASE WHEN risk_level = 'yellow' THEN 1 ELSE 0 END) as yellow_count,
|
||||
SUM(CASE WHEN risk_level = 'orange' THEN 1 ELSE 0 END) as orange_count,
|
||||
SUM(CASE WHEN risk_level = 'red' THEN 1 ELSE 0 END) as red_count
|
||||
FROM risk_predictions
|
||||
GROUP BY date, district_code;
|
||||
|
||||
-- ============================================================================
|
||||
-- Grant permissions (adjust as needed)
|
||||
-- ============================================================================
|
||||
-- GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;
|
||||
-- GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO app_user;
|
||||
-- GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO app_user;
|
||||
|
||||
-- ============================================================================
|
||||
-- Schema deployment complete
|
||||
-- ============================================================================
|
||||
10
Outputs/deploy-backend/start.sh
Executable file
10
Outputs/deploy-backend/start.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
source venv/bin/activate
|
||||
|
||||
echo "=== CBPOA Backend ==="
|
||||
echo "API docs: http://localhost:8000/docs"
|
||||
|
||||
exec python3 -m uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2
|
||||
Reference in New Issue
Block a user