@hupan56/wlkj 3.3.8 → 3.3.9
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/package.json +1 -1
- package/templates/qoder/commands/optional/wl-spec.md +12 -2
- package/templates/qoder/commands/optional/wl-status.md +8 -0
- package/templates/qoder/commands/wl-code.md +8 -0
- package/templates/qoder/commands/wl-commit.md +11 -1
- package/templates/qoder/commands/wl-data.md +46 -0
- package/templates/qoder/commands/wl-fix.md +47 -0
- package/templates/qoder/commands/wl-init.md +2 -2
- package/templates/qoder/commands/wl-knowledge.md +41 -0
- package/templates/qoder/commands/wl-prd.md +23 -22
- package/templates/qoder/commands/wl-review.md +49 -0
- package/templates/qoder/commands/wl-search.md +23 -27
- package/templates/qoder/commands/wl-task.md +11 -18
- package/templates/qoder/commands/wl-test.md +8 -0
- package/templates/qoder/contracts/insight.md +55 -0
- package/templates/qoder/hooks/__pycache__/pre-tool-use-commit.cpython-39.pyc +0 -0
- package/templates/qoder/hooks/__pycache__/pre-tool-use.cpython-39.pyc +0 -0
- package/templates/qoder/hooks/__pycache__/session-start.cpython-39.pyc +0 -0
- package/templates/qoder/hooks/__pycache__/stop-eval.cpython-39.pyc +0 -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 +1 -0
- package/templates/qoder/scripts/capability/registry.py +0 -3
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +1 -0
- package/templates/qoder/scripts/domain/integration/spec_upload.py +3 -3
- 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/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/io/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/io/__pycache__/context_cache.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/io/context_cache.py +94 -0
- package/templates/qoder/scripts/orchestration/__pycache__/wlkj.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +95 -0
- package/templates/qoder/scripts/tool_guide.md +70 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/alignment_matrix.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/bf2_content_fidelity.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/bf2_llmjudge.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/bf_score.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/code_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/dispatcher_ab.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/feature_fidelity_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/gradient_matrix.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/metrics_dashboard.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/multi_turn_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/prd_fidelity_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/prd_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/prototype_fidelity_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/recall_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/robustness_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/speed_accuracy_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/task_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/token_flywheel.cpython-39.pyc +0 -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/__pycache__/lint_cases.cpython-39.pyc +0 -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/__pycache__/test_context_cache.cpython-39.pyc +0 -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/settings.json +8 -0
- package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
- package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +1 -0
- package/templates/qoder/agents/design-agent.md +0 -20
- package/templates/qoder/agents/spec-generator.md +0 -21
- package/templates/qoder/scripts/capability/smoke_test_report.json.new +0 -94
- package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +0 -10
- package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +0 -57
- package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +0 -62
- package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +0 -146
- package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +0 -64
- package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +0 -52
- package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +0 -104
- package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +0 -55
- package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +0 -95
- package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +0 -8
- package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +0 -22
- package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +0 -55
- package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +0 -90
- package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +0 -9
- package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +0 -206
- package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +0 -132
- package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +0 -80
- package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +0 -59
- package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +0 -93
- package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +0 -421
- package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +0 -9
- package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +0 -79
- package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +0 -99
- package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +0 -69
- package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +0 -78
- package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +0 -8
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +0 -105
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +0 -153
- package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +0 -120
- 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
- package/templates/qoder/scripts/domain/kg/sync_repowiki.py +0 -109
- package/templates/qoder/scripts/engine/poller.py +0 -219
|
@@ -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()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""recall_flywheel.py — FW1 检索类召回飞轮(非循环 held-out gold)。
|
|
2
|
+
|
|
3
|
+
T1.0 的召回 R~80% 用 ILIKE-gold 是循环的(改非ILIKE检索提不了R)。本飞轮用**非循环 gold**:
|
|
4
|
+
held-out = 图邻居中【不含主题关键词】的实体(只有 graph/semantic 检索能找到,ILIKE 找不到)。
|
|
5
|
+
量各检索策略对此 gold 的召回 → 改检索(加图扩展→语义)→ 重量 → 直到 ≥0.85。
|
|
6
|
+
纯检索质量(无 LLM),快,可反复转飞轮。
|
|
7
|
+
|
|
8
|
+
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/recall_flywheel.py
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
15
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
16
|
+
sys.path.insert(0, _p)
|
|
17
|
+
|
|
18
|
+
from sqlalchemy import text, bindparam # noqa: E402
|
|
19
|
+
from app.db import SessionLocal # noqa: E402
|
|
20
|
+
|
|
21
|
+
THEMES = ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"]
|
|
22
|
+
TARGET = 0.85
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _q(s, sql, params=None):
|
|
26
|
+
try:
|
|
27
|
+
return s.execute(text(sql), params or {}).fetchall()
|
|
28
|
+
except Exception:
|
|
29
|
+
try:
|
|
30
|
+
s.rollback()
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
return []
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ilike_ids(s, theme, limit):
|
|
37
|
+
"""name/cn 关键词命中(T1.0 现状检索口径)。"""
|
|
38
|
+
return [r[0] for r in _q(s,
|
|
39
|
+
"SELECT id FROM entities WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT :n",
|
|
40
|
+
{"p": f"%{theme}%", "n": limit})]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def heldout_gold(s, theme, max_n=25):
|
|
44
|
+
"""非循环 gold:cn_desc(描述)提及主题 但 name/cn 不含 = 只有描述/语义/图能找到,
|
|
45
|
+
关键词搜 name/cn 找不到。cn_desc 与 name/cn 是独立信号 → 公平。"""
|
|
46
|
+
rows = _q(s,
|
|
47
|
+
"SELECT id FROM entities WHERE cn_desc ILIKE :p "
|
|
48
|
+
"AND NOT (canonical ILIKE :p OR cn ILIKE :p) LIMIT :n",
|
|
49
|
+
{"p": f"%{theme}%", "n": max_n * 3})
|
|
50
|
+
return [r[0] for r in rows][:max_n]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def strat_ilike(s, theme, limit=80):
|
|
54
|
+
"""基线:name/cn 关键词(T1.0 现状)。"""
|
|
55
|
+
return set(ilike_ids(s, theme, limit))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def strat_ilike_desc(s, theme, limit=80):
|
|
59
|
+
"""杠杆1:name/cn + cn_desc(多搜描述列,廉价高杠杆)。"""
|
|
60
|
+
return set(r[0] for r in _q(s,
|
|
61
|
+
"SELECT id FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p OR cn_desc ILIKE :p) LIMIT :n",
|
|
62
|
+
{"p": f"%{theme}%", "n": limit}))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def strat_ilike_graph(s, theme, seed_limit=25):
|
|
66
|
+
"""杠杆2:name/cn 种子 + 1跳图扩展。"""
|
|
67
|
+
seeds = ilike_ids(s, theme, seed_limit)
|
|
68
|
+
res = set(seeds)
|
|
69
|
+
if seeds:
|
|
70
|
+
stmt = text(
|
|
71
|
+
"SELECT DISTINCT e.to_id FROM edges e WHERE e.from_id IN :ids"
|
|
72
|
+
).bindparams(bindparam("ids", expanding=True))
|
|
73
|
+
try:
|
|
74
|
+
for r in s.execute(stmt, {"ids": seeds}).fetchall():
|
|
75
|
+
if r[0]:
|
|
76
|
+
res.add(r[0])
|
|
77
|
+
except Exception:
|
|
78
|
+
try:
|
|
79
|
+
s.rollback()
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
return res
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def recall(strategy_ids, gold):
|
|
86
|
+
if not gold:
|
|
87
|
+
return None
|
|
88
|
+
return len(set(gold) & strategy_ids) / len(gold)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def run(verbose=True):
|
|
92
|
+
s = SessionLocal()
|
|
93
|
+
per_theme = []
|
|
94
|
+
accA = accB = accC = 0.0
|
|
95
|
+
cnt = 0
|
|
96
|
+
for th in THEMES:
|
|
97
|
+
gold = heldout_gold(s, th, 25)
|
|
98
|
+
if not gold:
|
|
99
|
+
continue
|
|
100
|
+
A = strat_ilike(s, th, 80)
|
|
101
|
+
B = strat_ilike_desc(s, th, 80)
|
|
102
|
+
C = strat_ilike_graph(s, th, 25)
|
|
103
|
+
rA, rB, rC = recall(A, gold), recall(B, gold), recall(C, gold)
|
|
104
|
+
per_theme.append({"theme": th, "gold_n": len(gold),
|
|
105
|
+
"ilike": round(rA, 3), "ilike_desc": round(rB, 3), "ilike_graph": round(rC, 3)})
|
|
106
|
+
accA += rA
|
|
107
|
+
accB += rB
|
|
108
|
+
accC += rC
|
|
109
|
+
cnt += 1
|
|
110
|
+
if verbose:
|
|
111
|
+
print(" %s: gold=%d | ILIKE=%.2f | +desc=%.2f | +图=%.2f" % (th, len(gold), rA, rB, rC))
|
|
112
|
+
s.close()
|
|
113
|
+
n = cnt or 1
|
|
114
|
+
return {"per_theme": per_theme, "n_themes": cnt,
|
|
115
|
+
"ilike": round(accA / n, 3), "ilike_desc": round(accB / n, 3), "ilike_graph": round(accC / n, 3),
|
|
116
|
+
"desc_gain_pp": round((accB - accA) / n * 100, 1),
|
|
117
|
+
"graph_gain_pp": round((accC - accA) / n * 100, 1),
|
|
118
|
+
"target": TARGET}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main():
|
|
122
|
+
import argparse
|
|
123
|
+
ap = argparse.ArgumentParser()
|
|
124
|
+
ap.add_argument("--json", action="store_true")
|
|
125
|
+
args = ap.parse_args()
|
|
126
|
+
r = run()
|
|
127
|
+
if args.json:
|
|
128
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
129
|
+
return
|
|
130
|
+
print("\n## FW1 召回飞轮(held-out = cn_desc 提及但 name/cn 不含,非循环独立 gold)")
|
|
131
|
+
print("| 检索策略 | 召回 R | 达标(≥85%%) |")
|
|
132
|
+
print("|---|---|---|")
|
|
133
|
+
print("| ILIKE name/cn(T1.0 现状) | %.1f%% | %s |" % (r["ilike"] * 100, "✓" if r["ilike"] >= TARGET else "✗"))
|
|
134
|
+
print("| ILIKE +cn_desc(杠杆1 廉价) | %.1f%% | %s |" % (r["ilike_desc"] * 100, "✓" if r["ilike_desc"] >= TARGET else "✗"))
|
|
135
|
+
print("| ILIKE +图扩展(杠杆2) | %.1f%% | %s |" % (r["ilike_graph"] * 100, "✓" if r["ilike_graph"] >= TARGET else "✗"))
|
|
136
|
+
print("| +cn_desc 增益 | %+.1fpp | — |" % r["desc_gain_pp"])
|
|
137
|
+
print("| +图扩展 增益 | %+.1fpp | — |" % r["graph_gain_pp"])
|
|
138
|
+
best = max(r["ilike_desc"], r["ilike_graph"])
|
|
139
|
+
met = best >= TARGET
|
|
140
|
+
winner = "+cn_desc" if r["ilike_desc"] >= r["ilike_graph"] else "+图扩展"
|
|
141
|
+
print("\n飞轮状态: 最佳策略(%s)召回 %.1f%% %s 85%% 目标。%s" % (
|
|
142
|
+
winner, best * 100, "达标" if met else "未达",
|
|
143
|
+
"→ 胜出杠杆该接进 wl-search 检索;召回飞轮此轮达标,转 FW2" if met
|
|
144
|
+
else "→ 下轮:组合杠杆(+desc+图) / 语义 rag_search / 提种子数,继续转"))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
main()
|