# -*- coding: utf-8 -*- """ 种子数据执行入口:初始化 SQLite 表结构 → 灌入模拟数据 → 构建 Chroma 向量索引。 幂等设计:重复执行会自动清空重灌,方便数据更新后一键重建。 用法:python3 seed.py [--rebuild-vector] (--rebuild-vector 强制重建向量集合) """ import argparse import logging import sys import time logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") log = logging.getLogger("seed") from db import init_db, execute, executemany, query_one, table_count # noqa: E402 from seed_teams import SPORTS, LEAGUES, TEAMS, STANDINGS # noqa: E402 from seed_players_a import PLAYERS_A # noqa: E402 from seed_players_b import PLAYERS_B # noqa: E402 from seed_games import GAMES, BOX # noqa: E402 from seed_news import NEWS, WIKI # noqa: E402 from seed_persons import PERSONS # noqa: E402 import vector_store # noqa: E402 def wipe(): for t in ("game_player_stats", "games", "standings", "players", "teams", "leagues", "sports", "news", "persons"): execute(f"DELETE FROM {t}") execute(f"DELETE FROM sqlite_sequence WHERE name='{t}'") log.info("已清空旧数据") def seed_core(): """sports / leagues / teams / players / standings / games / persons""" for code, name, name_en in SPORTS: execute("INSERT INTO sports(code,name,name_en) VALUES(?,?,?)", (code, name, name_en)) sport_id = query_one("SELECT id FROM sports WHERE code='basketball'")["id"] for code, name, name_en, country, season in LEAGUES: execute("INSERT INTO leagues(sport_id,code,name,name_en,country,season) VALUES(?,?,?,?,?,?)", (sport_id, code, name, name_en, country, season)) league_id = query_one("SELECT id FROM leagues WHERE code='NBA'")["id"] # 球队 for code, name, en, city, arena, founded, champs, coach, intro in TEAMS: execute("""INSERT INTO teams(league_id,code,name,name_en,city,arena,founded,champion_count,head_coach,intro) VALUES(?,?,?,?,?,?,?,?,?,?)""", (league_id, code, name, en, city, arena, founded, champs, coach, intro)) team_id = {r["code"]: r["id"] for r in __import__("db").query("SELECT id,code FROM teams")} # 球员 p_rows = [] for p in PLAYERS_A + PLAYERS_B: (t, name, en, pos, num, h, w, country, dy, dp, sal, pts, reb, ast, stl, blk, mn, cpts, creb, cast, cg, awards, bio) = p p_rows.append((team_id[t], name, en, pos, num, h, w, country, dy, dp, sal, pts, reb, ast, stl, blk, mn, cpts, creb, cast, cg, awards, bio)) executemany("""INSERT INTO players(team_id,name,name_en,position,number,height_cm,weight_kg,country, draft_year,draft_pick,salary_m,season_pts,season_reb,season_ast,season_stl,season_blk,season_min, career_pts,career_reb,career_ast,career_games,awards,bio) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", p_rows) log.info("球员 %d 名", len(p_rows)) # 排名 s_rows = [(league_id, "2025-26", team_id[c], w, l, round(w / (w + l), 4), conf, rk) for c, conf, rk, w, l in STANDINGS] executemany("""INSERT INTO standings(league_id,season,team_id,wins,losses,win_pct,conference,rank) VALUES(?,?,?,?,?,?,?,?)""", s_rows) # 比赛 game_id = {} g_rows = [] for rnd, gt, status, home, away, hs, as_, venue, bcast in GAMES: g_rows.append((league_id, "2025-26", rnd, gt, status, team_id[home], team_id[away], hs, as_, venue, bcast)) executemany("""INSERT INTO games(league_id,season,round_name,game_time,status,home_team_id,away_team_id, home_score,away_score,venue,broadcast) VALUES(?,?,?,?,?,?,?,?,?,?,?)""", g_rows) for i, (rnd, gt, *_rest) in enumerate(GAMES): game_id[(rnd, gt)] = query_one( "SELECT id FROM games WHERE round_name=? AND game_time=?", (rnd, gt))["id"] # 球员技术统计(box score) box_rows = [] for (rnd, gt), lines in BOX.items(): gid = game_id.get((rnd, gt)) if not gid: continue for pname, pts, reb, ast, stl, blk, mn in lines: player = query_one("SELECT id,team_id FROM players WHERE name=?", (pname,)) if player: box_rows.append((gid, player["id"], player["team_id"], pts, reb, ast, stl, blk, mn)) executemany("""INSERT INTO game_player_stats(game_id,player_id,team_id,points,rebounds,assists,steals,blocks,minutes) VALUES(?,?,?,?,?,?,?,?,?)""", box_rows) log.info("比赛 %d 场,技术统计 %d 条", len(GAMES), len(box_rows)) # 人物 per_rows = [] for t, name, en, role, role_cn, title, bio, ach in PERSONS: per_rows.append((sport_id, league_id, team_id.get(t), name, en, role, role_cn, title, bio, ach)) executemany("""INSERT INTO persons(sport_id,league_id,team_id,name,name_en,role,role_cn,title,bio,achievements) VALUES(?,?,?,?,?,?,?,?,?,?)""", per_rows) log.info("人物 %d 名", len(per_rows)) # 新闻 / 百科 n_rows = [] for t, title, content, author, source, pt, tags, kind in NEWS + WIKI: n_rows.append((sport_id, league_id, team_id.get(t), title, content, author, source, pt, tags, kind)) executemany("""INSERT INTO news(sport_id,league_id,team_id,title,content,author,source,publish_time,tags,kind) VALUES(?,?,?,?,?,?,?,?,?,?)""", n_rows) log.info("新闻/百科 %d 篇", len(n_rows)) def seed_vector(rebuild=False): """把新闻/百科分块后写入 Chroma(title + 正文 按 400 字切块,重叠 60 字)""" from db import query as dbq rows = dbq("SELECT id,title,content,kind,team_id,publish_time,tags FROM news ORDER BY id") ids, docs, metas = [], [], [] for r in rows: text = f"{r['title']}\n{r['content']}" chunk_size, overlap = 400, 60 start = 0 while start < len(text): chunk = text[start:start + chunk_size] ids.append(f"news_{r['id']}_{start}") docs.append(chunk) metas.append({"news_id": r["id"], "title": r["title"], "kind": r["kind"], "team_id": r["team_id"] or 0, "publish_time": r["publish_time"], "tags": r["tags"] or ""}) start += chunk_size - overlap log.info("向量文档 %d 块(%d 篇)", len(ids), len(rows)) if rebuild: vector_store.reset_collection() # 若已有数据则跳过(增量) if vector_store.collection_count() >= len(ids): log.info("向量库已有 %d 条,跳过写入", vector_store.collection_count()) return if vector_store.collection_count() > 0: vector_store.reset_collection() for i in range(0, len(ids), 50): # 分批写入,避免单次请求过大 vector_store.add_documents(ids[i:i + 50], docs[i:i + 50], metas[i:i + 50]) log.info("向量索引完成,共 %d 条", vector_store.collection_count()) def main(): ap = argparse.ArgumentParser() ap.add_argument("--rebuild-vector", action="store_true", help="强制重建向量集合") ap.add_argument("--no-vector", action="store_true", help="跳过向量索引") args = ap.parse_args() t0 = time.time() init_db() wipe() seed_core() if not args.no_vector: seed_vector(rebuild=args.rebuild_vector) else: log.info("跳过向量索引") log.info("数据灌入完成,耗时 %.1fs", time.time() - t0) for t in ("sports", "leagues", "teams", "players", "games", "standings", "game_player_stats", "news", "persons"): log.info(" %-16s %d", t, table_count(t)) if __name__ == "__main__": sys.exit(main())