feat: 补全 SQLite 表结构 + 查询函数 + 迁移覆盖
mofin_db.py 新增: - 4 张表: portfolio_summary, advice_timeline, accuracy_stats, strategy_feedback - 18 个查询函数: query_holdings, query_watchlist, query_strategies, query_advice_timeline, query_candidates, query_candidate_scores, query_price_events, query_price_events_by_date, query_stock_sectors, query_sector_stocks, query_accuracy_stats, query_strategy_feedback, query_strategy_evaluations, query_latest_market, query_holding_by_code, query_portfolio_summary migrate_all.py 新增: - 4 个迁移函数: migrate_portfolio_summary, migrate_advice_timeline, migrate_accuracy_stats, migrate_strategy_feedback - 迁移量: portfolio_summary(1), advice_timeline(2547), accuracy_stats(1), strategy_feedback(37) 现在 13 张表全部覆盖,JSON→SQLite 数据完整迁移
This commit is contained in:
+124
@@ -387,6 +387,98 @@ def migrate_sectors(conn) -> int:
|
||||
return count
|
||||
|
||||
|
||||
def migrate_portfolio_summary(conn, pf: dict) -> int:
|
||||
"""portfolio.json 顶层字段 → portfolio_summary"""
|
||||
try:
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO portfolio_summary (id, total_assets, stock_value, cash, position_pct, total_pnl, updated_at) "
|
||||
"VALUES (1, ?, ?, ?, ?, ?, ?)",
|
||||
(pf.get("total_assets"), pf.get("stock_value"), pf.get("cash"),
|
||||
pf.get("position_pct"), pf.get("total_pnl"), pf.get("updated_at", datetime.now().isoformat())))
|
||||
conn.commit()
|
||||
return 1
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
def migrate_advice_timeline(conn, dec: dict) -> int:
|
||||
"""decisions.json advice_timeline[] → advice_timeline"""
|
||||
count = 0
|
||||
for d in dec.get("decisions", []):
|
||||
code = _normalize_code(d.get("code", ""))
|
||||
if not code:
|
||||
continue
|
||||
timeline = d.get("advice_timeline", [])
|
||||
for t in timeline:
|
||||
try:
|
||||
conn.execute(
|
||||
"INSERT INTO advice_timeline (code, date, direction, price, summary, status, evaluated, result, evaluated_at, report_id) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(code, t.get("date"), t.get("direction"), t.get("price"),
|
||||
t.get("summary"), t.get("status"),
|
||||
1 if t.get("evaluated") else 0, t.get("result"),
|
||||
t.get("evaluated_at"), t.get("report_id")))
|
||||
count += 1
|
||||
except Exception:
|
||||
pass
|
||||
conn.commit()
|
||||
return count
|
||||
|
||||
|
||||
def migrate_accuracy_stats(conn, acc: dict) -> int:
|
||||
"""accuracy_stats.json → accuracy_stats"""
|
||||
try:
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO accuracy_stats (id, period_start, period_end, total_advice, correct, wrong, partial, unknown, pending, ignored, evaluated, accuracy_pct, "
|
||||
"phase1_correct, phase1_wrong, phase1_pending, phase1_accuracy, "
|
||||
"phase2_correct, phase2_wrong, phase2_pending, phase2_accuracy, total_evaluated, updated_at) "
|
||||
"VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(acc.get("period_start"), acc.get("period_end"),
|
||||
acc.get("total_advice"), acc.get("correct"), acc.get("wrong"),
|
||||
acc.get("partial"), acc.get("unknown"), acc.get("pending"),
|
||||
acc.get("ignored"), acc.get("evaluated"), acc.get("accuracy_pct"),
|
||||
acc.get("phase1", {}).get("correct", 0) if isinstance(acc.get("phase1"), dict) else 0,
|
||||
acc.get("phase1", {}).get("wrong", 0) if isinstance(acc.get("phase1"), dict) else 0,
|
||||
acc.get("phase1", {}).get("pending", 0) if isinstance(acc.get("phase1"), dict) else 0,
|
||||
acc.get("phase1", {}).get("accuracy_pct", 0) if isinstance(acc.get("phase1"), dict) else 0,
|
||||
acc.get("phase2", {}).get("correct", 0) if isinstance(acc.get("phase2"), dict) else 0,
|
||||
acc.get("phase2", {}).get("wrong", 0) if isinstance(acc.get("phase2"), dict) else 0,
|
||||
acc.get("phase2", {}).get("pending", 0) if isinstance(acc.get("phase2"), dict) else 0,
|
||||
acc.get("phase2", {}).get("accuracy_pct", 0) if isinstance(acc.get("phase2"), dict) else 0,
|
||||
acc.get("total_evaluated"), acc.get("updated_at", datetime.now().isoformat())))
|
||||
conn.commit()
|
||||
return 1
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
def migrate_strategy_feedback(conn, sf: dict) -> int:
|
||||
"""strategy_feedback.json → strategy_feedback"""
|
||||
count = 0
|
||||
for f in sf.get("feedback", []):
|
||||
code = _normalize_code(f.get("code", ""))
|
||||
if not code:
|
||||
continue
|
||||
pc = f.get("phase_check", {})
|
||||
try:
|
||||
conn.execute(
|
||||
"INSERT INTO strategy_feedback (code, name, evaluated_at, "
|
||||
"phase1_completed, phase1_result, phase1_completed_at, phase1_price, "
|
||||
"phase2_completed, phase2_result, phase2_completed_at, days_in_phase1, adjustments_json) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(code, f.get("name"), f.get("evaluated_at"),
|
||||
1 if pc.get("phase1_completed") else 0, pc.get("phase1_result"),
|
||||
pc.get("phase1_completed_at"), pc.get("phase1_price_at_completion"),
|
||||
1 if pc.get("phase2_completed") else 0, pc.get("phase2_result"),
|
||||
pc.get("phase2_completed_at"), pc.get("days_in_phase1"),
|
||||
json.dumps(f.get("adjustments", []), ensure_ascii=False)))
|
||||
count += 1
|
||||
except Exception:
|
||||
pass
|
||||
conn.commit()
|
||||
return count
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print(" MoFin 数据迁移 → mofin.db")
|
||||
@@ -454,6 +546,34 @@ def main():
|
||||
totals["sector_mappings"] = n
|
||||
print(f"[8/8] stock_sectors: {n} 条映射")
|
||||
|
||||
# 9. portfolio_summary
|
||||
pf = load_json("portfolio.json")
|
||||
if pf:
|
||||
n = migrate_portfolio_summary(conn, pf)
|
||||
totals["portfolio_summary"] = n
|
||||
print(f"[9/12] portfolio_summary: {n} 条")
|
||||
|
||||
# 10. advice_timeline
|
||||
dec = load_json("decisions.json")
|
||||
if dec:
|
||||
n = migrate_advice_timeline(conn, dec)
|
||||
totals["advice_timeline"] = n
|
||||
print(f"[10/12] advice_timeline: {n} 条")
|
||||
|
||||
# 11. accuracy_stats
|
||||
acc = load_json("accuracy_stats.json")
|
||||
if acc:
|
||||
n = migrate_accuracy_stats(conn, acc)
|
||||
totals["accuracy_stats"] = n
|
||||
print(f"[11/12] accuracy_stats: {n} 条")
|
||||
|
||||
# 12. strategy_feedback
|
||||
sf = load_json("strategy_feedback.json")
|
||||
if sf:
|
||||
n = migrate_strategy_feedback(conn, sf)
|
||||
totals["strategy_feedback"] = n
|
||||
print(f"[12/12] strategy_feedback: {n} 条")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# ── 验证 ──
|
||||
@@ -470,6 +590,10 @@ def main():
|
||||
"price_events": "SELECT COUNT(*) FROM price_events",
|
||||
"strategy_evaluations": "SELECT COUNT(*) FROM strategy_evaluations",
|
||||
"stock_sectors": "SELECT COUNT(*) FROM stock_sectors",
|
||||
"portfolio_summary": "SELECT COUNT(*) FROM portfolio_summary",
|
||||
"advice_timeline": "SELECT COUNT(*) FROM advice_timeline",
|
||||
"accuracy_stats": "SELECT COUNT(*) FROM accuracy_stats",
|
||||
"strategy_feedback": "SELECT COUNT(*) FROM strategy_feedback",
|
||||
}
|
||||
for name, sql in tables.items():
|
||||
n = conn.execute(sql).fetchone()[0]
|
||||
|
||||
Reference in New Issue
Block a user