CREATE TABLE IF NOT EXISTS app.report_templates ( template_id BIGSERIAL PRIMARY KEY, template_key TEXT NOT NULL UNIQUE, name TEXT NOT NULL, report_type TEXT NOT NULL CHECK (report_type IN ('monthly', 'area_special', 'neighborhood_memo')), cadence TEXT NOT NULL CHECK (cadence IN ('weekly', 'monthly', 'manual')), description TEXT NOT NULL DEFAULT '', body_template TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS app.report_generation_runs ( generation_run_id BIGSERIAL PRIMARY KEY, template_id BIGINT NOT NULL REFERENCES app.report_templates(template_id), target_month TEXT NOT NULL CHECK (target_month ~ '^[0-9]{4}-[0-9]{2}$'), status TEXT NOT NULL CHECK (status IN ('running', 'succeeded', 'failed')), report_id BIGINT REFERENCES app.reports(report_id), parameters JSONB NOT NULL DEFAULT '{}'::jsonb, error_message TEXT, started_at TIMESTAMPTZ NOT NULL DEFAULT now(), finished_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_report_generation_runs_month ON app.report_generation_runs(target_month, generation_run_id DESC); CREATE INDEX IF NOT EXISTS idx_report_generation_runs_template ON app.report_generation_runs(template_id, generation_run_id DESC); INSERT INTO app.report_templates ( template_key, name, report_type, cadence, description, body_template ) VALUES ( 'monthly_market', '市场月报', 'monthly', 'monthly', '生成市场总览、板块评分、关键风险和下月跟踪事项。', '# 上海房市投资研究月报 {{month}}\n\n{{market_summary}}\n\n{{area_table}}\n\n{{risk_section}}\n\n{{next_actions}}' ), ( 'weekly_watchlist', '观察池周报', 'monthly', 'weekly', '生成观察池状态、价格触发和待复核事项摘要。', '# 上海房市观察池周报 {{month}}\n\n{{watchlist_summary}}\n\n{{watchlist_table}}\n\n{{next_actions}}' ) ON CONFLICT (template_key) DO UPDATE SET name = EXCLUDED.name, report_type = EXCLUDED.report_type, cadence = EXCLUDED.cadence, description = EXCLUDED.description, body_template = EXCLUDED.body_template, updated_at = now();