90bbb1860d
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 9s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 6s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Snapshot Admin Deployment / build-and-deploy (push) Failing after 2m30s
Deploy to Production / Build & Deploy to Production (push) Failing after 3m49s
279 lines
12 KiB
C#
279 lines
12 KiB
C#
using System.Data;
|
|
using Dapper;
|
|
|
|
namespace QuantEngine.Infrastructure.Data
|
|
{
|
|
public class DbMigrator
|
|
{
|
|
private readonly IDbConnectionFactory _connectionFactory;
|
|
|
|
public DbMigrator(IDbConnectionFactory connectionFactory)
|
|
{
|
|
_connectionFactory = connectionFactory;
|
|
}
|
|
|
|
public void Migrate()
|
|
{
|
|
using var conn = _connectionFactory.CreateConnection();
|
|
conn.Open();
|
|
|
|
// Create schema if not exists
|
|
conn.Execute("CREATE SCHEMA IF NOT EXISTS quantengine;");
|
|
|
|
// 0. kis_tokens
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS kis_tokens (
|
|
account TEXT PRIMARY KEY,
|
|
access_token TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
");
|
|
|
|
// 0b. workspace_account
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS workspace_account (
|
|
ordinal INT NOT NULL,
|
|
username TEXT PRIMARY KEY,
|
|
password_hash TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'Admin',
|
|
is_active TEXT NOT NULL DEFAULT 'true',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_workspace_account_active ON workspace_account(is_active, username);
|
|
");
|
|
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS workspace_session (
|
|
session_token_hash TEXT PRIMARY KEY,
|
|
username TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'Admin',
|
|
created_at TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
revoked_at TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_workspace_session_username ON workspace_session(username, expires_at DESC);
|
|
");
|
|
|
|
// 1. collection_runs
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS collection_runs (
|
|
run_id TEXT PRIMARY KEY,
|
|
collector_name TEXT NOT NULL,
|
|
started_at TEXT NOT NULL,
|
|
finished_at TEXT,
|
|
status TEXT NOT NULL,
|
|
input_source TEXT,
|
|
output_json_path TEXT,
|
|
output_db_path TEXT,
|
|
notes TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
");
|
|
|
|
// 2. collection_snapshots
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS collection_snapshots (
|
|
run_id TEXT NOT NULL,
|
|
dataset_name TEXT NOT NULL,
|
|
ticker TEXT NOT NULL,
|
|
name TEXT,
|
|
sector TEXT,
|
|
as_of_date TEXT,
|
|
source_priority TEXT,
|
|
source_status TEXT,
|
|
payload_json TEXT NOT NULL,
|
|
provenance_json TEXT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (run_id, dataset_name, ticker)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_collection_snapshots_ticker_time ON collection_snapshots(ticker, created_at DESC);
|
|
");
|
|
|
|
// 3. collection_source_errors
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS collection_source_errors (
|
|
run_id TEXT NOT NULL,
|
|
ticker TEXT,
|
|
source_name TEXT NOT NULL,
|
|
error_kind TEXT NOT NULL,
|
|
error_message TEXT NOT NULL,
|
|
payload_json TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_collection_source_errors_run ON collection_source_errors(run_id, source_name);
|
|
");
|
|
|
|
// 4. settings
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
ordinal INT NOT NULL,
|
|
key TEXT PRIMARY KEY,
|
|
value_json TEXT NOT NULL,
|
|
note TEXT NOT NULL DEFAULT '',
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
");
|
|
|
|
// 5. account_snapshot
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS account_snapshot (
|
|
ordinal INT NOT NULL,
|
|
row_json TEXT NOT NULL,
|
|
captured_at TEXT NOT NULL DEFAULT '',
|
|
account TEXT NOT NULL DEFAULT '',
|
|
account_type TEXT NOT NULL DEFAULT '',
|
|
ticker TEXT NOT NULL DEFAULT '',
|
|
name TEXT NOT NULL DEFAULT '',
|
|
parse_status TEXT NOT NULL DEFAULT '',
|
|
user_confirmed TEXT NOT NULL DEFAULT '',
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_account_snapshot_captured_at ON account_snapshot(captured_at);
|
|
CREATE INDEX IF NOT EXISTS idx_account_snapshot_ticker ON account_snapshot(ticker);
|
|
");
|
|
|
|
// 6. workspace_meta
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS workspace_meta (
|
|
key TEXT PRIMARY KEY,
|
|
value_json TEXT NOT NULL
|
|
);
|
|
");
|
|
|
|
// 7. workspace_change_log
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS workspace_change_log (
|
|
id SERIAL PRIMARY KEY,
|
|
domain TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
target_ref TEXT NOT NULL DEFAULT '',
|
|
actor TEXT NOT NULL DEFAULT 'system',
|
|
note TEXT NOT NULL DEFAULT '',
|
|
before_json TEXT NOT NULL DEFAULT 'null',
|
|
after_json TEXT NOT NULL DEFAULT 'null',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
");
|
|
|
|
// 8. workspace_approval_v2
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS workspace_approval_v2 (
|
|
domain TEXT NOT NULL,
|
|
target_ref TEXT NOT NULL DEFAULT '*',
|
|
status TEXT NOT NULL,
|
|
approved_by TEXT NOT NULL DEFAULT '',
|
|
approved_at TEXT NOT NULL DEFAULT '',
|
|
note TEXT NOT NULL DEFAULT '',
|
|
updated_at TEXT NOT NULL,
|
|
PRIMARY KEY (domain, target_ref)
|
|
);
|
|
");
|
|
|
|
// 9. workspace_lock
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS workspace_lock (
|
|
domain TEXT NOT NULL,
|
|
target_ref TEXT NOT NULL DEFAULT '',
|
|
locked_by TEXT NOT NULL DEFAULT '',
|
|
reason TEXT NOT NULL DEFAULT '',
|
|
locked_at TEXT NOT NULL,
|
|
PRIMARY KEY (domain, target_ref)
|
|
);
|
|
");
|
|
|
|
conn.Execute(@"
|
|
INSERT INTO quantengine.workspace_account (
|
|
ordinal, username, password_hash, role, is_active, created_at, updated_at
|
|
)
|
|
SELECT 1, 'admin', '8C6976E5B5410415BDE908BD4DEE15DFB167A9C873FC4BB8A81F6F2AB448A918', 'Admin', 'true', NOW()::text, NOW()::text
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM quantengine.workspace_account WHERE username = 'admin'
|
|
);
|
|
");
|
|
|
|
// 10. engine_history schema and tables
|
|
conn.Execute(@"
|
|
CREATE SCHEMA IF NOT EXISTS engine_history;
|
|
");
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS engine_history.market_raw_history (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
source_id TEXT NOT NULL,
|
|
observed_at TEXT NOT NULL,
|
|
source_name TEXT NOT NULL,
|
|
instrument_id TEXT NOT NULL,
|
|
field_name TEXT NOT NULL,
|
|
field_value TEXT NOT NULL,
|
|
unit TEXT NOT NULL,
|
|
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_market_raw_history_created_at ON engine_history.market_raw_history (created_at DESC);
|
|
");
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS engine_history.factor_version_history (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
factor_id TEXT NOT NULL,
|
|
factor_version TEXT NOT NULL,
|
|
effective_from TEXT NOT NULL,
|
|
effective_to TEXT NOT NULL,
|
|
formula_id TEXT NOT NULL,
|
|
source_version TEXT NOT NULL,
|
|
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_factor_version_history_created_at ON engine_history.factor_version_history (created_at DESC);
|
|
");
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS engine_history.factor_output_history (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
factor_output_id TEXT NOT NULL,
|
|
observed_at TEXT NOT NULL,
|
|
factor_id TEXT NOT NULL,
|
|
factor_version TEXT NOT NULL,
|
|
output_value TEXT NOT NULL,
|
|
output_gate TEXT NOT NULL,
|
|
source_version TEXT NOT NULL,
|
|
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_factor_output_history_created_at ON engine_history.factor_output_history (created_at DESC);
|
|
");
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS engine_history.decision_result_history (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
decision_id TEXT NOT NULL,
|
|
decided_at TEXT NOT NULL,
|
|
instrument_id TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
gate TEXT NOT NULL,
|
|
score TEXT NOT NULL,
|
|
source_version TEXT NOT NULL,
|
|
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_decision_result_history_created_at ON engine_history.decision_result_history (created_at DESC);
|
|
");
|
|
conn.Execute(@"
|
|
CREATE TABLE IF NOT EXISTS engine_history.market_vs_engine_gap_history (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
gap_id TEXT NOT NULL,
|
|
observed_at TEXT NOT NULL,
|
|
instrument_id TEXT NOT NULL,
|
|
metric_name TEXT NOT NULL,
|
|
market_value TEXT NOT NULL,
|
|
engine_value TEXT NOT NULL,
|
|
gap_value TEXT NOT NULL,
|
|
gap_pct TEXT NOT NULL,
|
|
source_version TEXT NOT NULL,
|
|
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_market_vs_engine_gap_history_created_at ON engine_history.market_vs_engine_gap_history (created_at DESC);
|
|
");
|
|
}
|
|
}
|
|
}
|