#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Part0: 把 downloads 表里 node_key 为空的导出文件回填到对应功能页, 实现导出↔页面一一对应。 匹配策略: 1) 普通导出: 文件目录(如 downloads/02_人事/员工关系) 对应功能页 screenshot 目录(模块/分组); 在该分组下的候选页中, 选文件名里能命中的页面名(最长命中优先)。 2) _任务中心 收割文件: 无目录线索, 用文件名关键词对全站页面名/路径做全局匹配。 仅回填 node_key 为空者; 打印每条匹配结果与未匹配项供人工核对。 """ import os, sqlite3, re, collections BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DB = os.path.join(BASE, "db", "research.db") def db(): c = sqlite3.connect(DB); c.row_factory = sqlite3.Row; return c def strip_num(s): return re.sub(r"^\d+_", "", s or "").strip() def load_pages(): c = db() rows = [dict(r) for r in c.execute( "SELECT node_key,name,full_path,screenshot_path FROM features " "WHERE screenshot_path IS NOT NULL AND screenshot_path<>''")] c.close() for r in rows: # screenshots/02_人事/员工管理/01_员工花名册.jpg -> 目录 02_人事/员工管理 d = os.path.dirname(r["screenshot_path"]).replace("screenshots/", "", 1) r["shot_dir"] = d r["name_core"] = strip_num(r["name"]).replace("员工", "").strip() return rows def best_page_in_group(fname, candidates): """在同分组候选页里, 选页面名能在文件名中命中且命中最长的。""" best, blen = None, 0 for p in candidates: for token in (p["name"], strip_num(p["name"]), p["name_core"]): token = (token or "").strip() if len(token) >= 2 and token in fname and len(token) > blen: best, blen = p, len(token) return best def global_match(fname, full_path_hint, pages): """任务中心文件: 按文件名关键词对全站页面做匹配。""" key = fname best, blen = None, 0 for p in pages: hay = (p["full_path"] or "") + " " + (p["name"] or "") for token in (strip_num(p["name"]), p["name_core"]): token = (token or "").strip() if len(token) >= 2 and token in key and len(token) > blen: best, blen = p, len(token) return best # 任务中心文件名 -> 目标页面关键词(人工先验, 提高命中) TC_HINTS = { "参保": ["参保档案", "社保"], "薪酬档案": ["薪酬档案", "薪资档案"], "已签署": ["已签署"], "电子合同": ["已签署", "合同"], } # 显式关键词->页面 node_key (第一遍模糊未命中时的人工规则, 按顺序取首个命中) EXPLICIT = [ ("异动", "人事::/personnel/relation/unusual-action"), ("谈话", "人事::/personnel/relation/talk"), ("实习生", "人事::/personnel/relation/internship"), ("已签订合同", "人事::/personnel/relation/contract"), ("合同人员", "人事::/personnel/relation/contract"), ("待入职", "人事::/personnel/relation/enrollment"), ("待离职", "人事::/personnel/relation/departure"), ("试用期", "人事::/personnel/relation/probation"), ("考勤明细", "考勤假期::/attendance/stat/records"), ("打卡记录", "考勤假期::/attendance/stat/records"), ("工资档案", "薪酬::/payroll/salary/profiles"), ] # 目录兜底: 该目录剩余未匹配文件统一归属(员工管理各类统计报表 -> 人事报表) DIR_FALLBACK = { "02_人事/员工管理": "人事::/personnel/employee/personnel-report", "02_人事/员工关系": "人事::/personnel/relation/office", } def map_explicit(): """第二遍: 对仍为空的行用显式关键词规则 + 目录兜底回填。""" c = db() fp = {r["node_key"]: r["full_path"] for r in c.execute("SELECT node_key,full_path FROM features")} rows = [dict(r) for r in c.execute( "SELECT id,file_path FROM downloads WHERE node_key IS NULL OR node_key=''")] done, left = 0, 0 for r in rows: rel = r["file_path"].replace("downloads/", "", 1) parts = rel.split("/"); fname = parts[-1]; dir_key = "/".join(parts[:-1]) nk = None for sub, node in EXPLICIT: if sub in fname: nk = node; break if not nk: nk = DIR_FALLBACK.get(dir_key) if nk and nk in fp: c.execute("UPDATE downloads SET node_key=?, full_path=? WHERE id=?", (nk, fp[nk], r["id"])) done += 1 print(" [OK2] %-40s -> %s" % (fname[:40], fp[nk])) else: left += 1 print(" [??2] %-40s (dir=%s)" % (fname[:40], dir_key)) c.execute("PRAGMA journal_mode=DELETE") c.commit(); c.close() print("显式回填: 命中 %d, 仍未匹配 %d" % (done, left)) def main(): pages = load_pages() by_dir = collections.defaultdict(list) for p in pages: by_dir[p["shot_dir"]].append(p) c = db() rows = [dict(r) for r in c.execute( "SELECT id,file_path FROM downloads WHERE node_key IS NULL OR node_key=''")] print("待回填导出文件: %d" % len(rows)) matched, unmatched = 0, [] for r in rows: rel = r["file_path"].replace("downloads/", "", 1) parts = rel.split("/") fname = parts[-1] dir_key = "/".join(parts[:-1]) page = None if dir_key.startswith("_任务") or dir_key.startswith("_"): # 任务中心: 关键词优先 + 全局匹配 hint_pages = None for kw, targets in TC_HINTS.items(): if kw in fname: hint_pages = [p for p in pages if any(t in (p["full_path"] or "") + (p["name"] or "") for t in targets)] break if hint_pages: page = best_page_in_group(fname, hint_pages) or hint_pages[0] if not page: page = global_match(fname, dir_key, pages) else: cands = by_dir.get(dir_key, []) page = best_page_in_group(fname, cands) if not page and len(cands) == 1: page = cands[0] if page: c.execute("UPDATE downloads SET node_key=?, full_path=? WHERE id=?", (page["node_key"], page["full_path"], r["id"])) matched += 1 print(" [OK] %-46s -> %s" % (fname[:46], page["full_path"])) else: unmatched.append((fname, dir_key)) print(" [??] %-46s (dir=%s) 未匹配" % (fname[:46], dir_key)) c.execute("PRAGMA journal_mode=DELETE") c.commit(); c.close() print("\n回填完成: 命中 %d, 未匹配 %d" % (matched, len(unmatched))) if unmatched: print("--- 第二遍: 显式关键词 + 目录兜底 ---") map_explicit() if __name__ == "__main__": main()