@hupan56/wlkj 3.3.13 → 3.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +0 -7
- package/package.json +29 -29
- package/templates/qoder/commands/optional/wl-spec.md +0 -8
- package/templates/qoder/commands/optional/wl-status.md +0 -8
- package/templates/qoder/commands/wl-code.md +0 -8
- package/templates/qoder/commands/wl-commit.md +0 -8
- package/templates/qoder/commands/wl-init.md +129 -129
- package/templates/qoder/commands/wl-search.md +0 -8
- package/templates/qoder/commands/wl-task.md +0 -8
- package/templates/qoder/commands/wl-test.md +0 -8
- package/templates/qoder/config.yaml +2 -3
- package/templates/qoder/contracts/insight.md +55 -55
- package/templates/qoder/hooks/pre-tool-use-commit.py +124 -124
- package/templates/qoder/hooks/session-start.py +384 -384
- package/templates/qoder/hooks/stop-eval.py +383 -383
- package/templates/qoder/scripts/capability/adapters/mcp.py +322 -322
- package/templates/qoder/scripts/capability/registry.py +245 -245
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
- package/templates/qoder/scripts/deployment/setup/setup.py +0 -11
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +1503 -1503
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
- package/templates/qoder/scripts/foundation/io/context_cache.py +94 -94
- package/templates/qoder/scripts/orchestration/wlkj.py +295 -306
- package/templates/qoder/scripts/tool_guide.md +70 -70
- package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -176
- package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -110
- package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -104
- package/templates/qoder/scripts/validation/eval/bf_score.py +218 -218
- package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -150
- package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -156
- package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -23
- package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -143
- package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -261
- package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -33
- package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -105
- package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -118
- package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -128
- package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -148
- package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -166
- package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -148
- package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -139
- package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -188
- package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -124
- package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -88
- package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -177
- package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -170
- package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -78
- package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -60
- package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -70
- package/templates/qoder/commands/wl-data.md +0 -46
- package/templates/qoder/commands/wl-fix.md +0 -47
- package/templates/qoder/commands/wl-knowledge.md +0 -41
- package/templates/qoder/commands/wl-review.md +0 -49
- package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
|
@@ -1,218 +1,218 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""bf_score.py - 业务保真(BF)统一打分器:吃【真命令产物文件】打 BF 分。
|
|
4
|
-
|
|
5
|
-
把 FW9-11 的 BF 尺子从"代理生成+打分"改成"读真产物文件+打分"。
|
|
6
|
-
宿主真跑 /wl-prd /wl-design /wl-code /wl-search 出的真产物 → 本工具打 BF 分(对真 ICS 业务数据)。
|
|
7
|
-
|
|
8
|
-
用法:
|
|
9
|
-
python bf_score.py prd <PRD.md> [--feature 保险] # BF1 schema保真 + BF2 内容保真(LLM-judge)
|
|
10
|
-
python bf_score.py proto <原型.html> # BF3 组件保真 + BF4 颜色保真
|
|
11
|
-
python bf_score.py code <实体列表.txt|diff> --feature 保险 # BF5 feature精准
|
|
12
|
-
python bf_score.py search <结果.json或txt> --feature 保险 # BF6 feature相关率
|
|
13
|
-
|
|
14
|
-
跑法(容器内,接真业务数据):
|
|
15
|
-
docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/bf_score.py prd /path/REQ.md --feature 保险
|
|
16
|
-
"""
|
|
17
|
-
import os
|
|
18
|
-
import re
|
|
19
|
-
import sys
|
|
20
|
-
import json
|
|
21
|
-
import colorsys
|
|
22
|
-
import urllib.request
|
|
23
|
-
import urllib.error
|
|
24
|
-
|
|
25
|
-
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
26
|
-
if os.path.isdir(_p) and _p not in sys.path:
|
|
27
|
-
sys.path.insert(0, _p)
|
|
28
|
-
|
|
29
|
-
from sqlalchemy import text # noqa: E402
|
|
30
|
-
from app.db import SessionLocal # noqa: E402
|
|
31
|
-
|
|
32
|
-
_H = None # lazy: 避免与 mcp_handlers 循环 import
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _hub():
|
|
36
|
-
"""惰性取 mcp_handlers(dispatch rag_search/get_design_system),避免循环 import。"""
|
|
37
|
-
global _H
|
|
38
|
-
if _H is None:
|
|
39
|
-
from app.routers import mcp_handlers as _h
|
|
40
|
-
try:
|
|
41
|
-
_h._init_handlers()
|
|
42
|
-
except Exception:
|
|
43
|
-
pass
|
|
44
|
-
_H = _h
|
|
45
|
-
return _H
|
|
46
|
-
|
|
47
|
-
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
48
|
-
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
49
|
-
JUDGE_MODEL = "qwen-max"
|
|
50
|
-
_NEUTRAL = {(255, 255, 255), (0, 0, 0), (51, 51, 51), (102, 102, 102), (153, 153, 153),
|
|
51
|
-
(240, 240, 240), (250, 250, 250), (217, 217, 217), (232, 232, 232), (245, 245, 245)}
|
|
52
|
-
_TABLE_RE = re.compile(r'\b(t_[a-z_]+|tb_[a-z_]+|report_[a-z_]+|salary_[a-z_]+|[a-z]+_[a-z_]+_table)\b', re.IGNORECASE)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _read(path):
|
|
56
|
-
try:
|
|
57
|
-
with open(path, encoding="utf-8", errors="replace") as f:
|
|
58
|
-
return f.read()
|
|
59
|
-
except Exception as e:
|
|
60
|
-
return ""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _q(s, sql, p=None):
|
|
64
|
-
try:
|
|
65
|
-
return s.execute(text(sql), p or {}).fetchall()
|
|
66
|
-
except Exception:
|
|
67
|
-
try:
|
|
68
|
-
s.rollback()
|
|
69
|
-
except Exception:
|
|
70
|
-
pass
|
|
71
|
-
return []
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def chat(model, prompt, max_tokens=10, timeout=60):
|
|
75
|
-
body = json.dumps({"model": model, "messages": [{"role": "user", "content": prompt}],
|
|
76
|
-
"temperature": 0.0, "max_tokens": max_tokens}).encode()
|
|
77
|
-
for a in range(3):
|
|
78
|
-
try:
|
|
79
|
-
req = urllib.request.Request(CHAT_URL, data=body,
|
|
80
|
-
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
81
|
-
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
82
|
-
except Exception:
|
|
83
|
-
if a < 2:
|
|
84
|
-
import time
|
|
85
|
-
time.sleep(1.0 * (a + 1))
|
|
86
|
-
else:
|
|
87
|
-
return ""
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# ── BF1: PRD schema 保真 ──
|
|
91
|
-
def bf1_schema(prd_text):
|
|
92
|
-
mentioned = set(m.lower() for m in _TABLE_RE.findall(prd_text))
|
|
93
|
-
if not mentioned:
|
|
94
|
-
return {"score": 0.0, "mentioned": 0, "note": "PRD 未提表名"}
|
|
95
|
-
s = SessionLocal()
|
|
96
|
-
all_tabs = set(r[0].lower() for r in _q(s, "SELECT table_name FROM db_tables"))
|
|
97
|
-
s.close()
|
|
98
|
-
real = mentioned & all_tabs
|
|
99
|
-
return {"score": round(len(real) / len(mentioned), 3), "mentioned": len(mentioned), "real": len(real)}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# ── BF2: PRD 内容保真(LLM-judge) ──
|
|
103
|
-
def bf2_content(prd_text, feature):
|
|
104
|
-
r = _hub().dispatch("rag_search", {"query": feature, "kind": "prd", "top_k": 1}) or {}
|
|
105
|
-
items = r.get("items") or []
|
|
106
|
-
ref = (items[0].get("text") or "")[:600] if items else ""
|
|
107
|
-
if not ref:
|
|
108
|
-
return {"score": 0, "note": "无真 PRD 范本"}
|
|
109
|
-
p = ("你是 ICS(车辆物联网系统)资深产品专家。对照【真实 ICS PRD 范本】,给【生成 PRD】业务保真度打分 1-5:\n"
|
|
110
|
-
"5=完全 ICS 业务写法(系统级/字段级/真业务流程);3=部分;1=通用产品宣传文。\n"
|
|
111
|
-
f"【真 ICS PRD 范本】:\n{ref}\n\n【待评 PRD】:\n{prd_text[:1000]}\n\n只输出一个整数(1-5)。")
|
|
112
|
-
out = chat(JUDGE_MODEL, p)
|
|
113
|
-
m = re.search(r"[1-5]", out)
|
|
114
|
-
return {"score": int(m.group()) if m else 0, "ref_len": len(ref)}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# ── BF3/4: 原型保真 ──
|
|
118
|
-
def _design():
|
|
119
|
-
r = _hub().dispatch("get_design_system", {}) or {}
|
|
120
|
-
proj = next((p for p in r.get("projects", []) if p.get("project") == "fywl-ui"), {})
|
|
121
|
-
colors = proj.get("colors", {}) or {}
|
|
122
|
-
palette = set()
|
|
123
|
-
for v in colors.values():
|
|
124
|
-
if isinstance(v, str) and v.startswith("hsl"):
|
|
125
|
-
m = re.match(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", v)
|
|
126
|
-
if m:
|
|
127
|
-
h, s_, l = float(m.group(1)) / 360, float(m.group(2)) / 100, float(m.group(3)) / 100
|
|
128
|
-
r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
|
|
129
|
-
palette.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
|
|
130
|
-
comps = set()
|
|
131
|
-
for d in (proj.get("commonComponents", {}), proj.get("formComponents", {})):
|
|
132
|
-
if isinstance(d, dict):
|
|
133
|
-
comps.update(k.lower() for k in d)
|
|
134
|
-
return palette, comps
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def _extract_colors(html):
|
|
138
|
-
out = set()
|
|
139
|
-
for m in re.findall(r"#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b", html):
|
|
140
|
-
h = m if len(m) == 6 else m * 2
|
|
141
|
-
out.add((int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)))
|
|
142
|
-
for m in re.findall(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", html):
|
|
143
|
-
h, s_, l = float(m[0]) / 360, float(m[1]) / 100, float(m[2]) / 100
|
|
144
|
-
r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
|
|
145
|
-
out.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
|
|
146
|
-
for m in re.findall(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", html):
|
|
147
|
-
out.add((int(m[0]), int(m[1]), int(m[2])))
|
|
148
|
-
return out
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _color_real(c, palette):
|
|
152
|
-
if c in _NEUTRAL:
|
|
153
|
-
return True
|
|
154
|
-
return any(all(abs(a - b) <= 40 for a, b in zip(c, p)) for p in palette)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def bf34_proto(html):
|
|
158
|
-
palette, comps = _design()
|
|
159
|
-
cols = _extract_colors(html)
|
|
160
|
-
color_score = (sum(1 for c in cols if _color_real(c, palette)) / len(cols)) if cols else 0.0
|
|
161
|
-
used = set(m.lower() for m in re.findall(r"<(?:el|van|a|n)-([\w-]+)", html))
|
|
162
|
-
comp_score = (sum(1 for c in used if c in comps) / len(used)) if used and comps else (1.0 if not comps else 0.0)
|
|
163
|
-
return {"bf3_component": round(comp_score, 3), "bf4_color": round(color_score, 3),
|
|
164
|
-
"colors": len(cols), "components": len(used)}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# ── BF5/6: feature 实体精准/相关率 ──
|
|
168
|
-
def _feature_gold(feature):
|
|
169
|
-
s = SessionLocal()
|
|
170
|
-
rows = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
|
|
171
|
-
"AND type IN ('CONTROLLER','SERVICE','TABLE','COLUMN','FUNCTION') LIMIT 30", {"p": f"%{feature}%"})
|
|
172
|
-
s.close()
|
|
173
|
-
return set(c for c, _ in rows if c)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def bf56_entities(text, feature):
|
|
177
|
-
gold = _feature_gold(feature)
|
|
178
|
-
cands = set(re.findall(r"[A-Za-z_][\w]{2,}", text))
|
|
179
|
-
if not cands:
|
|
180
|
-
return {"score": 0.0, "candidates": 0, "gold": len(gold)}
|
|
181
|
-
hit = sum(1 for c in cands if c in gold)
|
|
182
|
-
return {"score": round(hit / len(cands), 3), "candidates": len(cands), "gold_hit": hit, "gold": len(gold)}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def main():
|
|
186
|
-
if len(sys.argv) < 3:
|
|
187
|
-
sys.stderr.write(__doc__)
|
|
188
|
-
return 1
|
|
189
|
-
kind, path = sys.argv[1], sys.argv[2]
|
|
190
|
-
feature = None
|
|
191
|
-
if "--feature" in sys.argv:
|
|
192
|
-
feature = sys.argv[sys.argv.index("--feature") + 1]
|
|
193
|
-
text = _read(path)
|
|
194
|
-
if not text:
|
|
195
|
-
sys.stderr.write("读不到文件: %s\n" % path)
|
|
196
|
-
return 1
|
|
197
|
-
out = {"file": os.path.basename(path), "kind": kind}
|
|
198
|
-
if kind == "prd":
|
|
199
|
-
out["BF1_schema"] = bf1_schema(text)
|
|
200
|
-
if feature:
|
|
201
|
-
out["BF2_content"] = bf2_content(text, feature)
|
|
202
|
-
elif kind == "proto":
|
|
203
|
-
out.update(bf34_proto(text))
|
|
204
|
-
elif kind in ("code", "search"):
|
|
205
|
-
if not feature:
|
|
206
|
-
sys.stderr.write("code/search 需 --feature\n")
|
|
207
|
-
return 1
|
|
208
|
-
bf = bf56_entities(text, feature)
|
|
209
|
-
out["BF5_code" if kind == "code" else "BF6_search"] = bf
|
|
210
|
-
else:
|
|
211
|
-
sys.stderr.write("未知 kind: %s (prd/proto/code/search)\n" % kind)
|
|
212
|
-
return 1
|
|
213
|
-
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
214
|
-
return 0
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if __name__ == "__main__":
|
|
218
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""bf_score.py - 业务保真(BF)统一打分器:吃【真命令产物文件】打 BF 分。
|
|
4
|
+
|
|
5
|
+
把 FW9-11 的 BF 尺子从"代理生成+打分"改成"读真产物文件+打分"。
|
|
6
|
+
宿主真跑 /wl-prd /wl-design /wl-code /wl-search 出的真产物 → 本工具打 BF 分(对真 ICS 业务数据)。
|
|
7
|
+
|
|
8
|
+
用法:
|
|
9
|
+
python bf_score.py prd <PRD.md> [--feature 保险] # BF1 schema保真 + BF2 内容保真(LLM-judge)
|
|
10
|
+
python bf_score.py proto <原型.html> # BF3 组件保真 + BF4 颜色保真
|
|
11
|
+
python bf_score.py code <实体列表.txt|diff> --feature 保险 # BF5 feature精准
|
|
12
|
+
python bf_score.py search <结果.json或txt> --feature 保险 # BF6 feature相关率
|
|
13
|
+
|
|
14
|
+
跑法(容器内,接真业务数据):
|
|
15
|
+
docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/bf_score.py prd /path/REQ.md --feature 保险
|
|
16
|
+
"""
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import json
|
|
21
|
+
import colorsys
|
|
22
|
+
import urllib.request
|
|
23
|
+
import urllib.error
|
|
24
|
+
|
|
25
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
26
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
27
|
+
sys.path.insert(0, _p)
|
|
28
|
+
|
|
29
|
+
from sqlalchemy import text # noqa: E402
|
|
30
|
+
from app.db import SessionLocal # noqa: E402
|
|
31
|
+
|
|
32
|
+
_H = None # lazy: 避免与 mcp_handlers 循环 import
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _hub():
|
|
36
|
+
"""惰性取 mcp_handlers(dispatch rag_search/get_design_system),避免循环 import。"""
|
|
37
|
+
global _H
|
|
38
|
+
if _H is None:
|
|
39
|
+
from app.routers import mcp_handlers as _h
|
|
40
|
+
try:
|
|
41
|
+
_h._init_handlers()
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
_H = _h
|
|
45
|
+
return _H
|
|
46
|
+
|
|
47
|
+
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
48
|
+
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
49
|
+
JUDGE_MODEL = "qwen-max"
|
|
50
|
+
_NEUTRAL = {(255, 255, 255), (0, 0, 0), (51, 51, 51), (102, 102, 102), (153, 153, 153),
|
|
51
|
+
(240, 240, 240), (250, 250, 250), (217, 217, 217), (232, 232, 232), (245, 245, 245)}
|
|
52
|
+
_TABLE_RE = re.compile(r'\b(t_[a-z_]+|tb_[a-z_]+|report_[a-z_]+|salary_[a-z_]+|[a-z]+_[a-z_]+_table)\b', re.IGNORECASE)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _read(path):
|
|
56
|
+
try:
|
|
57
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
58
|
+
return f.read()
|
|
59
|
+
except Exception as e:
|
|
60
|
+
return ""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _q(s, sql, p=None):
|
|
64
|
+
try:
|
|
65
|
+
return s.execute(text(sql), p or {}).fetchall()
|
|
66
|
+
except Exception:
|
|
67
|
+
try:
|
|
68
|
+
s.rollback()
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def chat(model, prompt, max_tokens=10, timeout=60):
|
|
75
|
+
body = json.dumps({"model": model, "messages": [{"role": "user", "content": prompt}],
|
|
76
|
+
"temperature": 0.0, "max_tokens": max_tokens}).encode()
|
|
77
|
+
for a in range(3):
|
|
78
|
+
try:
|
|
79
|
+
req = urllib.request.Request(CHAT_URL, data=body,
|
|
80
|
+
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
81
|
+
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
82
|
+
except Exception:
|
|
83
|
+
if a < 2:
|
|
84
|
+
import time
|
|
85
|
+
time.sleep(1.0 * (a + 1))
|
|
86
|
+
else:
|
|
87
|
+
return ""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── BF1: PRD schema 保真 ──
|
|
91
|
+
def bf1_schema(prd_text):
|
|
92
|
+
mentioned = set(m.lower() for m in _TABLE_RE.findall(prd_text))
|
|
93
|
+
if not mentioned:
|
|
94
|
+
return {"score": 0.0, "mentioned": 0, "note": "PRD 未提表名"}
|
|
95
|
+
s = SessionLocal()
|
|
96
|
+
all_tabs = set(r[0].lower() for r in _q(s, "SELECT table_name FROM db_tables"))
|
|
97
|
+
s.close()
|
|
98
|
+
real = mentioned & all_tabs
|
|
99
|
+
return {"score": round(len(real) / len(mentioned), 3), "mentioned": len(mentioned), "real": len(real)}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ── BF2: PRD 内容保真(LLM-judge) ──
|
|
103
|
+
def bf2_content(prd_text, feature):
|
|
104
|
+
r = _hub().dispatch("rag_search", {"query": feature, "kind": "prd", "top_k": 1}) or {}
|
|
105
|
+
items = r.get("items") or []
|
|
106
|
+
ref = (items[0].get("text") or "")[:600] if items else ""
|
|
107
|
+
if not ref:
|
|
108
|
+
return {"score": 0, "note": "无真 PRD 范本"}
|
|
109
|
+
p = ("你是 ICS(车辆物联网系统)资深产品专家。对照【真实 ICS PRD 范本】,给【生成 PRD】业务保真度打分 1-5:\n"
|
|
110
|
+
"5=完全 ICS 业务写法(系统级/字段级/真业务流程);3=部分;1=通用产品宣传文。\n"
|
|
111
|
+
f"【真 ICS PRD 范本】:\n{ref}\n\n【待评 PRD】:\n{prd_text[:1000]}\n\n只输出一个整数(1-5)。")
|
|
112
|
+
out = chat(JUDGE_MODEL, p)
|
|
113
|
+
m = re.search(r"[1-5]", out)
|
|
114
|
+
return {"score": int(m.group()) if m else 0, "ref_len": len(ref)}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ── BF3/4: 原型保真 ──
|
|
118
|
+
def _design():
|
|
119
|
+
r = _hub().dispatch("get_design_system", {}) or {}
|
|
120
|
+
proj = next((p for p in r.get("projects", []) if p.get("project") == "fywl-ui"), {})
|
|
121
|
+
colors = proj.get("colors", {}) or {}
|
|
122
|
+
palette = set()
|
|
123
|
+
for v in colors.values():
|
|
124
|
+
if isinstance(v, str) and v.startswith("hsl"):
|
|
125
|
+
m = re.match(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", v)
|
|
126
|
+
if m:
|
|
127
|
+
h, s_, l = float(m.group(1)) / 360, float(m.group(2)) / 100, float(m.group(3)) / 100
|
|
128
|
+
r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
|
|
129
|
+
palette.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
|
|
130
|
+
comps = set()
|
|
131
|
+
for d in (proj.get("commonComponents", {}), proj.get("formComponents", {})):
|
|
132
|
+
if isinstance(d, dict):
|
|
133
|
+
comps.update(k.lower() for k in d)
|
|
134
|
+
return palette, comps
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _extract_colors(html):
|
|
138
|
+
out = set()
|
|
139
|
+
for m in re.findall(r"#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b", html):
|
|
140
|
+
h = m if len(m) == 6 else m * 2
|
|
141
|
+
out.add((int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)))
|
|
142
|
+
for m in re.findall(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", html):
|
|
143
|
+
h, s_, l = float(m[0]) / 360, float(m[1]) / 100, float(m[2]) / 100
|
|
144
|
+
r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
|
|
145
|
+
out.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
|
|
146
|
+
for m in re.findall(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", html):
|
|
147
|
+
out.add((int(m[0]), int(m[1]), int(m[2])))
|
|
148
|
+
return out
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _color_real(c, palette):
|
|
152
|
+
if c in _NEUTRAL:
|
|
153
|
+
return True
|
|
154
|
+
return any(all(abs(a - b) <= 40 for a, b in zip(c, p)) for p in palette)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def bf34_proto(html):
|
|
158
|
+
palette, comps = _design()
|
|
159
|
+
cols = _extract_colors(html)
|
|
160
|
+
color_score = (sum(1 for c in cols if _color_real(c, palette)) / len(cols)) if cols else 0.0
|
|
161
|
+
used = set(m.lower() for m in re.findall(r"<(?:el|van|a|n)-([\w-]+)", html))
|
|
162
|
+
comp_score = (sum(1 for c in used if c in comps) / len(used)) if used and comps else (1.0 if not comps else 0.0)
|
|
163
|
+
return {"bf3_component": round(comp_score, 3), "bf4_color": round(color_score, 3),
|
|
164
|
+
"colors": len(cols), "components": len(used)}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ── BF5/6: feature 实体精准/相关率 ──
|
|
168
|
+
def _feature_gold(feature):
|
|
169
|
+
s = SessionLocal()
|
|
170
|
+
rows = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
|
|
171
|
+
"AND type IN ('CONTROLLER','SERVICE','TABLE','COLUMN','FUNCTION') LIMIT 30", {"p": f"%{feature}%"})
|
|
172
|
+
s.close()
|
|
173
|
+
return set(c for c, _ in rows if c)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def bf56_entities(text, feature):
|
|
177
|
+
gold = _feature_gold(feature)
|
|
178
|
+
cands = set(re.findall(r"[A-Za-z_][\w]{2,}", text))
|
|
179
|
+
if not cands:
|
|
180
|
+
return {"score": 0.0, "candidates": 0, "gold": len(gold)}
|
|
181
|
+
hit = sum(1 for c in cands if c in gold)
|
|
182
|
+
return {"score": round(hit / len(cands), 3), "candidates": len(cands), "gold_hit": hit, "gold": len(gold)}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def main():
|
|
186
|
+
if len(sys.argv) < 3:
|
|
187
|
+
sys.stderr.write(__doc__)
|
|
188
|
+
return 1
|
|
189
|
+
kind, path = sys.argv[1], sys.argv[2]
|
|
190
|
+
feature = None
|
|
191
|
+
if "--feature" in sys.argv:
|
|
192
|
+
feature = sys.argv[sys.argv.index("--feature") + 1]
|
|
193
|
+
text = _read(path)
|
|
194
|
+
if not text:
|
|
195
|
+
sys.stderr.write("读不到文件: %s\n" % path)
|
|
196
|
+
return 1
|
|
197
|
+
out = {"file": os.path.basename(path), "kind": kind}
|
|
198
|
+
if kind == "prd":
|
|
199
|
+
out["BF1_schema"] = bf1_schema(text)
|
|
200
|
+
if feature:
|
|
201
|
+
out["BF2_content"] = bf2_content(text, feature)
|
|
202
|
+
elif kind == "proto":
|
|
203
|
+
out.update(bf34_proto(text))
|
|
204
|
+
elif kind in ("code", "search"):
|
|
205
|
+
if not feature:
|
|
206
|
+
sys.stderr.write("code/search 需 --feature\n")
|
|
207
|
+
return 1
|
|
208
|
+
bf = bf56_entities(text, feature)
|
|
209
|
+
out["BF5_code" if kind == "code" else "BF6_search"] = bf
|
|
210
|
+
else:
|
|
211
|
+
sys.stderr.write("未知 kind: %s (prd/proto/code/search)\n" % kind)
|
|
212
|
+
return 1
|
|
213
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
214
|
+
return 0
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
sys.exit(main())
|