Files
Aislo/M02_MasterTemplete/M02_San_Basis_Value.py
eomsangdonandClaude Opus 5.5 532cc52c8e feat(793-3a): 근거 제안 엔진 — 번호 ∩ 값 ∩ 제목 ∩ 기계 상수 → M01 기초 + 옵션 · 품 검산 · 세 단
- M01 값 역색인 · 숫자 정규화 한 벌(N1~N10 · 단위 기준 · % ↔ 소수 · t ↔ kg · 인/양 몫) · M01 지문마다 한 번 · 서버 시작 때 뒤에서 지음
- 값 길(상수 지문 → 로직 · 우연 보정 유의 4) · 합친 길(분석 648-D2 차례 · 후보 2~6 엔진 품 대조) · 검산 · 옵션(B1~B6 → 값 길 V → 고르기 맞댐 → 입력 필요)
- 세 단 확정 · 근접(판 차이 · 현장 값 · 감률 · 할증 배수 · 단위 기준 차이) · 사람(+ 값 약한 1 등) · 없음(M01 보강 목록)
- 길 GET hopyo-basis/{키} · hopyo-basis/{키}/{번} · 파생 저장본 · 올림 때 HP 숨은식(STmate G 식) · 끝내기 때 지움 · 판정 길 그대로
- 보정 자료 = 여덟 파일 상수 1,094 · 시험 test_793_3a 11(459 블록 기초 260 · 옵션 전부 52 · 품 같음 141 · 없음 85)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:19:26 +09:00

308 lines
12 KiB
Python

"""근거 제안 3 — 값 길: 상수 지문 → M01 로직 후보(PLAN 793-3a · 분석 648-D 1-3) · 읽기만.
로직마다 입력 값 조합(400 넘으면 입력마다 혼자 가장 많이 덮는 값) → 찾기마다 줄 하나(새로 덮는
상수가
가장 많은 줄부터) → 덮은 상수 · 우연 보정 유의(−log10 꼬리 확률) · 판정 U 유일 · 다 설명 · 옵션
하나 /
P 유일 / A 동점(유의 1 안) / N 유의 4 미만 / N0 상수 없음 · 동점이 모두 한 하위 로직을 부르면 그
하위(핵).
블록 = 일 절 2+ · 상수 2+ 주 절이 있으면 주 절 판정 모음 + 절 밖 상수 · 그 밖 블록 통째 + 덧 기초.
"""
from __future__ import annotations
import itertools
import json
import math
from collections import defaultdict
from typing import Any
from M02_MasterTemplete import M02_San_Basis_Const as bc
from M02_MasterTemplete import M02_San_Basis_Index as bi
TIE = 1.0 # 동점 폭(유의)
SIG_MIN = 4.0 # 1등 유의가 이 아래면 N — 로직 1,515 중 우연 1등 ≈ 10^-3.2
fam = bi.fam
def solve(hits: list[tuple]) -> dict:
"""hits = [(로직, 부름id, 줄, 묶음)] · 식 속 수 = 부름id None → 덮음 · 옵션 · 옵션여럿."""
inl = {h[0] for h in hits if h[1] is None}
bound = [h for h in hits if h[1] is not None]
inputs = sorted({i for h in bound for i in h[3] if not i.startswith("§")})
vals = {i: sorted({h[3][i] for h in bound if i in h[3]}) for i in inputs}
by_call: dict = defaultdict(lambda: defaultdict(set))
row_bind = {}
for k, call, row, b in bound:
by_call[call][row].add(k)
row_bind[(call, row)] = b
order = sorted(by_call, key=lambda c: -max(len(x) for x in by_call[c].values()))
def run(combo: dict):
cov, fixed, pick = set(inl), dict(combo), {}
while True:
best = None
for call in order:
if call in pick:
continue
for r, ks in by_call[call].items():
gain = len(ks - cov)
if (
gain
and (best is None or gain > best[0])
and all(
fixed.get(i, v) == v if i.startswith("§") else combo.get(i) == v
for i, v in row_bind[(call, r)].items()
)
):
best = (gain, call, r, ks)
if best is None:
break
_, call, r, ks = best
cov |= ks
pick[call] = r
fixed |= {i: v for i, v in row_bind[(call, r)].items() if i.startswith("§")}
return cov, pick, -len(pick)
if (math.prod(len(v) for v in vals.values()) if vals else 1) <= 400:
combos = [dict(zip(inputs, c)) for c in itertools.product(*[vals[i] for i in inputs])] or [
{}
]
else: # ponytail: 입력 값 조합이 많으면 입력마다 혼자 가장 많이 덮는 값 — 상호작용 놓칠 수 있음
cnt = defaultdict(set)
for h in bound:
for i, v in h[3].items():
cnt[(i, v)].add(h[0])
combos = [{i: max(vals[i], key=lambda v: len(cnt[(i, v)])) for i in inputs}]
best, outs = (-1, -1), []
for combo in combos:
cov, pick, ev = run(combo)
score = (len(cov), ev)
if score > best:
best, outs = score, [(combo, cov, pick)]
elif score == best:
outs.append((combo, cov, pick))
used = {
i
for _, _, pick in outs
for call, r in pick.items()
for i in row_bind[(call, r)]
if not i.startswith("§")
}
resolved, opt_amb = {}, {}
for i in sorted(used):
seen = sorted({o[0].get(i) for o in outs if o[0].get(i) is not None})
if len(seen) == 1:
resolved[i] = json.loads(seen[0])
elif seen:
opt_amb[i] = [json.loads(x) for x in seen]
same_cov = len({frozenset(o[1]) for o in outs}) == 1
return {"덮음": outs[0][1], "옵션": resolved, "옵션여럿": opt_amb, "조합같은덮음": same_cov}
def rank(idx: bi.Index, K: list[dict]) -> list[dict]:
"""상수 목록 → 로직 후보(유의 차례)."""
per_item, anchored, how_of = defaultdict(list), set(), defaultdict(dict)
for ki, k in enumerate(K):
for item, call, row, bd, how, anc in idx.explain(k):
per_item[item].append((ki, call, row, bd))
how_of[item].setdefault(ki, how)
if anc:
anchored.add(item)
real = sorted(i for i, k in enumerate(K) if not k.get("일반"))
ub = []
for it, hs in per_item.items():
if it in anchored:
c = len({h[0] for h in hs} & set(real))
ub.append((bi.pb_sig([idx.q_of(it, K[i]) for i in real], c), c, it))
ub.sort(key=lambda x: (-x[0], -x[1], x[2]))
out, best = [], 0.0
for s_ub, c, item in ub:
if c == 0 or s_ub < best - TIE or len(out) >= 200:
break
got = solve(per_item[item])
got_real = got["덮음"] & set(real)
s_val = bi.pb_sig([idx.q_of(item, K[i]) for i in real], len(got_real))
best = max(best, s_val)
out.append({"로직": item, **got, "꼴": how_of[item], "실수": len(got_real), "유의": s_val})
out.sort(key=lambda o: (-o["유의"], -o["실수"], o["로직"]))
return out
def collapse(idx: bi.Index, tied: list[dict]) -> tuple[list[dict], str]:
"""동점 정리 — 다른 동점이 모두 그 로직을 부르면 그 하위(핵) · 위 로직 하나면 그 위 로직 ·
그 밖 = 동점 위 로직이 덮는 하위는 뺌."""
for c in tied:
others = [o for o in tied if o is not c]
if others and all(o["로직"] in idx.callers.get(c["로직"], set()) for o in others):
return (
(others, "위 로직 하나")
if len(others) == 1
else ([c], f"핵 하위 로직(위 로직 {len(others)})")
)
keep = []
for o in tied:
if not [
p
for p in tied
if p is not o
and p["로직"] in idx.callers.get(o["로직"], set())
and o["덮음"] <= p["덮음"]
]:
keep.append(o)
return keep, ""
def judge(idx: bi.Index, K: list[dict]) -> dict:
"""한 단위(절 · 블록) 판정 — U · P · A · N · N0."""
real = {i for i, k in enumerate(K) if not k.get("일반")}
if not real:
return {"판정": "N0", "까닭": "지문 없음(상수 0)", "후보": [], "K": K}
cands = [o for o in rank(idx, K) if o["실수"]]
if not cands:
return {"판정": "N", "까닭": "맞는 표 없음", "후보": [], "K": K}
top = cands[0]
if top["유의"] < SIG_MIN:
return {
"판정": "N",
"까닭": f"유의 못 미침(1등 {top['실수']}/{len(real)} · 유의 {top['유의']})",
"후보": cands[:5],
"K": K,
}
tied, note = collapse(idx, [o for o in cands if o["유의"] >= top["유의"] - TIE])
if len(tied) >= 2:
return {
"판정": "A",
"까닭": f"동점 {len(tied)}",
"후보": tied[:12],
"n": len(tied),
"K": K,
"남음": sorted(real - top["덮음"]),
}
top = tied[0]
left = sorted(real - top["덮음"])
full = not left and not top["옵션여럿"] and top["조합같은덮음"]
rest = [o for o in cands if o is not top]
return {
"판정": "U" if full else "P",
"까닭": note,
"후보": [top] + rest[:4],
"K": K,
"남음": left,
}
def cover(idx: bi.Index, K: list[dict], first: dict) -> list[dict]:
"""블록 덮기 — 첫 기초 뒤 남은 상수로 다음 기초(새 상수 2+ · 유일 · 넷까지)."""
picks, done = [first], set(first["덮음"])
for _ in range(3):
rest = [k if i not in done else {**k, "일반": True} for i, k in enumerate(K)]
if all(k.get("일반") for k in rest):
break
got = judge(idx, rest)
if got["판정"] not in ("U", "P"):
break
o = got["후보"][0]
if len(o["덮음"] - done) < 2:
break
picks.append({**o, "동점": 1})
done |= o["덮음"]
return picks
def assemble(idx: bi.Index, bK: list[dict], mains: list) -> dict:
"""절 2+ 블록 — 주 절 판정 모음 + 절 밖 상수(머리 · 비목 절)는 고른 기초가 덮는지 ·
남으면 따로."""
picks, verdicts, covered = [], [], set()
def key(c):
return (round(c["v"], 9), fam(c["단위"]), c.get("직종"))
for _, _, K, sj in mains:
verdicts.append(sj["판정"])
if sj["판정"] in ("U", "P"):
o = sj["후보"][0]
mine = {key(K[j]) for j in o["덮음"]}
picks.append({**o, "덮음": {i for i, c in enumerate(bK) if key(c) in mine}})
covered |= {key(c) for c in K}
rest = [c if key(c) not in covered else {**c, "일반": True} for c in bK]
left = {i for i, c in enumerate(rest) if not c.get("일반")}
if left and picks:
chosen = {p["로직"] for p in picks}
for o in rank(idx, rest):
if o["로직"] not in chosen:
continue
got = o["덮음"] & left
for p in picks:
if p["로직"] != o["로직"]:
continue
p["덮음"] = p["덮음"] | got
if got: # 절 밖 상수가 정한 옵션도 얹음(같은 입력 다른 값 = 여럿)
for kk, vv in o["옵션"].items():
if kk in p["옵션"] and p["옵션"][kk] != vv:
p["옵션여럿"] = {**p["옵션여럿"], kk: [p["옵션"][kk], vv]}
else:
p["옵션"] = {**p["옵션"], kk: vv}
left -= got
if len(left) >= 2:
extra = judge(idx, [c if i in left else {**c, "일반": True} for i, c in enumerate(bK)])
if extra["판정"] in ("U", "P"):
picks.append(extra["후보"][0])
left -= extra["후보"][0]["덮음"]
verdicts.append(extra["판정"])
if not picks:
out = {
"판정": "A" if "A" in verdicts else ("N" if "N" in verdicts else "N0"),
"후보": [],
"K": bK,
}
a = next((x[3] for x in mains if x[3]["판정"] == "A"), None)
return out | ({"후보": a["후보"], "n": a["n"], "K": a["K"]} if a else {})
allu = not left and all(not p["옵션여럿"] for p in picks)
return {
"판정": "U" if allu else "P",
"후보": picks,
"K": bK,
"기초목록": picks,
"남음": sorted(left),
}
def _show(o: dict) -> dict:
return {"로직": o["로직"], "옵션": o["옵션"], "옵션여럿": o["옵션여럿"]}
def value(idx: bi.Index, pr: dict) -> dict:
"""블록 준비(`Const.prep`) → 값 판정 {판정 U|P|A|N|N0, 기초 · 동점 · 약한후보 [{로직, 옵션}],
절 [{절, 판정, 기초?, 동점?}], K, 입력}."""
lines, per_line, secs = pr["lines"], pr["per_line"], pr["secs"]
bK, bIns = bc.gather(per_line, range(len(lines)))
sec_res = []
for title, ids in secs:
K, _ = bc.gather(per_line, ids)
sec_res.append((title, ids, K, judge(idx, K)))
mains = [x for x in sec_res if len([k for k in x[2] if not k.get("일반")]) >= 2]
multi = len(secs) >= 2 and len(mains) >= 1
bj = assemble(idx, bK, mains) if multi else judge(idx, bK)
rec: dict[str, Any] = {
"판정": bj["판정"],
"K": [{"단위": c["단위"]} for c in bK],
"입력": [c["v"] for c in bIns],
}
if bj["판정"] in ("U", "P"):
rec["기초"] = [_show(p) for p in (bj.get("기초목록") or cover(idx, bK, bj["후보"][0]))]
elif bj["판정"] == "A":
rec["동점"] = [_show(o) for o in bj["후보"]]
rec["n"] = bj["n"]
elif bj["판정"] == "N" and bj.get("후보"):
rec["약한후보"] = [_show(o) for o in bj["후보"][:3]]
rec["절"] = []
for title, _, _, sj in sec_res:
one = {"절": title, "판정": sj["판정"]}
if sj["판정"] in ("U", "P"):
one["기초"] = _show(sj["후보"][0])
elif sj["판정"] == "A":
one["동점"] = [o["로직"] for o in sj["후보"]]
rec["절"].append(one)
return rec