| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- """
- 导入完整性验证脚本
- 检查 DOCX 文件数 vs 数据库入库数,定位漏读/漏写的药品
- """
- import os, sys, json, asyncio
- from pathlib import Path
- from collections import Counter
- # 添加同目录模块
- sys.path.insert(0, str(Path(__file__).resolve().parent))
- from docx_ingest import find_docx_files, parse_docx
- # 数据库
- import asyncpg
- SOURCE_DIR = os.environ.get("DOCX_SOURCE_DIR", "/opt/2025")
- DB_HOST = os.environ.get("POSTGRES_HOST", "localhost")
- DB_PORT = os.environ.get("POSTGRES_PORT", "5432")
- DB_NAME = os.environ.get("POSTGRES_DB", "pharmacopoeia")
- DB_USER = os.environ.get("POSTGRES_USER", "postgres")
- DB_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "postgres")
- async def main():
- # ==========================================
- # 1. 扫描 DOCX 文件
- # ==========================================
- print("=" * 60)
- print("📂 扫描 DOCX 文件...")
- files = find_docx_files(SOURCE_DIR)
- print(f" DOCX 文件总数: {len(files)}")
- # 提取文件名(去 .docx)作为药品名
- docx_names = set()
- docx_by_volume = Counter()
- for fp in files:
- name = Path(fp).stem
- docx_names.add(name)
- vol = "一部"
- for k in ["output4", "output3", "output2", "output"]:
- if k in fp:
- vol = {"output": "一部", "output2": "二部", "output3": "三部", "output4": "四部"}[k]
- break
- docx_by_volume[vol] += 1
- print(f" 去重后唯一药名: {len(docx_names)}")
- print(f" 各卷分布: {dict(docx_by_volume)}")
- # ==========================================
- # 2. 查询数据库
- # ==========================================
- print("\n📊 查询数据库...")
- conn = await asyncpg.connect(
- host=DB_HOST, port=DB_PORT, database=DB_NAME,
- user=DB_USER, password=DB_PASSWORD
- )
- # 2.1 药品统计
- total_drugs = await conn.fetchval("SELECT COUNT(*) FROM drugs WHERE is_active = TRUE")
- total_chunks = await conn.fetchval("SELECT COUNT(*) FROM drug_chunks WHERE vec IS NOT NULL")
- total_chunks_null = await conn.fetchval("SELECT COUNT(*) FROM drug_chunks WHERE vec IS NULL")
- print(f" drugs 表 (active): {total_drugs}")
- print(f" drug_chunks (有向量): {total_chunks}")
- print(f" drug_chunks (无向量): {total_chunks_null}")
- # 2.2 获取数据库中所有药名
- db_names = set()
- rows = await conn.fetch("SELECT name FROM drugs WHERE is_active = TRUE")
- for r in rows:
- db_names.add(r["name"])
- print(f" 数据库中药名: {len(db_names)}")
- # 2.3 按来源版本统计
- version_counts = await conn.fetch("""
- SELECT source_version, COUNT(*) FROM drugs
- WHERE is_active = TRUE GROUP BY source_version ORDER BY COUNT(*) DESC
- """)
- print(f" 版本分布:")
- for r in version_counts:
- print(f" {r['source_version']}: {r['count']}")
- # 2.4 按分类统计
- cat_counts = await conn.fetch("""
- SELECT category, COUNT(*) FROM drugs
- WHERE is_active = TRUE GROUP BY category ORDER BY COUNT(*) DESC
- """)
- print(f" 分类分布:")
- for r in cat_counts:
- print(f" {r['category']}: {r['count']}")
- # ==========================================
- # 3. 对比分析
- # ==========================================
- print("\n🔍 对比分析:")
- # 3.1 DOCX 有但 DB 没有的(漏读)
- missing = docx_names - db_names
- if missing:
- print(f"\n ❌ DOCX 有但数据库没有({len(missing)} 个):")
- for name in sorted(missing)[:30]:
- print(f" - {name}")
- if len(missing) > 30:
- print(f" ... 还有 {len(missing) - 30} 个")
- else:
- print(f"\n ✅ 无漏读:所有 DOCX 药名均已在数据库中")
- # 3.2 DB 有但 DOCX 没有的(额外数据,如 Wiki)
- extra = db_names - docx_names
- if extra:
- print(f"\n 📝 数据库额外药名(Wiki/样本数据,{len(extra)} 个):")
- for name in sorted(extra):
- print(f" - {name}")
- # ==========================================
- # 4. 抽查特定药品的 sections
- # ==========================================
- print("\n🔬 抽查药品内容完整性:")
- spot_check = ["布洛芬", "甲硝唑", "阿莫西林", "对乙酰氨基酚", "二甲双胍"]
- for name in spot_check:
- drug = await conn.fetchrow(
- "SELECT drug_id, name, sections, source_version FROM drugs WHERE name = $1 AND is_active = TRUE",
- name
- )
- if drug:
- sections = json.loads(drug["sections"]) if drug["sections"] else {}
- chunk_count = await conn.fetchval(
- "SELECT COUNT(*) FROM drug_chunks WHERE drug_id = $1 AND vec IS NOT NULL",
- drug["drug_id"]
- )
- sec_names = list(sections.keys())
- print(f" ✅ {name} | {len(sec_names)} sections | {chunk_count} chunks | {drug['source_version']}")
- print(f" 栏目: {sec_names[:10]}...")
- else:
- print(f" ❌ {name}: 数据库中不存在")
- # ==========================================
- # 5. 检查 chunks 完整性
- # ==========================================
- print("\n📐 Chunks 完整性检查:")
- # 5.1 检查 drug_id 引用完整性(孤儿 chunk)
- orphan_chunks = await conn.fetchval("""
- SELECT COUNT(*) FROM drug_chunks c
- LEFT JOIN drugs d ON d.drug_id = c.drug_id
- WHERE d.id IS NULL
- """)
- print(f" 孤儿 chunks(无对应 drug): {orphan_chunks}")
- # 5.2 有 drug 但没有 chunk 的
- no_chunk_drugs = await conn.fetchval("""
- SELECT COUNT(*) FROM drugs d
- WHERE d.is_active = TRUE
- AND NOT EXISTS (SELECT 1 FROM drug_chunks c WHERE c.drug_id = d.drug_id)
- """)
- print(f" 无 chunk 的药品: {no_chunk_drugs}")
- # 5.3 vec 维度是否正确
- vec_dim = await conn.fetchval("""
- SELECT vector_dims(vec) FROM drug_chunks WHERE vec IS NOT NULL LIMIT 1
- """)
- print(f" 向量维度: {vec_dim}")
- # ==========================================
- # 6. 汇总结论
- # ==========================================
- print("\n" + "=" * 60)
- print("📋 验证结论:")
- print(f" DOCX 文件: {len(files)}")
- print(f" 数据库中药品: {total_drugs}")
- print(f" 数据库 chunks: {total_chunks}")
- print(f" 漏读药品: {len(missing)}")
- print(f" 额外药品 (Wiki等): {len(extra)}")
- print(f" 孤儿 chunks: {orphan_chunks}")
- print(f" 无 chunk 药品: {no_chunk_drugs}")
- if len(missing) == 0 and orphan_chunks == 0 and total_chunks > 0:
- print("\n ✅ 导入完整,无漏读漏写")
- else:
- print(f"\n ⚠️ 发现问题,请检查上述 ❌ 项")
- print("=" * 60)
- await conn.close()
- if __name__ == "__main__":
- asyncio.run(main())
|