File size: 8,000 Bytes
ede84b4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | #!/usr/bin/env python3
"""modutech_v30_2.json → modutech_v30_3.json — ใช้ "พนักงานฝ่ายขาย" แทน "ฝ่ายเทคนิค" (11 ต.ค. 2026)
ทำไม
ผลรอบ 4 (RESULTS_phase2_answer_review.md): คำตอบมี "ฝ่ายเทคนิค" 22 จุด ปนกับ "ฝ่ายขาย" บางข้อในประโยคเดียว
("ยืนยันกับพนักงานฝ่ายขายหรือฝ่ายเทคนิค") ทั้งที่ช่องทางติดต่อที่บอตให้ลูกค้ามีแต่ฝ่ายขาย
เจ้าของตัดสิน 11 ต.ค.: ให้ใช้ "พนักงานฝ่ายขาย" ทุกจุด
ทำอะไร
ไล่ทุกสตริงในไฟล์ ที่มีคำว่า ฝ่ายเทคนิค
"ยืนยันกับฝ่ายเทคนิคก่อนเสนอ" → "ยืนยันกับพนักงานฝ่ายขายก่อนใช้งานจริง" (ก่อนเสนอ เป็นคำของฝ่ายขาย)
"ฝ่ายเทคนิค" ที่เหลือ → "พนักงานฝ่ายขาย"
ของเดิมทุกจุดเก็บใน _text_patch_v30_3 ที่รากของไฟล์ (path · เดิม · ใหม่)
ไม่เก็บเป็นฟิลด์คู่ข้าง ๆ เพราะบาง dict (variant) ถูกคัดลอกเข้า Context ทั้งก้อน — ข้อความเดิมจะหลุดไปถึง LLM
ไม่แตะ: ตัวเลข · สถานะ · ฟิลด์อื่น · จำนวนนับ · ไฟล์ต้นทาง
รัน: python patch_v30_3.py [modutech_v30_2.json] [modutech_v30_3.json] [--changelog CHANGELOG_v30_3.md]
"""
from __future__ import annotations
import argparse
import copy
import hashlib
import json
import sys
OLD = "ฝ่ายเทคนิค"
SUBS = [("ยืนยันกับฝ่ายเทคนิคก่อนเสนอ", "ยืนยันกับพนักงานฝ่ายขายก่อนใช้งานจริง"),
("ฝ่ายเทคนิค", "พนักงานฝ่ายขาย")]
LOG_KEY = "_text_patch_v30_3"
def fingerprint(o) -> str:
return hashlib.sha256(json.dumps(o, ensure_ascii=False, sort_keys=True).encode()).hexdigest()
def fix(s: str) -> str:
for a, b in SUBS:
s = s.replace(a, b)
return s
def walk(o, path, log):
"""แก้ในที่ คืน log ของจุดที่แก้"""
if isinstance(o, dict):
for k in list(o):
o[k] = walk(o[k], f"{path}/{k}", log)
elif isinstance(o, list):
for i in range(len(o)):
o[i] = walk(o[i], f"{path}/{i}", log)
elif isinstance(o, str) and OLD in o:
new = fix(o)
log.append({"path": path, "old": o, "new": new})
return new
return o
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("src", nargs="?", default="modutech_v30_2.json")
ap.add_argument("dst", nargs="?", default="modutech_v30_3.json")
ap.add_argument("--changelog")
a = ap.parse_args()
if a.src == a.dst:
raise SystemExit("ห้ามเขียนทับไฟล์ต้นทาง")
data = json.load(open(a.src, encoding="utf-8"))
if LOG_KEY in data:
raise SystemExit(f"มี {LOG_KEY} อยู่แล้ว — ไฟล์นี้ถูกแพตช์แล้ว หยุด")
shadow = copy.deepcopy(data)
log: list[dict] = []
walk(data, "", log)
print(f"1) แก้ {len(log)} สตริง")
left = json.dumps(data, ensure_ascii=False).count(OLD)
if left:
raise SystemExit(f"❌ ยังเหลือ '{OLD}' {left} จุด")
if any("ก่อนเสนอ" in x["new"] and "ก่อนเสนอ" not in x["old"] for x in log):
raise SystemExit("❌ เกิดคำว่า ก่อนเสนอ ใหม่")
print(f" ✅ ไม่เหลือ '{OLD}'")
print("2) เทียบกับ shadow copy — ย้อนทุกจุดใน log แล้วต้องได้ไฟล์เดิมทุกไบต์")
back = copy.deepcopy(data)
for x in log:
keys = x["path"].split("/")[1:]
o = back
for k in keys[:-1]:
o = o[int(k)] if isinstance(o, list) else o[k]
last = keys[-1]
if isinstance(o, list):
o[int(last)] = x["old"]
else:
o[last] = x["old"]
if fingerprint(back) != fingerprint(shadow):
raise SystemExit("❌ มีอย่างอื่นเปลี่ยน — ไม่เขียนไฟล์")
print(" ✅ นอกจากจุดใน log ไม่มีอะไรเปลี่ยน")
prods = data["products"]
n_pair = sum(len(p.get("industries") or []) for p in prods)
n_app = sum(len(x.get("applications") or []) for p in prods for x in p.get("industries") or [])
if (len(prods), n_pair, n_app) != (68, 211, 651):
raise SystemExit("❌ จำนวนนับเปลี่ยน")
print(f"3) จำนวนนับ {len(prods)} · {n_pair} · {n_app}")
data[LOG_KEY] = log
json.dump(data, open(a.dst, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print(f"\nเขียน {a.dst} แล้ว · {a.src} ไม่ถูกแตะต้อง")
if a.changelog:
groups: dict[tuple, list] = {}
for x in log:
field = [k for k in x["path"].split("/") if not k.isdigit()][-1]
groups.setdefault((field, x["old"], x["new"]), []).append(x["path"])
out = [f"# CHANGELOG {a.dst}", "",
f"สร้างจาก `{a.src}` ด้วย `patch_v30_3.py` · แทน \"ฝ่ายเทคนิค\" ด้วย \"พนักงานฝ่ายขาย\" {len(log)} สตริง",
f"(\"ยืนยันกับฝ่ายเทคนิคก่อนเสนอ\" → \"ยืนยันกับพนักงานฝ่ายขายก่อนใช้งานจริง\") · ของเดิมเก็บใน `{LOG_KEY}`", "",
"เหตุผล: เจ้าของตัดสิน 11 ต.ค. ให้ทุกจุดที่ให้ลูกค้ายืนยันใช้พนักงานฝ่ายขาย (ช่องทางติดต่อที่บอตให้มีแต่ฝ่ายขาย)", "",
"ฟิลด์ที่ไปถึงคำตอบลูกค้า: `width_note_th` · `temperature_unconfirmed_th` · "
"`forbidden_th` อยู่ในส่วนกฎของ prompt · `temperature_note_th` และ `answering_policy` ไม่ได้ส่งให้ LLM", "",
f"## ก่อน → หลัง ({len(groups)} แบบข้อความ)", ""]
for i, ((field, old, new), paths) in enumerate(sorted(groups.items(), key=lambda kv: (kv[0][0], -len(kv[1]))), 1):
codes = sorted({data["products"][int(p.split('/')[2])]["belt_code"] for p in paths
if p.startswith("/products/")})
out += [f"### {i}. `{field}` · {len(paths)} จุด" + (f" · {', '.join(codes)}" if codes else ""), "",
f"- ก่อน: {old}", f"- หลัง: {new}", ""]
open(a.changelog, "w", encoding="utf-8").write("\n".join(out))
print(f"เขียน {a.changelog} แล้ว")
return 0
if __name__ == "__main__":
sys.exit(main())
|