Files
twoJob-demo/scripts/map_downloads.py
T
2026-09-03 15:35:23 +08:00

172 lines
6.7 KiB
Python

#!/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()