172 lines
6.7 KiB
Python
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()
|