File size: 4,923 Bytes
e31fd17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# -*- coding: utf-8 -*-
"""B3 判据校验器:1229 行用途分类 + 抽样 40 复核,判据 U1–U7。

用法:python scripts/check_skill_usage_labels.py          → 退出码 0/1
      python scripts/check_skill_usage_labels.py --json   → 输出回执 JSON
U1 行数 1229 且与索引 count / name 集合逐一对应(不许多、不许少、不许重名混淆)
U2 label 只取 K/M/T/X/U 五值,无空值("全有值"= 每行 label 非空且合法)
U3 五类计数相加 == 1229,且 U(待判)≤ 20
U4 stage1 判定的行 score ≥ TH;stage2 行必须有 domain/weak 依据(reason 非空)
U5 抽样 40:条数 40、agree ≥ 36(90%),且失败项必须写明改判理由
U6 复跑字节一致(--check 由 classify_skill_usage.py 提供,这里做二次比对)
U7 CSV 自身 sha256 与 generation 回执一致(防手工改表后没重跑)
"""
import csv
import hashlib
import io
import json
import os
import sys

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INDEX = os.path.join(ROOT, "data", "skill_matrix_index.json")
CSV_F = os.path.join(ROOT, "data", "skill_usage_labels.csv")
VERDICT_F = os.path.join(ROOT, "data", "b3_sample_verdicts.json")
OUT = os.path.join(ROOT, "reports", "b3_usage_labels_check.json")
LABELS = ("K", "M", "T", "X", "U")


def main():
    checks = []

    def add(cid, ok, detail):
        checks.append({"id": cid, "ok": bool(ok), "detail": detail})

    missing = [os.path.basename(p) for p in (INDEX, CSV_F, VERDICT_F) if not os.path.isfile(p)]
    if missing:
        add("U0", False, "missing={}".format(missing))
        print(json.dumps({"verdict": "FAIL", "checks": checks}, ensure_ascii=False, indent=2))
        return 1

    with open(INDEX, encoding="utf-8") as f:
        idx = json.load(f)
    with open(CSV_F, encoding="utf-8") as f:
        text = f.read()
    rows = list(csv.DictReader(io.StringIO(text)))
    with open(VERDICT_F, encoding="utf-8") as f:
        sample = json.load(f)

    names_idx = [x.get("name") for x in idx["skills"]]
    names_csv = [r["name"] for r in rows]
    add("U1", len(rows) == 1229 and len(names_csv) == idx["count"] and names_csv == names_idx,
        "rows={} index_count={} order_identical={}".format(len(rows), idx["count"],
                                                          names_csv == names_idx))

    bad = [r["name"] for r in rows if r.get("label", "") not in LABELS]
    add("U2", not bad, "invalid_or_empty={}".format(bad[:5] + (["..."] if len(bad) > 5 else [])))

    counts = {k: 0 for k in LABELS}
    for r in rows:
        counts[r["label"]] += 1
    total = sum(counts.values())
    add("U3", total == 1229 and counts["U"] <= 20,
        "sum={} counts={} undecided={}".format(total, counts, counts["U"]))

    weak = [r["name"] for r in rows
            if r["stage"] == "stage1" and float(r["score"]) < 0.80
            or r["stage"] not in ("stage1", "stage2", "stage2-weak", "undecided")
            or not r["reason"]]
    add("U4", not weak, "bad_stage_or_reason={}".format(weak[:5]))

    agree = sample.get("agree", 0)
    dis = [x["name"] for x in sample.get("items", []) if x["verdict"] == "disagree"]
    all_noted = all(x.get("note") for x in sample.get("items", []) if x["verdict"] == "disagree")
    add("U5", len(sample.get("items", [])) == 40 and agree >= 36 and all_noted and len(dis) == sample.get("disagree"),
        "sample={} agree={}/40 disagree={} notes_complete={}".format(
            len(sample.get("items", [])), agree, len(dis), all_noted))

    # U6 复跑字节一致由 classify_skill_usage.py --check 负责,这里核一次同源构建
    sys.path.insert(0, ROOT)
    import importlib.util
    spec = importlib.util.spec_from_file_location(
        "csu", os.path.join(ROOT, "scripts", "classify_skill_usage.py"))
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    rebuilt, rcounts, rdigest = mod.build()
    same = mod.csv_text(rebuilt) == text
    add("U6", same and rcounts == counts, "byte_consistent={} digest={}".format(same, rdigest))

    actual = hashlib.sha256(text.encode("utf-8")).hexdigest()
    add("U7", sample.get("csv_sha256") == actual,
        "csv_sha256={}".format(actual))

    verdict = "PASS" if all(c["ok"] for c in checks) else "FAIL"
    doc = {"batch": "B3", "index_digest": idx.get("digest"), "labels_digest": rdigest,
           "csv_sha256": actual, "counts": counts, "sample": {"agree": agree,
           "disagree": len(dis), "threshold": 36}, "verdict": verdict,
           "failed": [c["id"] for c in checks if not c["ok"]], "checks": checks}
    os.makedirs(os.path.dirname(OUT), exist_ok=True)
    with open(OUT, "w", encoding="utf-8") as f:
        json.dump(doc, f, ensure_ascii=False, indent=2)
    print(json.dumps(doc, ensure_ascii=False, indent=2))
    return 0 if verdict == "PASS" else 1


if __name__ == "__main__":
    sys.exit(main())