db.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. # -*- coding: utf-8 -*-
  2. """mode_workflow · MySQL 持久化(DB 为唯一事实源)
  3. ================================================================================
  4. 读 .env 的 MYSQL_* 连接 MySQL。三张表:
  5. search_data —— 每行一个 (query, 帖子):搜索 + llm 评估结果
  6. mode_process —— 每行一个解构出的工序(steps 等嵌套结构存 JSON 列)
  7. mode_tools —— 每行一个解构出的工具
  8. 与旧 fixed_query_eval/db.py 的关键差异:本系统 DB 是主存储,写入失败直接 raise,
  9. 不做"失败不阻断"。读侧保留防御(返回空/None)。
  10. 用法:
  11. python db.py init # 建表(幂等)
  12. python db.py check # 打印三表行数
  13. """
  14. import json
  15. import os
  16. import sys
  17. from pathlib import Path
  18. PROJECT_ROOT = Path(__file__).resolve().parents[2]
  19. sys.path.insert(0, str(PROJECT_ROOT))
  20. from dotenv import load_dotenv
  21. load_dotenv()
  22. import pymysql
  23. from pymysql.cursors import DictCursor
  24. def _conn():
  25. if not os.getenv("MYSQL_HOST"):
  26. raise RuntimeError("缺 MYSQL_HOST:检查 .env 的 MYSQL_* 配置")
  27. return pymysql.connect(
  28. host=os.getenv("MYSQL_HOST"),
  29. port=int(os.getenv("MYSQL_PORT", 3306)),
  30. user=os.getenv("MYSQL_USER"),
  31. password=os.getenv("MYSQL_PASSWORD"),
  32. database=os.getenv("MYSQL_DATABASE"),
  33. charset="utf8mb4", cursorclass=DictCursor,
  34. autocommit=True, connect_timeout=10,
  35. )
  36. # ── DDL ──────────────────────────────────────────────────────────────────────
  37. DDL_SEARCH = """
  38. CREATE TABLE IF NOT EXISTS search_data (
  39. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  40. query_id VARCHAR(32) NOT NULL COMMENT 'q0000',
  41. query_text VARCHAR(512) NULL,
  42. case_id VARCHAR(128) NOT NULL COMMENT 'platform_channelContentId',
  43. platform VARCHAR(32) NULL,
  44. channel_content_id VARCHAR(128) NULL,
  45. title VARCHAR(512) NULL,
  46. url VARCHAR(1024) NULL,
  47. content_type VARCHAR(32) NULL,
  48. body LONGTEXT NULL,
  49. images JSON NULL,
  50. videos JSON NULL,
  51. like_count INT NULL,
  52. publish_time VARCHAR(64) NULL,
  53. quality_score FLOAT NULL COMMENT 'post._quality_score',
  54. quality_grade VARCHAR(8) NULL,
  55. found_by JSON NULL COMMENT '命中的措辞数组',
  56. knowledge_type JSON NULL COMMENT '["能力","工序","工具"] 子集',
  57. mode_type VARCHAR(16) NULL COMMENT '该 query 的解构方向:工序/工具(空=通用)',
  58. overall_score FLOAT NULL COMMENT '(相关均值+质量均值)/2',
  59. llm_evaluation JSON NULL COMMENT '评估全量 blob',
  60. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  61. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  62. UNIQUE KEY uk_qid_case (query_id, case_id),
  63. KEY idx_platform (platform)
  64. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='搜索+评估结果';
  65. """
  66. DDL_PROCESS = """
  67. CREATE TABLE IF NOT EXISTS mode_process (
  68. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  69. query_id VARCHAR(32) NOT NULL,
  70. case_id VARCHAR(128) NOT NULL,
  71. platform VARCHAR(32) NULL,
  72. post_title VARCHAR(512) NULL,
  73. source JSON NULL COMMENT '解构返回的 source 块',
  74. procedure_id VARCHAR(16) NULL COMMENT 'p1,p2…',
  75. name VARCHAR(255) NULL,
  76. purpose TEXT NULL,
  77. category VARCHAR(32) NULL COMMENT '产物创造/资产建设/自动化/分析/学习',
  78. declarations JSON NULL,
  79. type_registry JSON NULL,
  80. steps JSON NULL COMMENT '步骤数组全量',
  81. step_count INT NULL,
  82. tools_used JSON NULL COMMENT '从 steps[].via 去重提取',
  83. model VARCHAR(64) NULL,
  84. version VARCHAR(16) NULL COMMENT 'v_MMDDHHMM,保留历史',
  85. cost_usd DECIMAL(10,6) NULL COMMENT '本次解构调用成本(同版本各行相同,聚合需按 case+version 去重)',
  86. duration_s FLOAT NULL,
  87. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  88. KEY idx_case_ver (case_id, version),
  89. KEY idx_qid (query_id)
  90. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工序解构结果(每行一个工序)';
  91. """
  92. DDL_TOOLS = """
  93. CREATE TABLE IF NOT EXISTS mode_tools (
  94. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  95. query_id VARCHAR(32) NOT NULL,
  96. case_id VARCHAR(128) NOT NULL,
  97. platform VARCHAR(32) NULL,
  98. post_title VARCHAR(512) NULL,
  99. tool_name VARCHAR(255) NULL,
  100. substance_scope JSON NULL COMMENT '实质作用域(数组)',
  101. form_scope JSON NULL COMMENT '形式作用域(数组或null)',
  102. creation_layer VARCHAR(32) NULL COMMENT '制作层/创作层',
  103. source_link VARCHAR(1024) NULL,
  104. input_desc TEXT NULL,
  105. output_desc TEXT NULL,
  106. usage_json JSON NULL,
  107. cases_json JSON NULL,
  108. defects_json JSON NULL,
  109. updated_time VARCHAR(64) NULL COMMENT '工具最新更新时间',
  110. model VARCHAR(64) NULL,
  111. version VARCHAR(16) NULL,
  112. cost_usd DECIMAL(10,6) NULL COMMENT '同 mode_process,聚合按 case+version 去重',
  113. duration_s FLOAT NULL,
  114. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  115. KEY idx_case_ver (case_id, version),
  116. KEY idx_qid (query_id),
  117. KEY idx_tool_name (tool_name)
  118. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工具解构结果(每行一个工具)';
  119. """
  120. def init_tables():
  121. conn = _conn()
  122. try:
  123. with conn.cursor() as cur:
  124. cur.execute(DDL_SEARCH)
  125. cur.execute(DDL_PROCESS)
  126. cur.execute(DDL_TOOLS)
  127. # 迁移:旧表补 mode_type 列(CREATE IF NOT EXISTS 不会改已有表)
  128. cur.execute("SHOW COLUMNS FROM search_data LIKE 'mode_type'")
  129. if not cur.fetchone():
  130. cur.execute("ALTER TABLE search_data ADD COLUMN mode_type VARCHAR(16) NULL "
  131. "COMMENT '该 query 的解构方向:工序/工具(空=通用)' AFTER knowledge_type")
  132. print("🔧 迁移:search_data 已补 mode_type 列")
  133. print("✅ 建表完成:search_data, mode_process, mode_tools")
  134. finally:
  135. conn.close()
  136. # ── 工具函数 ──────────────────────────────────────────────────────────────────
  137. def _loads(v, default=None):
  138. """pymysql 的 JSON 列可能返回字符串,统一解析。"""
  139. if v is None:
  140. return default
  141. if isinstance(v, (list, dict)):
  142. return v
  143. try:
  144. return json.loads(v)
  145. except Exception:
  146. return default
  147. def _j(v):
  148. """写入 JSON 列:None 保持 NULL,其余 dumps。"""
  149. return None if v is None else json.dumps(v, ensure_ascii=False)
  150. def _collect_scores(node):
  151. """递归收集嵌套评估里所有数值「得分」。"""
  152. out = []
  153. if isinstance(node, dict):
  154. for k, v in node.items():
  155. if k == "得分" and isinstance(v, (int, float)):
  156. out.append(float(v))
  157. else:
  158. out.extend(_collect_scores(v))
  159. elif isinstance(node, list):
  160. for v in node:
  161. out.extend(_collect_scores(v))
  162. return out
  163. def overall_score(e):
  164. """综合分 = (相关性各项均值 + 质量各项均值) / 可得部分数。算不出返回 None。"""
  165. parts = []
  166. for key in ("相关性", "质量"):
  167. scores = _collect_scores((e or {}).get(key))
  168. if scores:
  169. parts.append(sum(scores) / len(scores))
  170. return round(sum(parts) / len(parts), 2) if parts else None
  171. # ── search_data ──────────────────────────────────────────────────────────────
  172. def upsert_search_posts(query_id, query_text, results, mode_type=None):
  173. """一组搜索结果写入 search_data(按 (query_id, case_id) upsert)。返回写入条数。
  174. mode_type:该 query 的解构方向(工序/工具),None 不覆盖已有值。"""
  175. if not results:
  176. return 0
  177. rows = []
  178. for r in results:
  179. post = r.get("post") or {}
  180. e = r.get("llm_evaluation") or {}
  181. rows.append((
  182. query_id, query_text, r.get("case_id"), r.get("platform"),
  183. r.get("channel_content_id"),
  184. (post.get("title") or post.get("desc") or "")[:500],
  185. r.get("source_url"), post.get("content_type"),
  186. post.get("body_text") or post.get("desc") or "",
  187. _j(post.get("images") or []), _j(post.get("videos") or []),
  188. post.get("like_count"),
  189. str(post.get("publish_time") or post.get("publish_timestamp") or "")[:64],
  190. post.get("_quality_score"), post.get("_quality_grade"),
  191. _j(r.get("found_by_queries") or []),
  192. _j(e.get("知识类型") or []),
  193. mode_type,
  194. overall_score(e),
  195. _j(e),
  196. ))
  197. sql = """
  198. INSERT INTO search_data
  199. (query_id, query_text, case_id, platform, channel_content_id, title, url,
  200. content_type, body, images, videos, like_count, publish_time,
  201. quality_score, quality_grade, found_by, knowledge_type, mode_type,
  202. overall_score, llm_evaluation)
  203. VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
  204. ON DUPLICATE KEY UPDATE
  205. query_text=VALUES(query_text), platform=VALUES(platform),
  206. channel_content_id=VALUES(channel_content_id), title=VALUES(title), url=VALUES(url),
  207. content_type=VALUES(content_type), body=VALUES(body), images=VALUES(images),
  208. videos=VALUES(videos), like_count=VALUES(like_count), publish_time=VALUES(publish_time),
  209. quality_score=VALUES(quality_score), quality_grade=VALUES(quality_grade),
  210. found_by=VALUES(found_by), knowledge_type=VALUES(knowledge_type),
  211. mode_type=COALESCE(VALUES(mode_type), mode_type),
  212. overall_score=VALUES(overall_score), llm_evaluation=VALUES(llm_evaluation);
  213. """
  214. conn = _conn()
  215. try:
  216. with conn.cursor() as cur:
  217. cur.executemany(sql, rows)
  218. return len(rows)
  219. finally:
  220. conn.close()
  221. def fetch_queries():
  222. """query 列表 + 帖子数 + 解构进度。"""
  223. conn = _conn()
  224. try:
  225. with conn.cursor() as cur:
  226. cur.execute("""SELECT query_id, MAX(query_text) AS query_text,
  227. MAX(mode_type) AS mode_type, COUNT(*) AS post_count
  228. FROM search_data GROUP BY query_id ORDER BY query_id""")
  229. queries = cur.fetchall()
  230. cur.execute("SELECT query_id, COUNT(DISTINCT case_id) AS n FROM mode_process GROUP BY query_id")
  231. np = {r["query_id"]: r["n"] for r in cur.fetchall()}
  232. cur.execute("SELECT query_id, COUNT(DISTINCT case_id) AS n FROM mode_tools GROUP BY query_id")
  233. nt = {r["query_id"]: r["n"] for r in cur.fetchall()}
  234. finally:
  235. conn.close()
  236. for q in queries:
  237. q["process_done"] = np.get(q["query_id"], 0)
  238. q["tools_done"] = nt.get(q["query_id"], 0)
  239. return queries
  240. def fetch_posts(query_id):
  241. """某 query 下全部帖子(JSON 列已解析),带 has_process/has_tools 标记。"""
  242. conn = _conn()
  243. try:
  244. with conn.cursor() as cur:
  245. cur.execute("""SELECT * FROM search_data WHERE query_id=%s
  246. ORDER BY overall_score DESC, id""", (query_id,))
  247. rows = cur.fetchall()
  248. cur.execute("SELECT DISTINCT case_id FROM mode_process WHERE query_id=%s", (query_id,))
  249. hp = {r["case_id"] for r in cur.fetchall()}
  250. cur.execute("SELECT DISTINCT case_id FROM mode_tools WHERE query_id=%s", (query_id,))
  251. ht = {r["case_id"] for r in cur.fetchall()}
  252. finally:
  253. conn.close()
  254. for r in rows:
  255. for col in ("images", "videos", "found_by", "knowledge_type", "llm_evaluation"):
  256. r[col] = _loads(r[col])
  257. r["has_process"] = r["case_id"] in hp
  258. r["has_tools"] = r["case_id"] in ht
  259. r.pop("created_at", None); r.pop("updated_at", None)
  260. return rows
  261. def fetch_post(query_id, case_id):
  262. """单帖完整行(给 pipeline 脚本重建 source 用)。无则 None。"""
  263. conn = _conn()
  264. try:
  265. with conn.cursor() as cur:
  266. cur.execute("SELECT * FROM search_data WHERE query_id=%s AND case_id=%s",
  267. (query_id, case_id))
  268. row = cur.fetchone()
  269. finally:
  270. conn.close()
  271. if not row:
  272. return None
  273. for col in ("images", "videos", "found_by", "knowledge_type", "llm_evaluation"):
  274. row[col] = _loads(row[col])
  275. return row
  276. # ── mode_process ─────────────────────────────────────────────────────────────
  277. def replace_process(query_id, case_id, platform, post_title, payload,
  278. model, version, cost_usd, duration_s):
  279. """写入一帖某版本的工序解构结果(payload = {source, procedures})。
  280. 删 (case_id, version) 旧行再插,同版本重跑幂等、跨版本保留历史。返回工序条数。"""
  281. source = payload.get("source")
  282. procedures = payload.get("procedures") or []
  283. conn = _conn()
  284. try:
  285. with conn.cursor() as cur:
  286. cur.execute("DELETE FROM mode_process WHERE case_id=%s AND version=%s",
  287. (case_id, version))
  288. if procedures:
  289. rows = []
  290. for p in procedures:
  291. steps = p.get("steps") or []
  292. vias = []
  293. for s in steps:
  294. v = s.get("via")
  295. if v and v not in vias:
  296. vias.append(v)
  297. rows.append((
  298. query_id, case_id, platform, (post_title or "")[:500],
  299. _j(source), p.get("id"), (p.get("name") or "")[:250],
  300. p.get("purpose"), p.get("category"),
  301. _j(p.get("declarations")), _j(p.get("type_registry")),
  302. _j(steps), len(steps), _j(vias),
  303. model, version, cost_usd, duration_s,
  304. ))
  305. cur.executemany("""
  306. INSERT INTO mode_process
  307. (query_id, case_id, platform, post_title, source, procedure_id, name,
  308. purpose, category, declarations, type_registry, steps, step_count,
  309. tools_used, model, version, cost_usd, duration_s)
  310. VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
  311. """, rows)
  312. return len(procedures)
  313. finally:
  314. conn.close()
  315. def fetch_process_versions(case_id):
  316. conn = _conn()
  317. try:
  318. with conn.cursor() as cur:
  319. cur.execute("""SELECT version, COUNT(*) AS n, MAX(model) AS model
  320. FROM mode_process WHERE case_id=%s
  321. GROUP BY version ORDER BY version DESC""", (case_id,))
  322. return cur.fetchall()
  323. finally:
  324. conn.close()
  325. def fetch_process(case_id, version=None):
  326. """重建 {case_id, version, model, source, procedures:[...]}。version=None 取最新。"""
  327. conn = _conn()
  328. try:
  329. with conn.cursor() as cur:
  330. if version is None:
  331. cur.execute("""SELECT version FROM mode_process WHERE case_id=%s
  332. ORDER BY version DESC, id DESC LIMIT 1""", (case_id,))
  333. row = cur.fetchone()
  334. if not row:
  335. return None
  336. version = row["version"]
  337. cur.execute("""SELECT * FROM mode_process WHERE case_id=%s AND version=%s
  338. ORDER BY id""", (case_id, version))
  339. rows = cur.fetchall()
  340. finally:
  341. conn.close()
  342. if not rows:
  343. return None
  344. procedures = [{
  345. "id": r["procedure_id"], "name": r["name"], "purpose": r["purpose"],
  346. "category": r["category"], "declarations": _loads(r["declarations"]),
  347. "type_registry": _loads(r["type_registry"]), "steps": _loads(r["steps"], []),
  348. "tools_used": _loads(r["tools_used"], []),
  349. } for r in rows]
  350. return {"case_id": case_id, "version": version, "platform": rows[0]["platform"],
  351. "title": rows[0]["post_title"], "model": rows[0]["model"],
  352. "cost_usd": float(rows[0]["cost_usd"]) if rows[0]["cost_usd"] is not None else None,
  353. "duration_s": rows[0]["duration_s"],
  354. "source": _loads(rows[0]["source"]), "procedures": procedures}
  355. # ── mode_tools ───────────────────────────────────────────────────────────────
  356. def replace_tools(query_id, case_id, platform, post_title, tools,
  357. model, version, cost_usd, duration_s):
  358. """写入一帖某版本的工具解构结果。语义同 replace_process。返回工具条数。"""
  359. conn = _conn()
  360. try:
  361. with conn.cursor() as cur:
  362. cur.execute("DELETE FROM mode_tools WHERE case_id=%s AND version=%s",
  363. (case_id, version))
  364. if tools:
  365. rows = [(
  366. query_id, case_id, platform, (post_title or "")[:500],
  367. (t.get("工具名称") or "")[:250],
  368. _j(t.get("实质作用域")), _j(t.get("形式作用域")),
  369. t.get("创作层级"), t.get("来源链接"), t.get("输入"), t.get("输出"),
  370. _j(t.get("用法")), _j(t.get("案例")), _j(t.get("缺点")),
  371. t.get("最新更新时间"), model, version, cost_usd, duration_s,
  372. ) for t in tools]
  373. cur.executemany("""
  374. INSERT INTO mode_tools
  375. (query_id, case_id, platform, post_title, tool_name, substance_scope,
  376. form_scope, creation_layer, source_link, input_desc, output_desc,
  377. usage_json, cases_json, defects_json, updated_time, model, version,
  378. cost_usd, duration_s)
  379. VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
  380. """, rows)
  381. return len(tools)
  382. finally:
  383. conn.close()
  384. def fetch_tools_versions(case_id):
  385. conn = _conn()
  386. try:
  387. with conn.cursor() as cur:
  388. cur.execute("""SELECT version, COUNT(*) AS n, MAX(model) AS model
  389. FROM mode_tools WHERE case_id=%s
  390. GROUP BY version ORDER BY version DESC""", (case_id,))
  391. return cur.fetchall()
  392. finally:
  393. conn.close()
  394. def fetch_tools(case_id, version=None):
  395. """重建 {case_id, version, model, tool_count, tools:[...]}。version=None 取最新。"""
  396. conn = _conn()
  397. try:
  398. with conn.cursor() as cur:
  399. if version is None:
  400. cur.execute("""SELECT version FROM mode_tools WHERE case_id=%s
  401. ORDER BY version DESC, id DESC LIMIT 1""", (case_id,))
  402. row = cur.fetchone()
  403. if not row:
  404. return None
  405. version = row["version"]
  406. cur.execute("""SELECT * FROM mode_tools WHERE case_id=%s AND version=%s
  407. ORDER BY id""", (case_id, version))
  408. rows = cur.fetchall()
  409. finally:
  410. conn.close()
  411. if not rows:
  412. return None
  413. tools = [{
  414. "工具名称": r["tool_name"], "实质作用域": _loads(r["substance_scope"]),
  415. "形式作用域": _loads(r["form_scope"]), "创作层级": r["creation_layer"],
  416. "来源链接": r["source_link"], "输入": r["input_desc"], "输出": r["output_desc"],
  417. "用法": _loads(r["usage_json"]), "案例": _loads(r["cases_json"]),
  418. "缺点": _loads(r["defects_json"]), "最新更新时间": r["updated_time"],
  419. } for r in rows]
  420. return {"case_id": case_id, "version": version, "platform": rows[0]["platform"],
  421. "title": rows[0]["post_title"], "model": rows[0]["model"],
  422. "cost_usd": float(rows[0]["cost_usd"]) if rows[0]["cost_usd"] is not None else None,
  423. "duration_s": rows[0]["duration_s"],
  424. "tool_count": len(tools), "tools": tools}
  425. # ── Dashboard 原始行(指标计算在 server.py)─────────────────────────────────────
  426. def fetch_dashboard_rows():
  427. """拉 Dashboard 计算所需的轻量行。数据量级:百~千行,Python 聚合足够。"""
  428. conn = _conn()
  429. try:
  430. with conn.cursor() as cur:
  431. cur.execute("SELECT query_id, case_id, knowledge_type FROM search_data")
  432. posts = cur.fetchall()
  433. cur.execute("""SELECT case_id, version, steps, tools_used, cost_usd,
  434. duration_s, created_at FROM mode_process""")
  435. procs = cur.fetchall()
  436. cur.execute("""SELECT case_id, version, tool_name, substance_scope,
  437. form_scope, cost_usd, duration_s, created_at
  438. FROM mode_tools""")
  439. tools = cur.fetchall()
  440. finally:
  441. conn.close()
  442. for p in posts:
  443. p["knowledge_type"] = _loads(p["knowledge_type"], [])
  444. for r in procs:
  445. r["steps"] = _loads(r["steps"], [])
  446. r["tools_used"] = _loads(r["tools_used"], [])
  447. r["cost_usd"] = float(r["cost_usd"]) if r["cost_usd"] is not None else None
  448. r["created_at"] = str(r["created_at"]) if r["created_at"] else None
  449. for r in tools:
  450. r["substance_scope"] = _loads(r["substance_scope"], [])
  451. r["form_scope"] = _loads(r["form_scope"], [])
  452. r["cost_usd"] = float(r["cost_usd"]) if r["cost_usd"] is not None else None
  453. r["created_at"] = str(r["created_at"]) if r["created_at"] else None
  454. return posts, procs, tools
  455. def check():
  456. conn = _conn()
  457. try:
  458. with conn.cursor() as cur:
  459. for t in ("search_data", "mode_process", "mode_tools"):
  460. cur.execute(f"SELECT COUNT(*) AS n FROM {t}")
  461. print(f"{t}: {cur.fetchone()['n']} 行")
  462. finally:
  463. conn.close()
  464. if __name__ == "__main__":
  465. cmd = sys.argv[1] if len(sys.argv) > 1 else ""
  466. if cmd == "init":
  467. init_tables()
  468. elif cmd == "check":
  469. check()
  470. else:
  471. print("用法:\n python db.py init # 建表\n python db.py check # 三表行数")