MenuPilot 0.1.0__py3-none-any.whl
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.
- menupilot/__init__.py +3 -0
- menupilot/__main__.py +4 -0
- menupilot/agent/__init__.py +0 -0
- menupilot/agent/agent_loop.py +414 -0
- menupilot/agent/matching_engine.py +974 -0
- menupilot/agent/option_expander.py +490 -0
- menupilot/agent/orchestration.py +570 -0
- menupilot/agent/rule_engine.py +509 -0
- menupilot/agent/sandbox.py +216 -0
- menupilot/agent/schema_analyzer.py +1026 -0
- menupilot/agent/template_preprocessor.py +293 -0
- menupilot/agent/token_classifier.py +816 -0
- menupilot/agent/tools.py +365 -0
- menupilot/agent/workflow.py +1072 -0
- menupilot/cli/human_review.py +191 -0
- menupilot/cli/repl.py +821 -0
- menupilot/config.py +113 -0
- menupilot/data/__init__.py +0 -0
- menupilot/data/canonical_schema.py +135 -0
- menupilot/data/mapping_rules.yaml +387 -0
- menupilot/data/memory.py +674 -0
- menupilot/data/token_dict.py +275 -0
- menupilot/excel_io/__init__.py +0 -0
- menupilot/excel_io/excel_reader.py +552 -0
- menupilot/excel_io/excel_writer.py +413 -0
- menupilot/main.py +322 -0
- menupilot/wizard.py +86 -0
- menupilot-0.1.0.dist-info/METADATA +397 -0
- menupilot-0.1.0.dist-info/RECORD +33 -0
- menupilot-0.1.0.dist-info/WHEEL +5 -0
- menupilot-0.1.0.dist-info/entry_points.txt +2 -0
- menupilot-0.1.0.dist-info/licenses/LICENSE +21 -0
- menupilot-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Human Review CLI — 低置信度行交互式审核。
|
|
3
|
+
|
|
4
|
+
用法:
|
|
5
|
+
from menupilot.cli.human_review import run_review
|
|
6
|
+
result = run_review(low_conf_rows, master_fingerprint)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_review(
|
|
13
|
+
low_conf_rows: List[Dict[str, Any]],
|
|
14
|
+
master_fingerprint: str = "",
|
|
15
|
+
) -> Dict[str, Any]:
|
|
16
|
+
"""交互式审核低置信度行。
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
low_conf_rows: step_match 输出的低置信度行列表。
|
|
20
|
+
master_fingerprint: 主数据文件 MD5 前 8 位(保留字段)。
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
{"decisions": [{"row_index": 5, "action": "accept", "sop": "..."}, ...]}
|
|
24
|
+
"""
|
|
25
|
+
total = len(low_conf_rows)
|
|
26
|
+
if total == 0:
|
|
27
|
+
return {"decisions": []}
|
|
28
|
+
|
|
29
|
+
decisions = []
|
|
30
|
+
for idx_in_list, row in enumerate(low_conf_rows):
|
|
31
|
+
# row 来自 match_results,row_index 是原始 match_results 索引
|
|
32
|
+
row_index = row.get("_match_index", idx_in_list)
|
|
33
|
+
product = row.get("template_product_name", "?")
|
|
34
|
+
sop = row.get("sop", "")
|
|
35
|
+
score = row.get("product_score", 0)
|
|
36
|
+
reason = row.get("failure_reason", "?")
|
|
37
|
+
unmatched = row.get("unmatched_attributes", [])
|
|
38
|
+
|
|
39
|
+
print(f"\n{'='*56}")
|
|
40
|
+
print(f"[LOW_CONFIDENCE 审核] {idx_in_list + 1}/{total} 条需要确认")
|
|
41
|
+
print(f"{'='*56}")
|
|
42
|
+
print(f"\n[{idx_in_list + 1}/{total}] 商品:{product}")
|
|
43
|
+
if unmatched:
|
|
44
|
+
print(f" 不匹配属性:{', '.join(unmatched)}")
|
|
45
|
+
print(f" 候选 SOP:{sop}(置信度 {score:.0f}%)")
|
|
46
|
+
print(f" 原因:{reason}")
|
|
47
|
+
print()
|
|
48
|
+
print("[1] 接受此结果")
|
|
49
|
+
print("[2] 手动输入正确 SOP")
|
|
50
|
+
print("[3] 本次跳过(下次运行仍会提示)")
|
|
51
|
+
print("[4] 永久跳过(不再提示此行)")
|
|
52
|
+
print("-" * 42)
|
|
53
|
+
|
|
54
|
+
decision = _get_user_decision(row_index, sop)
|
|
55
|
+
decisions.append(decision)
|
|
56
|
+
|
|
57
|
+
# 打印摘要
|
|
58
|
+
accepted = sum(1 for d in decisions if d["action"] in ("accept", "manual"))
|
|
59
|
+
skipped = sum(1 for d in decisions if d["action"] == "skip")
|
|
60
|
+
perm_skipped = sum(1 for d in decisions if d["action"] == "permanent_skip")
|
|
61
|
+
print(f"\n审核完成:接受 {accepted} / 跳过 {skipped} / 永久跳过 {perm_skipped}")
|
|
62
|
+
|
|
63
|
+
return {"decisions": decisions}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_user_decision(row_index: int, default_sop: str) -> Dict[str, Any]:
|
|
67
|
+
"""获取单条审核决策。"""
|
|
68
|
+
while True:
|
|
69
|
+
try:
|
|
70
|
+
choice = input(" 请输入 1/2/3/4: ").strip()
|
|
71
|
+
except (EOFError, KeyboardInterrupt):
|
|
72
|
+
return {"row_index": row_index, "action": "skip"}
|
|
73
|
+
|
|
74
|
+
if choice == "1":
|
|
75
|
+
return {
|
|
76
|
+
"row_index": row_index,
|
|
77
|
+
"action": "accept",
|
|
78
|
+
"sop": default_sop,
|
|
79
|
+
}
|
|
80
|
+
elif choice == "2":
|
|
81
|
+
sop = input(" 请输入正确的 SOP: ").strip()
|
|
82
|
+
if sop:
|
|
83
|
+
return {"row_index": row_index, "action": "manual", "sop": sop}
|
|
84
|
+
print(" [错误] SOP 不能为空,请重新输入或选择其他选项")
|
|
85
|
+
elif choice == "3":
|
|
86
|
+
return {"row_index": row_index, "action": "skip"}
|
|
87
|
+
elif choice == "4":
|
|
88
|
+
return {"row_index": row_index, "action": "permanent_skip"}
|
|
89
|
+
else:
|
|
90
|
+
print(" [错误] 无效输入,请输入 1、2、3 或 4")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def run_review_silent(
|
|
94
|
+
low_conf_rows: List[Dict[str, Any]],
|
|
95
|
+
action: str = "skip",
|
|
96
|
+
) -> Dict[str, Any]:
|
|
97
|
+
"""非交互式审核(批量模式用)。
|
|
98
|
+
|
|
99
|
+
对所有低置信度行统一执行相同操作。
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
low_conf_rows: 低置信度行列表。
|
|
103
|
+
action: "accept" | "skip" | "permanent_skip"
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
{"decisions": [...]}
|
|
107
|
+
"""
|
|
108
|
+
decisions = []
|
|
109
|
+
for idx, row in enumerate(low_conf_rows):
|
|
110
|
+
sop = row.get("sop", "")
|
|
111
|
+
if action == "accept":
|
|
112
|
+
decisions.append({
|
|
113
|
+
"row_index": idx,
|
|
114
|
+
"action": "accept",
|
|
115
|
+
"sop": sop,
|
|
116
|
+
})
|
|
117
|
+
else:
|
|
118
|
+
decisions.append({
|
|
119
|
+
"row_index": idx,
|
|
120
|
+
"action": action,
|
|
121
|
+
})
|
|
122
|
+
return {"decisions": decisions}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ── 自测 ────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
passed = 0
|
|
129
|
+
failed = 0
|
|
130
|
+
|
|
131
|
+
def check(condition, msg):
|
|
132
|
+
global passed, failed
|
|
133
|
+
if condition:
|
|
134
|
+
passed += 1
|
|
135
|
+
print(f" PASS {msg}")
|
|
136
|
+
else:
|
|
137
|
+
failed += 1
|
|
138
|
+
print(f" FAIL {msg}")
|
|
139
|
+
|
|
140
|
+
print("=== Human Review 自测 ===\n")
|
|
141
|
+
|
|
142
|
+
# ── 1. 空列表 ──
|
|
143
|
+
print("1. 空列表返回空 decisions")
|
|
144
|
+
r1 = run_review_silent([], "skip")
|
|
145
|
+
check(r1["decisions"] == [], "空列表 → 空 decisions")
|
|
146
|
+
print()
|
|
147
|
+
|
|
148
|
+
# ── 2. accept 操作 ──
|
|
149
|
+
print("2. accept 操作")
|
|
150
|
+
rows = [{"sop": "T240"}]
|
|
151
|
+
r2 = run_review_silent(rows, "accept")
|
|
152
|
+
check(len(r2["decisions"]) == 1, "1 条 decision")
|
|
153
|
+
check(r2["decisions"][0]["action"] == "accept", "action=accept")
|
|
154
|
+
check(r2["decisions"][0]["sop"] == "T240", "sop=T240")
|
|
155
|
+
print()
|
|
156
|
+
|
|
157
|
+
# ── 3. manual 操作需要用户输入,用 silent 模拟 ──
|
|
158
|
+
print("3. manual 操作(silent 模拟)")
|
|
159
|
+
# manual 只在交互模式触发,silent 模式用 accept + sop 覆盖
|
|
160
|
+
r3 = run_review_silent(
|
|
161
|
+
[{"sop": "OLD"}], "accept"
|
|
162
|
+
)
|
|
163
|
+
check(r3["decisions"][0]["action"] == "accept", "silent accept")
|
|
164
|
+
print()
|
|
165
|
+
|
|
166
|
+
# ── 4. skip 操作 ──
|
|
167
|
+
print("4. skip 操作")
|
|
168
|
+
r4 = run_review_silent(rows, "skip")
|
|
169
|
+
check(r4["decisions"][0]["action"] == "skip", "action=skip")
|
|
170
|
+
print()
|
|
171
|
+
|
|
172
|
+
# ── 5. permanent_skip 操作 ──
|
|
173
|
+
print("5. permanent_skip 操作")
|
|
174
|
+
r5 = run_review_silent(rows, "permanent_skip")
|
|
175
|
+
check(r5["decisions"][0]["action"] == "permanent_skip", "action=permanent_skip")
|
|
176
|
+
print()
|
|
177
|
+
|
|
178
|
+
# ── 6. 多条审核 ──
|
|
179
|
+
print("6. 多条审核")
|
|
180
|
+
rows6 = [
|
|
181
|
+
{"sop": "SOP-A", "template_product_name": "商品A"},
|
|
182
|
+
{"sop": "SOP-B", "template_product_name": "商品B"},
|
|
183
|
+
{"sop": "SOP-C", "template_product_name": "商品C"},
|
|
184
|
+
]
|
|
185
|
+
r6 = run_review_silent(rows6, "accept")
|
|
186
|
+
check(len(r6["decisions"]) == 3, "3 条 decisions")
|
|
187
|
+
check(r6["decisions"][0]["sop"] == "SOP-A", "第 1 条 SOP 正确")
|
|
188
|
+
check(r6["decisions"][2]["sop"] == "SOP-C", "第 3 条 SOP 正确")
|
|
189
|
+
print()
|
|
190
|
+
|
|
191
|
+
print(f"=== 结果: {passed} passed, {failed} failed ===")
|