#!/usr/bin/env python3 """ I列类目覆盖检查器 — 建表后跑,报警但不阻断。 检查I列是否覆盖了足够多的固定类目,不够就报警提示人工核查。 用法: python3 i-column-coverage-check.py 规则: 租赁合同行:21个固定类目,≥15个不报警,<15个报警提示核查 物业合同行:15个固定类目,≥10个不报警,<10个报警提示核查 能耗协议/其他:不检查 报警≠阻断:有些合同确实没这么多类目,报警只是提醒逐一确认"是真没有还是漏了"。 """ import sys, re import openpyxl LEASE_CATEGORIES = [ '用途', '转租', '装修改造', '广告标识', '非竞争', '维修责任', '保险要求', '物业服务联动', '配套设施', '出租方变更', '解除权机制', '违约金机制', '不可抗力', '征收拆迁', '房屋抵押查封', '政策变化', '到期处理', '恢复原状', '优先权', '管辖', '备案' ] PROPERTY_CATEGORIES = [ '物业服务内容', '服务标准', '公共能耗费', '特约服务', '共用设施管理', '装修管理', '安保措施', '消防安全', '保险要求', '联动终止', '违约责任', '退出交接', '免责条款', '不可抗力', '管辖' ] LEASE_THRESHOLD = 15 PROPERTY_THRESHOLD = 10 def check_row(row_num, cell_value, contract_type): """检查一行I列的类目覆盖情况""" if not cell_value: return None text = str(cell_value) if contract_type == 'lease': categories = LEASE_CATEGORIES threshold = LEASE_THRESHOLD type_name = '租赁合同' elif contract_type == 'property': categories = PROPERTY_CATEGORIES threshold = PROPERTY_THRESHOLD type_name = '物业合同' else: return None found = [] missing = [] for cat in categories: # 检查类目是否出现在文本中(允许【】或[]包裹) if re.search(rf'[·\-\[\【]{cat}[\]\】]?', text) or cat in text: found.append(cat) else: missing.append(cat) coverage = len(found) total = len(categories) result = { 'row': row_num, 'type': type_name, 'coverage': coverage, 'total': total, 'threshold': threshold, 'found': found, 'missing': missing, 'alert': coverage < threshold } return result def main(): if len(sys.argv) < 2: print("用法: python3 i-column-coverage-check.py ") sys.exit(1) filepath = sys.argv[1] wb = openpyxl.load_workbook(filepath) ws = wb.active results = [] for row in range(1, ws.max_row + 1): # 判断合同类型(C列) c_val = str(ws.cell(row, 3).value or '').strip() i_val = ws.cell(row, 9).value if not i_val or not c_val: continue if '租赁' in c_val and '物业' not in c_val: contract_type = 'lease' elif '物业' in c_val: contract_type = 'property' else: continue # 能耗协议等不检查 result = check_row(row, i_val, contract_type) if result: results.append(result) if not results: print("⚠️ 未找到租赁/物业合同行,请确认文件结构。") sys.exit(0) all_pass = True for r in results: status = '✅' if not r['alert'] else '⚠️' if r['alert']: all_pass = False print(f"{status} Row {r['row']} [{r['type']}]: {r['coverage']}/{r['total']} 类目" f"(阈值{r['threshold']})") if r['alert']: print(f" 缺失类目: {', '.join(r['missing'])}") print(f" → 请逐一核查:是合同确实没有,还是提取时漏了?") print() if all_pass: print(f"\n✅ I列类目覆盖检查通过({len(results)}行全部达标)。") else: alert_count = sum(1 for r in results if r['alert']) print(f"\n⚠️ {alert_count}行I列类目覆盖不足,请核查确认。") print(" 说明:报警≠错误。有些合同确实没这么多类目,核查确认即可。") if __name__ == '__main__': main()