@hupan56/wlkj 3.3.14 → 3.3.16
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 +16 -1
- package/package.json +1 -1
- package/templates/qoder/commands/optional/wl-spec.md +4 -2
- package/templates/qoder/commands/wl-commit.md +3 -1
- package/templates/qoder/commands/wl-init.md +2 -2
- package/templates/qoder/commands/wl-prd.md +24 -0
- package/templates/qoder/commands/wl-search.md +18 -11
- package/templates/qoder/commands/wl-task.md +3 -1
- package/templates/qoder/config.yaml +0 -6
- package/templates/qoder/contracts/insight.md +55 -0
- package/templates/qoder/hooks/pre-tool-use-commit.py +124 -0
- package/templates/qoder/hooks/session-start.py +20 -1
- package/templates/qoder/hooks/stop-eval.py +129 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +9 -1
- package/templates/qoder/scripts/capability/registry.py +0 -3
- package/templates/qoder/scripts/deployment/setup/init_doctor.py +6 -2
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +7 -7
- package/templates/qoder/scripts/deployment/setup/setup.py +5 -2
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +41 -5
- package/templates/qoder/scripts/domain/kg/kg.py +14 -0
- package/templates/qoder/scripts/foundation/io/context_cache.py +94 -0
- package/templates/qoder/scripts/tool_guide.md +70 -0
- package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -0
- package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -0
- package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -0
- package/templates/qoder/scripts/validation/eval/bf_score.py +218 -0
- package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -0
- package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -0
- package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -0
- package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -0
- package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -0
- package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -0
- package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -0
- package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -0
- package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -0
- package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -0
- package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -0
- package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -0
- package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -0
- package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -0
- package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -0
- package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -0
- package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -0
- package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -0
- package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -0
- package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -0
- package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -0
- package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
- package/templates/qoder/agents/design-agent.md +0 -20
- package/templates/qoder/agents/spec-generator.md +0 -21
- package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +0 -235
- package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +0 -238
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +0 -197
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""metrics_dashboard.py - T1.7 通用维度度量汇总(token/反复搜/幻觉/完成率 → §9.1 目标进度)。
|
|
4
|
+
|
|
5
|
+
聚合 T1.0 gradient_matrix + T1.1 dispatcher_ab 的真实测量数据,输出 §9.1 达标进度表。
|
|
6
|
+
诚实标注:哪些是 API 代理已测(token/幻觉/召回/路由),哪些需宿主真跑(13命令×5任务全表/真 tool_call 计数)。
|
|
7
|
+
|
|
8
|
+
跑法:
|
|
9
|
+
python scripts/validation/eval/metrics_dashboard.py
|
|
10
|
+
python scripts/validation/eval/metrics_dashboard.py --json
|
|
11
|
+
"""
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load(name):
|
|
20
|
+
p = os.path.join(_HERE, name)
|
|
21
|
+
try:
|
|
22
|
+
with open(p, encoding="utf-8") as f:
|
|
23
|
+
return json.load(f)
|
|
24
|
+
except Exception:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build():
|
|
29
|
+
grad = _load("gradient_matrix_baseline_2026-07-21.json")
|
|
30
|
+
disp = _load("dispatcher_ab_2026-07-21.json")
|
|
31
|
+
rows = []
|
|
32
|
+
notes = []
|
|
33
|
+
|
|
34
|
+
if grad:
|
|
35
|
+
m = grad["matrix"]
|
|
36
|
+
# §9.1 幻觉率 <5%:+grounding 后三档都达标;裸跑 ~98% 反证 grounding 必要
|
|
37
|
+
for tier in ("weak", "mid", "strong"):
|
|
38
|
+
h_scaffold = m[f"{tier}/scaffold"]["H"]
|
|
39
|
+
ok = h_scaffold < 0.05
|
|
40
|
+
rows.append(("幻觉率<5%% (+grounding, %s)" % tier, "%.1f%%" % (h_scaffold * 100),
|
|
41
|
+
"✓" if ok else "✗", "代理测(wl-search类)"))
|
|
42
|
+
# §9.1 低模型梯度:weak+工作流 ≥ strong裸-10pp(精度口径)
|
|
43
|
+
weak_scaffold_p = m["weak/scaffold"]["P"]
|
|
44
|
+
strong_bare_p = m["strong/bare"]["P"]
|
|
45
|
+
diff_pp = (weak_scaffold_p - strong_bare_p) * 100
|
|
46
|
+
rows.append(("低模型梯度: weak+grounding P vs strong+bare P", "%+.1fpp" % diff_pp,
|
|
47
|
+
"✓" if diff_pp >= -10 else "✗", "代理测(卖点铁证 +92.9pp)"))
|
|
48
|
+
# §9.1 各命令完成率 ≥85%:scaffold 召回 R 作近似
|
|
49
|
+
for tier in ("weak", "mid", "strong"):
|
|
50
|
+
r = m[f"{tier}/scaffold"]["R"]
|
|
51
|
+
ok = r >= 0.85
|
|
52
|
+
rows.append(("完成率≥85%% (召回R, %s+scaffold)" % tier, "%.1f%%" % (r * 100),
|
|
53
|
+
"✓" if ok else "✗(短板~80%)", "代理测; 飞轮杠杆=检索升级"))
|
|
54
|
+
notes.append("grounding 让三档幻觉从 ~98%(裸) 降到 <4%(+grounding);召回天花板 ~80% 是飞轮下轮短板(检索升级)。")
|
|
55
|
+
else:
|
|
56
|
+
rows.append(("gradient_matrix 基线", "缺", "?", "先跑 gradient_matrix.py"))
|
|
57
|
+
notes.append("gradient_matrix_baseline_2026-07-21.json 未找到。")
|
|
58
|
+
|
|
59
|
+
if disp:
|
|
60
|
+
dm = disp["matrix"]
|
|
61
|
+
# T1.1 dispatcher: 新路由准确率(弱模型)
|
|
62
|
+
weak_new = dm["weak"]["new_acc"]
|
|
63
|
+
rows.append(("dispatcher 路由准确率 (weak+新表)", "%.1f%%" % (weak_new * 100),
|
|
64
|
+
"✓" if weak_new >= 0.95 else "✗", "代理测(T1.1 +14.3pp→100%)"))
|
|
65
|
+
notes.append("确定性路由表让弱模型选对通道 85.7%→100%(旧'判断问题类型'散文的 2 个系统性误差被修)。")
|
|
66
|
+
else:
|
|
67
|
+
rows.append(("dispatcher_ab 基线", "缺", "?", "先跑 dispatcher_ab.py"))
|
|
68
|
+
|
|
69
|
+
# §9.1 token ≤裸×0.8 / tool_call ≤裸×0.7:命令层维度,需宿主真 transcript
|
|
70
|
+
rows.append(("token +工作流≤裸×0.8", "待测", "🖥️", "宿主真命令 transcript (T1.2瘦身/T1.3缓存降)"))
|
|
71
|
+
rows.append(("反复搜 tool_call +工作流≤裸×0.7", "待测", "🖥️", "宿主 transcript 计 MCP 调用数"))
|
|
72
|
+
rows.append(("13命令×≥5任务全表", "待测", "🖥️", "宿主 qwork_harness 跑全命令矩阵"))
|
|
73
|
+
notes.append("token/tool_call/13命令全表 = B类宿主真跑(qwork_harness.py 已备),代理测不了真 agent-loop tool_call 计数。")
|
|
74
|
+
|
|
75
|
+
return {"rows": rows, "notes": notes,
|
|
76
|
+
"targets_source": "SPEC §9.1",
|
|
77
|
+
"measured_by_proxy": "幻觉率/召回/低模型梯度/dispatcher准确率(gradient_matrix+dispatcher_ab)",
|
|
78
|
+
"needs_host": "token节省/tool_call计数/13命令全表(qwork_harness 真跑)"}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def format_table(r):
|
|
82
|
+
lines = ["## T1.7 度量汇总 — §9.1 目标进度", "",
|
|
83
|
+
"| 指标 | 实测 | 达标 | 来源 |", "|---|---|---|---|"]
|
|
84
|
+
for name, val, ok, src in r["rows"]:
|
|
85
|
+
lines.append("| %s | %s | %s | %s |" % (name, val, ok, src))
|
|
86
|
+
lines.append("")
|
|
87
|
+
lines.append("**洞察**:")
|
|
88
|
+
for n in r["notes"]:
|
|
89
|
+
lines.append("- " + n)
|
|
90
|
+
lines.append("")
|
|
91
|
+
lines.append("**代理已测**: " + r["measured_by_proxy"])
|
|
92
|
+
lines.append("**需宿主**: " + r["needs_host"])
|
|
93
|
+
return "\n".join(lines)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main():
|
|
97
|
+
r = build()
|
|
98
|
+
if "--json" in sys.argv:
|
|
99
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
100
|
+
else:
|
|
101
|
+
print(format_table(r))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
main()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""multi_turn_flywheel.py — FW8 多轮飞轮(§1.2 ⑩ 跨轮上下文复用/准确性)。
|
|
2
|
+
|
|
3
|
+
多轮场景:turn1「列出 X 相关实体」→ turn2「从上面里选 Controller」。量 turn2 是否正确
|
|
4
|
+
【复用 turn1 结果】(非重查/非编造)。bare(turn2 无 turn1 上下文,盲猜) vs +turn1上下文(复用)。
|
|
5
|
+
准确 = turn2 选的 Controller ∈ turn1 真实 Controller 集。
|
|
6
|
+
|
|
7
|
+
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/multi_turn_flywheel.py
|
|
8
|
+
"""
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
import json
|
|
13
|
+
import time
|
|
14
|
+
import urllib.request
|
|
15
|
+
import urllib.error
|
|
16
|
+
|
|
17
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
18
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
19
|
+
sys.path.insert(0, _p)
|
|
20
|
+
|
|
21
|
+
from sqlalchemy import text # noqa: E402
|
|
22
|
+
from app.db import SessionLocal # noqa: E402
|
|
23
|
+
|
|
24
|
+
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
25
|
+
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
26
|
+
MODEL = "qwen-turbo"
|
|
27
|
+
THEMES = ["考勤", "保险", "车辆"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def chat(messages, max_tokens=400, timeout=80):
|
|
31
|
+
body = json.dumps({"model": MODEL, "messages": messages,
|
|
32
|
+
"temperature": 0.0, "max_tokens": max_tokens}).encode()
|
|
33
|
+
for a in range(3):
|
|
34
|
+
try:
|
|
35
|
+
req = urllib.request.Request(CHAT_URL, data=body,
|
|
36
|
+
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
37
|
+
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
38
|
+
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
39
|
+
if a < 2: time.sleep(1.0*(a+1))
|
|
40
|
+
else: return f"[ERR {repr(e)[:50]}]"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _q(s, sql, p=None):
|
|
44
|
+
try: return s.execute(text(sql), p or {}).fetchall()
|
|
45
|
+
except Exception:
|
|
46
|
+
try: s.rollback()
|
|
47
|
+
except Exception: pass
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def turn1_entities(theme, k=20):
|
|
52
|
+
"""turn1 检索到的真实实体(模拟工作流 turn1 结果)。返 [(canonical,cn,type)]。"""
|
|
53
|
+
s = SessionLocal()
|
|
54
|
+
rows = _q(s, "SELECT canonical, cn, type FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
|
|
55
|
+
"AND type IN ('CONTROLLER','SERVICE','FUNCTION','TABLE') LIMIT :n", {"p": f"%{theme}%", "n": k})
|
|
56
|
+
s.close()
|
|
57
|
+
return [(c, n, t) for c, n, t in rows if c]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def filter_accuracy(ans, gold_controllers):
|
|
61
|
+
"""turn2 答案是否正确选出 turn1 的 Controller。返 (命中gold数, 编造数)。"""
|
|
62
|
+
gold_names = {c for c, _, _ in gold_controllers}
|
|
63
|
+
hit = sum(1 for c in gold_names if c in ans)
|
|
64
|
+
# 编造 = ans 里的 CamelCase 标识符不在 turn1 全集
|
|
65
|
+
return hit, len(gold_names)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run():
|
|
69
|
+
rep = {"themes": []}
|
|
70
|
+
for th in THEMES:
|
|
71
|
+
ents = turn1_entities(th, 20)
|
|
72
|
+
controllers = [e for e in ents if e[2] == "CONTROLLER"]
|
|
73
|
+
turn1_list = "\n".join("%s (%s) [%s]" % (c, n, t) for c, n, t in ents)
|
|
74
|
+
turn2_q = "从上面列出的实体里,选出所有类型是 Controller 的,逐行列出名称。"
|
|
75
|
+
|
|
76
|
+
# bare: turn2 无 turn1 上下文(盲猜,会重查/编造)
|
|
77
|
+
bare = chat([{"role": "user", "content": f"列出 ICS 系统「{th}」相关的 Controller 类名。"}])
|
|
78
|
+
# scaffold: 多轮——turn1 结果作 assistant 上下文,turn2 复用
|
|
79
|
+
scaff = chat([
|
|
80
|
+
{"role": "user", "content": f"列出 ICS 系统「{th}」相关的实体。"},
|
|
81
|
+
{"role": "assistant", "content": "相关实体:\n" + turn1_list},
|
|
82
|
+
{"role": "user", "content": turn2_q},
|
|
83
|
+
])
|
|
84
|
+
b_hit, gold_n = filter_accuracy(bare, controllers)
|
|
85
|
+
s_hit, _ = filter_accuracy(scaff, controllers)
|
|
86
|
+
bR = round(b_hit / max(gold_n, 1), 3)
|
|
87
|
+
sR = round(s_hit / max(gold_n, 1), 3)
|
|
88
|
+
rep["themes"].append({"theme": th, "turn1_n": len(ents), "controller_n": gold_n,
|
|
89
|
+
"bare_recall": bR, "scaffold_recall": sR})
|
|
90
|
+
print(" %s: turn1实体=%d(其中Controller=%d) | bare命中Controller=%.2f | +多轮复用=%.2f" % (
|
|
91
|
+
th, len(ents), gold_n, bR, sR))
|
|
92
|
+
n = len(THEMES) or 1
|
|
93
|
+
rep["agg"] = {"bare_recall": round(sum(t["bare_recall"] for t in rep["themes"])/n, 3),
|
|
94
|
+
"scaffold_recall": round(sum(t["scaffold_recall"] for t in rep["themes"])/n, 3)}
|
|
95
|
+
return rep
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def main():
|
|
99
|
+
import argparse
|
|
100
|
+
ap = argparse.ArgumentParser()
|
|
101
|
+
ap.add_argument("--json", action="store_true")
|
|
102
|
+
args = ap.parse_args()
|
|
103
|
+
r = run()
|
|
104
|
+
a = r["agg"]
|
|
105
|
+
if args.json:
|
|
106
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
107
|
+
return
|
|
108
|
+
print("\n## FW8 多轮飞轮(turn2 复用 turn1 上下文,选 Controller 召回)")
|
|
109
|
+
print("| 模式 | Controller 召回 R |")
|
|
110
|
+
print("|---|---|")
|
|
111
|
+
print("| bare(turn2 盲猜,无 turn1) | %.1f%% |" % (a["bare_recall"]*100))
|
|
112
|
+
print("| +多轮(turn1 上下文复用) | %.1f%% |" % (a["scaffold_recall"]*100))
|
|
113
|
+
met = a["scaffold_recall"] >= 0.85
|
|
114
|
+
print("\n多轮达标(≥85%%)?" , "✓" if met else "✗ → 续转(更强 turn2 复用指令)")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""prd_fidelity_flywheel.py — FW9 PRD 业务保真度飞轮(vs 真实 ICS schema,非结构代理)。
|
|
2
|
+
|
|
3
|
+
用户根因:之前 FW2 量的是"6 章齐"(结构),不是"PRD 是不是这个业务的"(保真)。
|
|
4
|
+
本尺子量 **schema 保真**:PRD 引用的表/列是不是 ICS 真实业务表列(db_tables/db_columns),
|
|
5
|
+
而非泛泛/编造。bare(无 schema grounding→编造表名) vs +真schema grounding(喂真表真列)。
|
|
6
|
+
保真率 = PRD 提的表/列 ∈ 真实 schema 的比例;召回 = 该功能真表被提的比例。
|
|
7
|
+
|
|
8
|
+
(原型颜色保真=eval_prd A2 读宿主真色板,云 KG 无设计系统表→那格 🖥️ 宿主;本尺子量可代理的 schema 保真)
|
|
9
|
+
|
|
10
|
+
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/prd_fidelity_flywheel.py
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
import urllib.request
|
|
18
|
+
import urllib.error
|
|
19
|
+
|
|
20
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
21
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
22
|
+
sys.path.insert(0, _p)
|
|
23
|
+
|
|
24
|
+
from sqlalchemy import text # noqa: E402
|
|
25
|
+
from app.db import SessionLocal # noqa: E402
|
|
26
|
+
|
|
27
|
+
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
28
|
+
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
29
|
+
MODEL = "qwen-turbo"
|
|
30
|
+
FEATURES = [("车辆保险管理", "保险"), ("异常记录管理", "异常"), ("合同维护", "合同")]
|
|
31
|
+
# 表名 token(t_xxx / tb_xxx / report_xxx 等)
|
|
32
|
+
_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)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def chat(prompt, max_tokens=900, timeout=90):
|
|
36
|
+
body = json.dumps({"model": MODEL, "messages": [{"role": "user", "content": prompt}],
|
|
37
|
+
"temperature": 0.2, "max_tokens": max_tokens}).encode()
|
|
38
|
+
for a in range(3):
|
|
39
|
+
try:
|
|
40
|
+
req = urllib.request.Request(CHAT_URL, data=body,
|
|
41
|
+
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
42
|
+
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
43
|
+
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
44
|
+
if a < 2: time.sleep(1.0*(a+1))
|
|
45
|
+
else: return f"[ERR {repr(e)[:50]}]"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _q(s, sql, p=None):
|
|
49
|
+
try: return s.execute(text(sql), p or {}).fetchall()
|
|
50
|
+
except Exception:
|
|
51
|
+
try: s.rollback()
|
|
52
|
+
except Exception: pass
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def real_schema(theme):
|
|
57
|
+
"""该功能的真实业务表(db_tables) + 列(db_columns) = 业务保真 gold。"""
|
|
58
|
+
s = SessionLocal()
|
|
59
|
+
tabs = [r[0] for r in _q(s, "SELECT table_name FROM db_tables WHERE table_name ILIKE :p OR table_comment ILIKE :p LIMIT 15", {"p": f"%{theme}%"})]
|
|
60
|
+
cols = [r[0] for r in _q(s, "SELECT column_name FROM db_columns WHERE column_name ILIKE :p OR column_comment ILIKE :p LIMIT 15", {"p": f"%{theme}%"})]
|
|
61
|
+
all_tabs = set(r[0] for r in _q(s, "SELECT table_name FROM db_tables")) # 全量真表(保真校验集)
|
|
62
|
+
s.close()
|
|
63
|
+
return tabs, cols, all_tabs
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def extract_tables(prd):
|
|
67
|
+
return set(m.lower() for m in _TABLE_RE.findall(prd))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def fidelity(prd, gold_tabs, all_tabs):
|
|
71
|
+
"""保真率 = PRD 提的表 ∈ 真实 schema 的比例;召回 = gold 表被提的比例。"""
|
|
72
|
+
mentioned = extract_tables(prd)
|
|
73
|
+
if not mentioned:
|
|
74
|
+
return 0.0, 0.0, 0
|
|
75
|
+
real_mentioned = mentioned & all_tabs
|
|
76
|
+
precision = len(real_mentioned) / len(mentioned) # 保真:提的表是真的比例
|
|
77
|
+
recall = len(mentioned & set(t.lower() for t in gold_tabs)) / max(len(gold_tabs), 1) # 召回:gold 表被提
|
|
78
|
+
return round(precision, 3), round(recall, 3), len(mentioned)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def run():
|
|
82
|
+
rep = {"features": []}
|
|
83
|
+
for feat, theme in FEATURES:
|
|
84
|
+
gtabs, gcols, all_tabs = real_schema(theme)
|
|
85
|
+
schema_ctx = "真实业务表:\n" + "\n".join(gtabs[:12]) + "\n真实业务列:\n" + ", ".join(gcols[:12])
|
|
86
|
+
bare = chat(f"你是产品经理。为 ICS 系统「{feat}」写 PRD(含字段设计/数据表)。直接输出正文。")
|
|
87
|
+
scaff = chat(f"你是 ICS(车辆物联网)产品经理。为「{feat}」写 PRD(含字段设计/数据表)。\n"
|
|
88
|
+
f"【业务 schema 接地】以下是该功能的真实业务表/列(均为真实存在):\n{schema_ctx}\n"
|
|
89
|
+
f"铁律:字段设计/数据表章节【必须把上面列出的真实业务表全部引用】,用真实表名,勿编造/勿省略。"
|
|
90
|
+
f"直接输出 PRD 正文。")
|
|
91
|
+
bP, bR, bN = fidelity(bare, gtabs, all_tabs)
|
|
92
|
+
sP, sR, sN = fidelity(scaff, gtabs, all_tabs)
|
|
93
|
+
rep["features"].append({"feature": feat, "gold_tabs": len(gtabs),
|
|
94
|
+
"bare_fidelity": bP, "bare_recall": bR, "bare_mentioned": bN,
|
|
95
|
+
"scaffold_fidelity": sP, "scaffold_recall": sR, "scaffold_mentioned": sN})
|
|
96
|
+
print(" %s: gold表=%d | bare 保真=%.2f 召回=%.2f(提%d) | scaffold 保真=%.2f 召回=%.2f(提%d)" % (
|
|
97
|
+
feat, len(gtabs), bP, bR, bN, sP, sR, sN))
|
|
98
|
+
n = len(FEATURES) or 1
|
|
99
|
+
rep["agg"] = {
|
|
100
|
+
"bare_fidelity": round(sum(f["bare_fidelity"] for f in rep["features"])/n, 3),
|
|
101
|
+
"scaffold_fidelity": round(sum(f["scaffold_fidelity"] for f in rep["features"])/n, 3),
|
|
102
|
+
"bare_recall": round(sum(f["bare_recall"] for f in rep["features"])/n, 3),
|
|
103
|
+
"scaffold_recall": round(sum(f["scaffold_recall"] for f in rep["features"])/n, 3),
|
|
104
|
+
}
|
|
105
|
+
return rep
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def main():
|
|
109
|
+
import argparse
|
|
110
|
+
ap = argparse.ArgumentParser()
|
|
111
|
+
ap.add_argument("--json", action="store_true")
|
|
112
|
+
args = ap.parse_args()
|
|
113
|
+
r = run()
|
|
114
|
+
a = r["agg"]
|
|
115
|
+
if args.json:
|
|
116
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
117
|
+
return
|
|
118
|
+
print("\n## FW9 PRD 业务保真度飞轮(PRD 提的表 vs 真实 ICS schema)")
|
|
119
|
+
print("| 维度 | bare(无schema接地) | +真schema接地 |")
|
|
120
|
+
print("|---|---|---|")
|
|
121
|
+
print("| 保真率(提的表是真的) | %.0f%% | %.0f%% |" % (a["bare_fidelity"]*100, a["scaffold_fidelity"]*100))
|
|
122
|
+
print("| 召回(gold真表被提) | %.0f%% | %.0f%% |" % (a["bare_recall"]*100, a["scaffold_recall"]*100))
|
|
123
|
+
print("\n业务保真=PRD 引用的是 ICS 真实业务表(t_insurance_log 等),非编造/泛泛。"
|
|
124
|
+
"bare 编造表名→保真低;scaffold 接真 schema→保真高。")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
main()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""prd_flywheel.py — FW2 wl-prd 生成类质量飞轮(完整性/准确性 → 目标)。
|
|
2
|
+
|
|
3
|
+
生成类维度(SPEC §1.1):完整性(必要段数) / 准确性(gold 对齐) / 可用性(EVA≥80%)。
|
|
4
|
+
飞轮:弱模型(bare prompt) vs +wl-prd scaffold(6章强制 + KG grounding) → 量
|
|
5
|
+
- 完整性 = 6 章命中 / 6(QUICK_SECTIONS,纯 regex)
|
|
6
|
+
- 现实锚定 = PRD 里出现的【KG 真实体名】数(bare 编造→少;scaffold 接地→多)
|
|
7
|
+
→ 改 scaffold(强制章 + 接地)→ 重量 → 直到 完整性=6/6 + 锚定≥阈值。
|
|
8
|
+
|
|
9
|
+
云端跑(DashScope 生 + PG 真实体)。eval_prd.py 是宿主 DuckDB 侧,本尺子是其云端同构代理。
|
|
10
|
+
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/prd_flywheel.py
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
import urllib.request
|
|
18
|
+
import urllib.error
|
|
19
|
+
|
|
20
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
21
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
22
|
+
sys.path.insert(0, _p)
|
|
23
|
+
|
|
24
|
+
from sqlalchemy import text # noqa: E402
|
|
25
|
+
from app.db import SessionLocal # noqa: E402
|
|
26
|
+
|
|
27
|
+
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
28
|
+
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
29
|
+
MODEL = "qwen-turbo" # 弱模型(卖点:弱模型+工作流)
|
|
30
|
+
QUICK_SECTIONS = ["功能入口", "需求背景", "需求说明", "影响范围", "验收标准", "不在本次范围"]
|
|
31
|
+
FEATURES = ["车辆保养提醒", "考勤异常导出", "保险续保预警"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def chat(prompt, max_tokens=900, timeout=90):
|
|
35
|
+
body = json.dumps({"model": MODEL,
|
|
36
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
37
|
+
"temperature": 0.2, "max_tokens": max_tokens}).encode()
|
|
38
|
+
for attempt in range(3):
|
|
39
|
+
try:
|
|
40
|
+
req = urllib.request.Request(CHAT_URL, data=body,
|
|
41
|
+
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
42
|
+
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
43
|
+
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
44
|
+
if attempt < 2:
|
|
45
|
+
time.sleep(1.0 * (attempt + 1))
|
|
46
|
+
else:
|
|
47
|
+
return f"[ERR {repr(e)[:60]}]"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _q(s, sql, params=None):
|
|
51
|
+
try:
|
|
52
|
+
return s.execute(text(sql), params or {}).fetchall()
|
|
53
|
+
except Exception:
|
|
54
|
+
try:
|
|
55
|
+
s.rollback()
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def real_entities(theme, k=15):
|
|
62
|
+
"""KG 里该主题的真实实体 (canonical, cn)(现实锚定 gold)。"""
|
|
63
|
+
s = SessionLocal()
|
|
64
|
+
rows = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
|
|
65
|
+
"AND canonical IS NOT NULL LIMIT :n", {"p": f"%{theme}%", "n": k})
|
|
66
|
+
s.close()
|
|
67
|
+
return [(r[0], r[1]) for r in rows if r[0]]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def completeness(prd_text):
|
|
71
|
+
"""6 章命中数(regex,接受 #/##/粗体/裸文本)。"""
|
|
72
|
+
hit = 0
|
|
73
|
+
for sec in QUICK_SECTIONS:
|
|
74
|
+
if re.search(r'#{0,3}\s*\*{0,2}' + sec, prd_text):
|
|
75
|
+
hit += 1
|
|
76
|
+
return hit
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def reality_anchor(prd_text, reals):
|
|
80
|
+
"""PRD 里出现的 KG 真实体数(canonical 或 cn 命中任一即算;cn 中文名更易在散文出现)。"""
|
|
81
|
+
n = 0
|
|
82
|
+
for canon, cn in reals:
|
|
83
|
+
if (canon and canon in prd_text) or (cn and cn in prd_text):
|
|
84
|
+
n += 1
|
|
85
|
+
return n
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def gen_bare(feature):
|
|
89
|
+
return (f"你是产品经理。为「{feature}」功能写一份 PRD 需求文档。\n"
|
|
90
|
+
f"直接输出 PRD 正文。")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def gen_scaffold(feature, grounding):
|
|
94
|
+
return (f"你是车辆物联网系统(ICS)的产品经理。为「{feature}」写一份 PRD。\n"
|
|
95
|
+
f"知识图谱检索到该功能相关的真实系统对象(字段/类/接口,均为真实存在):\n{grounding}\n\n"
|
|
96
|
+
f"**铁律**:PRD 必须【完整含以下 6 章,每章一个 ## 标题,缺一不可】:\n"
|
|
97
|
+
f"## 功能入口\n## 需求背景\n## 需求说明\n## 影响范围\n## 验收标准\n## 不在本次范围\n"
|
|
98
|
+
f"PRD 里的字段名/接口名【必须用上面检索到的真实名称】,不要编造。直接输出 PRD 正文。")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def run():
|
|
102
|
+
rep = {"model": MODEL, "features": []}
|
|
103
|
+
for feat in FEATURES:
|
|
104
|
+
reals = real_entities(feat.split("提醒")[0].split("导出")[0].split("预警")[0] or feat, 15)
|
|
105
|
+
ctx = "\n".join("%s (%s)" % (c, n) for c, n in reals[:15]) if reals else "(无检索结果)"
|
|
106
|
+
bare = chat(gen_bare(feat))
|
|
107
|
+
scaff = chat(gen_scaffold(feat, ctx))
|
|
108
|
+
b_cmp = completeness(bare)
|
|
109
|
+
s_cmp = completeness(scaff)
|
|
110
|
+
b_real = reality_anchor(bare, reals)
|
|
111
|
+
s_real = reality_anchor(scaff, reals)
|
|
112
|
+
rep["features"].append({"feature": feat, "real_n": len(reals),
|
|
113
|
+
"bare_completeness": b_cmp, "scaffold_completeness": s_cmp,
|
|
114
|
+
"bare_reality": b_real, "scaffold_reality": s_real})
|
|
115
|
+
print(" %s: 完整性 bare=%d/6 scaff=%d/6 | 锚定 bare=%d scaff=%d (gold=%d)" % (
|
|
116
|
+
feat, b_cmp, s_cmp, b_real, s_real, len(reals)))
|
|
117
|
+
n = len(FEATURES) or 1
|
|
118
|
+
rep["agg"] = {
|
|
119
|
+
"bare_completeness": round(sum(f["bare_completeness"] for f in rep["features"]) / n, 2),
|
|
120
|
+
"scaffold_completeness": round(sum(f["scaffold_completeness"] for f in rep["features"]) / n, 2),
|
|
121
|
+
"bare_reality": round(sum(f["bare_reality"] for f in rep["features"]) / n, 2),
|
|
122
|
+
"scaffold_reality": round(sum(f["scaffold_reality"] for f in rep["features"]) / n, 2),
|
|
123
|
+
}
|
|
124
|
+
return rep
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def main():
|
|
128
|
+
import argparse
|
|
129
|
+
ap = argparse.ArgumentParser()
|
|
130
|
+
ap.add_argument("--json", action="store_true")
|
|
131
|
+
args = ap.parse_args()
|
|
132
|
+
r = run()
|
|
133
|
+
a = r["agg"]
|
|
134
|
+
if args.json:
|
|
135
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
136
|
+
return
|
|
137
|
+
print("\n## FW2 wl-prd 生成类飞轮(完整性 6/6 + 现实锚定)")
|
|
138
|
+
print("| 维度 | bare(弱模型裸) | +wl-prd scaffold | 目标 |")
|
|
139
|
+
print("|---|---|---|---|")
|
|
140
|
+
print("| 完整性(章/6) | %.2f | %.2f | 6.0 |" % (a["bare_completeness"], a["scaffold_completeness"]))
|
|
141
|
+
print("| 现实锚定(真实体命中) | %.2f | %.2f | 高 |" % (a["bare_reality"], a["scaffold_reality"]))
|
|
142
|
+
cmp_met = a["scaffold_completeness"] >= 6.0
|
|
143
|
+
print("\n完整性达标(≥6/6)?" , "✓" if cmp_met else "✗ → 下轮:强章模板/Stop hook 拦缺章")
|
|
144
|
+
print("锚定提升:", "%+.2f" % (a["scaffold_reality"] - a["bare_reality"]), "(scaffold 接地 vs bare 编造)")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
main()
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""prototype_fidelity_flywheel.py — FW9 原型保真度飞轮(vs 真实 fywl-ui 设计系统)。
|
|
2
|
+
|
|
3
|
+
用户根因例子:"原型保真不保真"。本尺子量:生成原型的颜色/组件是不是 fywl-ui 真设计系统的
|
|
4
|
+
(get_design_system 返回的真色板 hsl(214,86%,59%)/真组件/真layout),而非泛泛 #409eff 蓝或编造组件。
|
|
5
|
+
bare(无设计系统接地→通用色/编造组件) vs +真设计系统接地(用真 hsl 色板/真组件)。
|
|
6
|
+
color保真率 = 原型颜色∈真色板比例;component保真率 = 真组件占比。
|
|
7
|
+
|
|
8
|
+
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/prototype_fidelity_flywheel.py
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
import json
|
|
14
|
+
import colorsys
|
|
15
|
+
import time
|
|
16
|
+
import urllib.request
|
|
17
|
+
import urllib.error
|
|
18
|
+
|
|
19
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
20
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
21
|
+
sys.path.insert(0, _p)
|
|
22
|
+
|
|
23
|
+
from app.routers import mcp_handlers as H # noqa: E402
|
|
24
|
+
try:
|
|
25
|
+
H._init_handlers()
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
30
|
+
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
31
|
+
MODEL = "qwen-turbo"
|
|
32
|
+
PAGES = ["车辆保险列表页", "异常记录表单页", "合同维护详情页"]
|
|
33
|
+
_NEUTRAL = {(255, 255, 255), (0, 0, 0), (51, 51, 51), (102, 102, 102), (153, 153, 153),
|
|
34
|
+
(240, 240, 240), (250, 250, 250), (217, 217, 217), (232, 232, 232), (245, 245, 245)}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def chat(prompt, max_tokens=1200, timeout=90):
|
|
38
|
+
body = json.dumps({"model": MODEL, "messages": [{"role": "user", "content": prompt}],
|
|
39
|
+
"temperature": 0.2, "max_tokens": max_tokens}).encode()
|
|
40
|
+
for a in range(3):
|
|
41
|
+
try:
|
|
42
|
+
req = urllib.request.Request(CHAT_URL, data=body,
|
|
43
|
+
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
44
|
+
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
45
|
+
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
46
|
+
if a < 2: time.sleep(1.0*(a+1))
|
|
47
|
+
else: return ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def real_design():
|
|
51
|
+
"""fywl-ui 真设计系统:色板(rgb 集) + 真组件名 + layout。"""
|
|
52
|
+
r = H.dispatch("get_design_system", {}) or {}
|
|
53
|
+
proj = next((p for p in r.get("projects", []) if p.get("project") == "fywl-ui"), {})
|
|
54
|
+
colors = proj.get("colors", {}) or {}
|
|
55
|
+
palette_rgb = set()
|
|
56
|
+
for v in colors.values():
|
|
57
|
+
if isinstance(v, str) and v.startswith("hsl"):
|
|
58
|
+
m = re.match(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", v)
|
|
59
|
+
if m:
|
|
60
|
+
h, s, l = float(m.group(1)) / 360.0, float(m.group(2)) / 100.0, float(m.group(3)) / 100.0
|
|
61
|
+
r_, g_, b_ = colorsys.hls_to_rgb(h, l, s)
|
|
62
|
+
palette_rgb.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
|
|
63
|
+
comps = set()
|
|
64
|
+
for d in (proj.get("commonComponents", {}), proj.get("formComponents", {})):
|
|
65
|
+
if isinstance(d, dict):
|
|
66
|
+
comps.update(k.lower() for k in d.keys())
|
|
67
|
+
return palette_rgb, comps, proj
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def extract_colors(html):
|
|
71
|
+
"""提取 HTML 里的颜色(#hex/hsl/rgb)→ rgb 集合。"""
|
|
72
|
+
out = set()
|
|
73
|
+
for m in re.findall(r"#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b", html):
|
|
74
|
+
h = m if len(m) == 6 else m * 2
|
|
75
|
+
out.add((int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)))
|
|
76
|
+
for m in re.findall(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", html):
|
|
77
|
+
h, s, l = float(m[0]) / 360.0, float(m[1]) / 100.0, float(m[2]) / 100.0
|
|
78
|
+
r_, g_, b_ = colorsys.hls_to_rgb(h, l, s)
|
|
79
|
+
out.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
|
|
80
|
+
for m in re.findall(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", html):
|
|
81
|
+
out.add((int(m[0]), int(m[1]), int(m[2])))
|
|
82
|
+
return out
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def extract_components(html):
|
|
86
|
+
"""提取 HTML 里的组件标签(el-xxx/van-xxx/a-xxx 等)。"""
|
|
87
|
+
return set(m.lower() for m in re.findall(r"<(?:el|van|a|n|iview|ant)-([\w-]+)", html))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def near(c1, c2, tol=40):
|
|
91
|
+
return all(abs(a - b) <= tol for a, b in zip(c1, c2))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def color_fidelity(html, palette_rgb):
|
|
95
|
+
cols = extract_colors(html)
|
|
96
|
+
if not cols:
|
|
97
|
+
return 0.0, 0
|
|
98
|
+
real = sum(1 for c in cols if any(near(c, p) for p in palette_rgb) or c in _NEUTRAL)
|
|
99
|
+
return round(real / len(cols), 3), len(cols)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def comp_fidelity(html, real_comps):
|
|
103
|
+
comps = extract_components(html)
|
|
104
|
+
if not comps:
|
|
105
|
+
return 0.0, 0
|
|
106
|
+
if not real_comps:
|
|
107
|
+
return 1.0, len(comps) # 无真组件集则不罚
|
|
108
|
+
real = sum(1 for c in comps if c in real_comps)
|
|
109
|
+
return round(real / len(comps), 3), len(comps)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def run():
|
|
113
|
+
palette, comps, proj = real_design()
|
|
114
|
+
print(" 真色板 rgb:", list(palette)[:4], "| 真组件:", list(comps)[:8])
|
|
115
|
+
rep = {"pages": []}
|
|
116
|
+
for pg in PAGES:
|
|
117
|
+
bare = chat(f"画一个 {pg} 的 HTML 原型(单文件,含表格/表单/按钮,内联样式)。直接输出 HTML。")
|
|
118
|
+
ds = ("fywl-ui 真设计系统:主色 hsl(214,86%,59%) 成功 hsl(100,54%,49%) 警告 hsl(36,100%,64%) "
|
|
119
|
+
"危险 hsl(0,100%,68%) 圆角 0.25rem 布局 mixed-nav 侧栏 160px;"
|
|
120
|
+
"组件用 ElementPlus(el-table/el-form/el-button/el-input/el-select/el-pagination)。")
|
|
121
|
+
# BF4 杠杆:CSS 变量注入(:root 定义真色板 + 强制 var() 用色 → 颜色全是色板色)
|
|
122
|
+
cssvar = ("<style>:root{--primary:hsl(214,86%,59%);--success:hsl(100,54%,49%);"
|
|
123
|
+
"--warning:hsl(36,100%,64%);--danger:hsl(0,100%,68%);--text:#333;--border:#d9d9d9;--bg:#f5f5f5}</style>")
|
|
124
|
+
scaff = chat(f"画一个 {pg} 的 HTML 原型(单文件,内联样式)。\n"
|
|
125
|
+
f"【设计系统接地,必须保真】:{ds}\n"
|
|
126
|
+
f"⚠️ 颜色铁律(CSS 变量):把这段 {cssvar} 放在 <head>,"
|
|
127
|
+
f"所有 color/background/border 【必须用 var(--primary)/var(--success)/var(--warning)/var(--danger)/var(--text)/var(--border)/var(--bg)】,"
|
|
128
|
+
f"【禁止】写任何裸 hex/hsl 色值(#409eff 等一律不许);组件用 el-xxx。直接输出 HTML。")
|
|
129
|
+
bP, bN = color_fidelity(bare, palette)
|
|
130
|
+
sP, sN = color_fidelity(scaff, palette)
|
|
131
|
+
bC, _ = comp_fidelity(bare, comps)
|
|
132
|
+
sC, _ = comp_fidelity(scaff, comps)
|
|
133
|
+
rep["pages"].append({"page": pg, "bare_color": bP, "scaffold_color": sP,
|
|
134
|
+
"bare_comp": bC, "scaffold_comp": sC})
|
|
135
|
+
print(" %s: 颜色保真 bare=%.0f%% scaff=%.0f%% | 组件保真 bare=%.0f%% scaff=%.0f%%" % (
|
|
136
|
+
pg, bP*100, sP*100, bC*100, sC*100))
|
|
137
|
+
n = len(PAGES) or 1
|
|
138
|
+
rep["agg"] = {
|
|
139
|
+
"bare_color": round(sum(p["bare_color"] for p in rep["pages"])/n, 3),
|
|
140
|
+
"scaffold_color": round(sum(p["scaffold_color"] for p in rep["pages"])/n, 3),
|
|
141
|
+
"bare_comp": round(sum(p["bare_comp"] for p in rep["pages"])/n, 3),
|
|
142
|
+
"scaffold_comp": round(sum(p["scaffold_comp"] for p in rep["pages"])/n, 3),
|
|
143
|
+
}
|
|
144
|
+
return rep
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def main():
|
|
148
|
+
import argparse
|
|
149
|
+
ap = argparse.ArgumentParser()
|
|
150
|
+
ap.add_argument("--json", action="store_true")
|
|
151
|
+
args = ap.parse_args()
|
|
152
|
+
r = run()
|
|
153
|
+
a = r["agg"]
|
|
154
|
+
if args.json:
|
|
155
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
156
|
+
return
|
|
157
|
+
print("\n## FW9 原型保真度飞轮(vs 真实 fywl-ui 设计系统)")
|
|
158
|
+
print("| 维度 | bare(无设计接地) | +真设计系统接地 |")
|
|
159
|
+
print("|---|---|---|")
|
|
160
|
+
print("| 颜色保真(色∈真fywl-ui色板) | %.0f%% | %.0f%% |" % (a["bare_color"]*100, a["scaffold_color"]*100))
|
|
161
|
+
print("| 组件保真(用真el-组件) | %.0f%% | %.0f%% |" % (a["bare_comp"]*100, a["scaffold_comp"]*100))
|
|
162
|
+
print("\n保真=原型是 fywl-ui 真样子(真色板/真组件/真layout),非通用蓝/编造组件。bare 通用→低保真;scaffold 接真→高保真。")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
main()
|