-- 010: 어구 연관성 추적 시스템 -- - correlation_param_models: 파라미터 모델 마스터 -- - gear_correlation_raw_metrics: raw 메트릭 (타임스탬프 파티셔닝, 7일 보존) -- - gear_correlation_scores: 모델별 어피니티 점수 (상태 테이블) -- - system_config: 런타임 설정 (파티션 보관기간 등) SET search_path TO kcg, public; -- ── 파라미터 모델 ── CREATE TABLE IF NOT EXISTS kcg.correlation_param_models ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, is_default BOOLEAN DEFAULT FALSE, is_active BOOLEAN DEFAULT TRUE, params JSONB NOT NULL, description TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- default 모델 삽입 INSERT INTO kcg.correlation_param_models (name, is_default, is_active, params, description) VALUES ('default', TRUE, TRUE, '{"alpha_base":0.30,"alpha_min":0.08,"alpha_decay_per_streak":0.005,"track_threshold":0.50,"polygon_threshold":0.70,"w_proximity":0.45,"w_visit":0.35,"w_activity":0.20,"w_dtw":0.30,"w_sog_corr":0.20,"w_heading":0.25,"w_prox_vv":0.25,"w_prox_persist":0.50,"w_drift":0.30,"w_signal_sync":0.20,"group_quiet_ratio":0.30,"normal_gap_hours":1.0,"decay_slow":0.015,"decay_fast":0.08,"stale_hours":6.0,"shadow_stay_bonus":0.10,"shadow_return_bonus":0.15,"candidate_radius_factor":3.0,"proximity_threshold_nm":5.0,"visit_threshold_nm":5.0,"night_bonus":1.3,"long_decay_days":7.0}', '기본 추적 모델') ON CONFLICT (name) DO NOTHING; -- ── Raw 메트릭 (모델 독립, 5분마다 기록, 타임스탬프 파티셔닝) ── CREATE TABLE IF NOT EXISTS kcg.gear_correlation_raw_metrics ( id BIGSERIAL, observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), group_key VARCHAR(100) NOT NULL, target_mmsi VARCHAR(20) NOT NULL, target_type VARCHAR(10) NOT NULL, target_name VARCHAR(200), -- Raw 메트릭 (모든 모델이 공유) proximity_ratio DOUBLE PRECISION, visit_score DOUBLE PRECISION, activity_sync DOUBLE PRECISION, dtw_similarity DOUBLE PRECISION, speed_correlation DOUBLE PRECISION, heading_coherence DOUBLE PRECISION, drift_similarity DOUBLE PRECISION, -- Shadow shadow_stay BOOLEAN DEFAULT FALSE, shadow_return BOOLEAN DEFAULT FALSE, -- 상태 gear_group_active_ratio DOUBLE PRECISION, PRIMARY KEY (id, observed_at) ) PARTITION BY RANGE (observed_at); -- 일별 파티션 생성 함수 CREATE OR REPLACE FUNCTION kcg.create_raw_metric_partitions(days_ahead INT DEFAULT 3) RETURNS void AS $$ DECLARE d DATE; partition_name TEXT; BEGIN FOR i IN 0..days_ahead LOOP d := CURRENT_DATE + i; partition_name := 'gear_correlation_raw_metrics_' || TO_CHAR(d, 'YYYYMMDD'); IF NOT EXISTS ( SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = partition_name AND n.nspname = 'kcg' ) THEN EXECUTE format( 'CREATE TABLE IF NOT EXISTS kcg.%I PARTITION OF kcg.gear_correlation_raw_metrics FOR VALUES FROM (%L) TO (%L)', partition_name, d, d + 1 ); END IF; END LOOP; END; $$ LANGUAGE plpgsql; -- 초기 파티션 생성 (오늘 + 3일) SELECT kcg.create_raw_metric_partitions(3); -- raw_metrics 인덱스 CREATE INDEX IF NOT EXISTS idx_raw_metrics_group_time ON kcg.gear_correlation_raw_metrics (group_key, observed_at DESC); CREATE INDEX IF NOT EXISTS idx_raw_metrics_target ON kcg.gear_correlation_raw_metrics (target_mmsi, observed_at DESC); -- ── 어피니티 점수 (모델별 독립, 상태 테이블) ── CREATE TABLE IF NOT EXISTS kcg.gear_correlation_scores ( id BIGSERIAL PRIMARY KEY, model_id INT NOT NULL REFERENCES kcg.correlation_param_models(id) ON DELETE CASCADE, group_key VARCHAR(100) NOT NULL, target_mmsi VARCHAR(20) NOT NULL, target_type VARCHAR(10) NOT NULL, target_name VARCHAR(200), -- 모델별 점수 (EMA 결과) current_score DOUBLE PRECISION DEFAULT 0, streak_count INT DEFAULT 0, observation_count INT DEFAULT 0, -- Shadow 축적 shadow_bonus_total DOUBLE PRECISION DEFAULT 0, shadow_stay_count INT DEFAULT 0, shadow_return_count INT DEFAULT 0, -- 상태 freeze_state VARCHAR(20) DEFAULT 'ACTIVE', -- 시간 first_observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (model_id, group_key, target_mmsi) ); CREATE INDEX IF NOT EXISTS idx_gc_model_group ON kcg.gear_correlation_scores (model_id, group_key, current_score DESC); CREATE INDEX IF NOT EXISTS idx_gc_active ON kcg.gear_correlation_scores (current_score DESC) WHERE current_score >= 0.5; -- ── 시스템 설정 (런타임 변경 가능, 재시작 불필요) ── CREATE TABLE IF NOT EXISTS kcg.system_config ( key VARCHAR(100) PRIMARY KEY, value JSONB NOT NULL, description TEXT, updated_at TIMESTAMPTZ DEFAULT NOW(), updated_by VARCHAR(100) DEFAULT 'system' ); INSERT INTO kcg.system_config (key, value, description) VALUES ('partition.raw_metrics.retention_days', '7', 'raw_metrics 파티션 보관 기간 (일). 초과 시 파티션 DROP.'), ('partition.raw_metrics.create_ahead_days', '3', '미래 파티션 미리 생성 일수.'), ('partition.scores.cleanup_days', '30', '미관측 점수 레코드 정리 기간 (일).'), ('correlation.max_active_models', '5', '동시 활성 모델 최대 수.') ON CONFLICT (key) DO NOTHING;