8f13bb4a48
- PostgreSQL history contract와 schema/validator를 추가했습니다. - .NET history store, snapshot reader, repository, migration을 연결했습니다. - history-first 운영 모델 문서와 daily signal tracking 문구를 정리했습니다.
243 lines
10 KiB
C#
243 lines
10 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
|
|
);
|
|
");
|
|
|
|
// 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)
|
|
);
|
|
");
|
|
|
|
// 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);
|
|
");
|
|
}
|
|
}
|
|
}
|