verify_import.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. """
  2. 导入完整性验证脚本
  3. 检查 DOCX 文件数 vs 数据库入库数,定位漏读/漏写的药品
  4. """
  5. import os, sys, json, asyncio
  6. from pathlib import Path
  7. from collections import Counter
  8. # 添加同目录模块
  9. sys.path.insert(0, str(Path(__file__).resolve().parent))
  10. from docx_ingest import find_docx_files, parse_docx
  11. # 数据库
  12. import asyncpg
  13. SOURCE_DIR = os.environ.get("DOCX_SOURCE_DIR", "/opt/2025")
  14. DB_HOST = os.environ.get("POSTGRES_HOST", "localhost")
  15. DB_PORT = os.environ.get("POSTGRES_PORT", "5432")
  16. DB_NAME = os.environ.get("POSTGRES_DB", "pharmacopoeia")
  17. DB_USER = os.environ.get("POSTGRES_USER", "postgres")
  18. DB_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "postgres")
  19. async def main():
  20. # ==========================================
  21. # 1. 扫描 DOCX 文件
  22. # ==========================================
  23. print("=" * 60)
  24. print("📂 扫描 DOCX 文件...")
  25. files = find_docx_files(SOURCE_DIR)
  26. print(f" DOCX 文件总数: {len(files)}")
  27. # 提取文件名(去 .docx)作为药品名
  28. docx_names = set()
  29. docx_by_volume = Counter()
  30. for fp in files:
  31. name = Path(fp).stem
  32. docx_names.add(name)
  33. vol = "一部"
  34. for k in ["output4", "output3", "output2", "output"]:
  35. if k in fp:
  36. vol = {"output": "一部", "output2": "二部", "output3": "三部", "output4": "四部"}[k]
  37. break
  38. docx_by_volume[vol] += 1
  39. print(f" 去重后唯一药名: {len(docx_names)}")
  40. print(f" 各卷分布: {dict(docx_by_volume)}")
  41. # ==========================================
  42. # 2. 查询数据库
  43. # ==========================================
  44. print("\n📊 查询数据库...")
  45. conn = await asyncpg.connect(
  46. host=DB_HOST, port=DB_PORT, database=DB_NAME,
  47. user=DB_USER, password=DB_PASSWORD
  48. )
  49. # 2.1 药品统计
  50. total_drugs = await conn.fetchval("SELECT COUNT(*) FROM drugs WHERE is_active = TRUE")
  51. total_chunks = await conn.fetchval("SELECT COUNT(*) FROM drug_chunks WHERE vec IS NOT NULL")
  52. total_chunks_null = await conn.fetchval("SELECT COUNT(*) FROM drug_chunks WHERE vec IS NULL")
  53. print(f" drugs 表 (active): {total_drugs}")
  54. print(f" drug_chunks (有向量): {total_chunks}")
  55. print(f" drug_chunks (无向量): {total_chunks_null}")
  56. # 2.2 获取数据库中所有药名
  57. db_names = set()
  58. rows = await conn.fetch("SELECT name FROM drugs WHERE is_active = TRUE")
  59. for r in rows:
  60. db_names.add(r["name"])
  61. print(f" 数据库中药名: {len(db_names)}")
  62. # 2.3 按来源版本统计
  63. version_counts = await conn.fetch("""
  64. SELECT source_version, COUNT(*) FROM drugs
  65. WHERE is_active = TRUE GROUP BY source_version ORDER BY COUNT(*) DESC
  66. """)
  67. print(f" 版本分布:")
  68. for r in version_counts:
  69. print(f" {r['source_version']}: {r['count']}")
  70. # 2.4 按分类统计
  71. cat_counts = await conn.fetch("""
  72. SELECT category, COUNT(*) FROM drugs
  73. WHERE is_active = TRUE GROUP BY category ORDER BY COUNT(*) DESC
  74. """)
  75. print(f" 分类分布:")
  76. for r in cat_counts:
  77. print(f" {r['category']}: {r['count']}")
  78. # ==========================================
  79. # 3. 对比分析
  80. # ==========================================
  81. print("\n🔍 对比分析:")
  82. # 3.1 DOCX 有但 DB 没有的(漏读)
  83. missing = docx_names - db_names
  84. if missing:
  85. print(f"\n ❌ DOCX 有但数据库没有({len(missing)} 个):")
  86. for name in sorted(missing)[:30]:
  87. print(f" - {name}")
  88. if len(missing) > 30:
  89. print(f" ... 还有 {len(missing) - 30} 个")
  90. else:
  91. print(f"\n ✅ 无漏读:所有 DOCX 药名均已在数据库中")
  92. # 3.2 DB 有但 DOCX 没有的(额外数据,如 Wiki)
  93. extra = db_names - docx_names
  94. if extra:
  95. print(f"\n 📝 数据库额外药名(Wiki/样本数据,{len(extra)} 个):")
  96. for name in sorted(extra):
  97. print(f" - {name}")
  98. # ==========================================
  99. # 4. 抽查特定药品的 sections
  100. # ==========================================
  101. print("\n🔬 抽查药品内容完整性:")
  102. spot_check = ["布洛芬", "甲硝唑", "阿莫西林", "对乙酰氨基酚", "二甲双胍"]
  103. for name in spot_check:
  104. drug = await conn.fetchrow(
  105. "SELECT drug_id, name, sections, source_version FROM drugs WHERE name = $1 AND is_active = TRUE",
  106. name
  107. )
  108. if drug:
  109. sections = json.loads(drug["sections"]) if drug["sections"] else {}
  110. chunk_count = await conn.fetchval(
  111. "SELECT COUNT(*) FROM drug_chunks WHERE drug_id = $1 AND vec IS NOT NULL",
  112. drug["drug_id"]
  113. )
  114. sec_names = list(sections.keys())
  115. print(f" ✅ {name} | {len(sec_names)} sections | {chunk_count} chunks | {drug['source_version']}")
  116. print(f" 栏目: {sec_names[:10]}...")
  117. else:
  118. print(f" ❌ {name}: 数据库中不存在")
  119. # ==========================================
  120. # 5. 检查 chunks 完整性
  121. # ==========================================
  122. print("\n📐 Chunks 完整性检查:")
  123. # 5.1 检查 drug_id 引用完整性(孤儿 chunk)
  124. orphan_chunks = await conn.fetchval("""
  125. SELECT COUNT(*) FROM drug_chunks c
  126. LEFT JOIN drugs d ON d.drug_id = c.drug_id
  127. WHERE d.id IS NULL
  128. """)
  129. print(f" 孤儿 chunks(无对应 drug): {orphan_chunks}")
  130. # 5.2 有 drug 但没有 chunk 的
  131. no_chunk_drugs = await conn.fetchval("""
  132. SELECT COUNT(*) FROM drugs d
  133. WHERE d.is_active = TRUE
  134. AND NOT EXISTS (SELECT 1 FROM drug_chunks c WHERE c.drug_id = d.drug_id)
  135. """)
  136. print(f" 无 chunk 的药品: {no_chunk_drugs}")
  137. # 5.3 vec 维度是否正确
  138. vec_dim = await conn.fetchval("""
  139. SELECT vector_dims(vec) FROM drug_chunks WHERE vec IS NOT NULL LIMIT 1
  140. """)
  141. print(f" 向量维度: {vec_dim}")
  142. # ==========================================
  143. # 6. 汇总结论
  144. # ==========================================
  145. print("\n" + "=" * 60)
  146. print("📋 验证结论:")
  147. print(f" DOCX 文件: {len(files)}")
  148. print(f" 数据库中药品: {total_drugs}")
  149. print(f" 数据库 chunks: {total_chunks}")
  150. print(f" 漏读药品: {len(missing)}")
  151. print(f" 额外药品 (Wiki等): {len(extra)}")
  152. print(f" 孤儿 chunks: {orphan_chunks}")
  153. print(f" 无 chunk 药品: {no_chunk_drugs}")
  154. if len(missing) == 0 and orphan_chunks == 0 and total_chunks > 0:
  155. print("\n ✅ 导入完整,无漏读漏写")
  156. else:
  157. print(f"\n ⚠️ 发现问题,请检查上述 ❌ 项")
  158. print("=" * 60)
  159. await conn.close()
  160. if __name__ == "__main__":
  161. asyncio.run(main())