@chrono-meta/fh-gate 3.4.0 → 3.6.0
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/.claude-plugin/marketplace.json +9 -3
- package/README.md +2 -2
- package/docs/codex-compat.md +1 -1
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +89 -4
- package/package.json +8 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +2 -2
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +47 -0
- package/plugins/fh-preprep/.claude-plugin/plugin.json +22 -0
- package/plugins/fh-preprep/README.md +24 -0
- package/plugins/{fh-commons → fh-preprep}/skills/preprep/SKILL.md +75 -3
- package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/mk_slide_fixtures.py +152 -1
- package/plugins/fh-preprep/skills/preprep/lane_attr_consistency.py +370 -0
- package/plugins/fh-preprep/skills/preprep/lane_geometry.py +339 -0
- package/plugins/fh-preprep/skills/preprep/lane_screen_parity.py +232 -0
- package/plugins/fh-preprep/skills/preprep/oox.py +305 -0
- package/plugins/{fh-commons → fh-preprep}/skills/preprep/preprep.py +52 -4
- package/plugins/fh-preprep/skills/preprep/safe_install.py +369 -0
- package/plugins/{fh-commons → fh-preprep}/skills/preprep/surfaces.example.yaml +29 -0
- package/plugins/fh-preprep/skills/preprep/test_preprep_lanes_rp.py +1417 -0
- package/plugins/fh-preprep/skills/preprep/usage_ledger.py +231 -0
- package/plugins/fh-qp/.claude-plugin/plugin.json +1 -1
- package/scripts/fh-run.sh +13 -0
- package/scripts/field_canon_preload.sh +1 -1
- package/scripts/selfcheck.sh +12 -8
- package/scripts/test_fh_run_resolver_lanes.sh +116 -0
- package/scripts/test_files_manifest_shipping_lanes.sh +31 -0
- package/scripts/test_preprep_adjacent_dup_lanes.sh +1 -1
- package/scripts/test_preprep_diagram_lanes.sh +1 -1
- package/scripts/test_preprep_drift_anchor.sh +27 -12
- package/scripts/test_preprep_drift_anchor_lanes.sh +4 -2
- package/scripts/test_preprep_font_lanes.sh +7 -7
- package/scripts/test_preprep_progression_lanes.sh +1 -1
- package/scripts/test_preprep_promise_lanes.sh +1 -1
- package/scripts/test_preprep_retired_lanes.sh +45 -2
- package/scripts/test_preprep_slide_refs_lanes.sh +5 -5
- package/scripts/test_usage_ledger_lanes.sh +284 -0
- package/scripts/usage_report.py +211 -0
- package/plugins/fh-commons/skills/preprep/lane_geometry.py +0 -181
- package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +0 -184
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/README.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/canon_terms.example.yaml +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/diagram_from_json.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/adjdup_known_negative.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/adjdup_known_positive.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/font_revert_probe.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/progression_known_negative.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/progression_known_positive.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/promise_known_negative.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/promise_known_positive.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/interslide_deps.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/jargon_terms.example.yaml +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_adjacent_dup.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_diagram.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_font.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_progression.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_promise.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_slide_refs.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_slide_relations.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/ooxml/c1_baseline.txt +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/ooxml/gate.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/preprep_wire.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/presentation_checklist.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/test_lane_font.py +0 -0
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""P1·P3 — 도형 배치 검사 둘. the private companion store's origin script (lane_geometry.py(2026-09-04,
|
|
3
|
-
36p 사고에서 역산)를 preprep 관례로 이식한 것 — 로직은 그대로다.
|
|
4
|
-
정본: `tracks-meta/fh_signal_2026-09-04_preprep-evolution.md` §2.
|
|
5
|
-
|
|
6
|
-
P1 build-jitter 같은 이름의 도형이 «연속한 두 장»에서 조금 다른 자리에 있다
|
|
7
|
-
→ 넘길 때 튄다. 실사고: 노란 게이트 막대가 직전 장 대비 35,000 EMU 밀려
|
|
8
|
-
회색 연결선과 겹쳤다. 큰 차이는 «의도한 이동»이라 제외한다.
|
|
9
|
-
P3 adjacency 한 도형의 «오른쪽 끝»과 다른 도형의 «왼쪽 끝»이 맞닿으려다 어긋났다
|
|
10
|
-
→ 실제 결함이 이 형태였다(회색 스텁 끝 ↔ 막대 시작, 35,000 겹침).
|
|
11
|
-
|
|
12
|
-
🟥 **P2(near-miss, 같은 종류 모서리끼리만 대조)는 미포함이다 — 채택하지 않는다.** 실사고가
|
|
13
|
-
못 잡히는 이유가 설계 근거였다: 실제 결함은 「회색 스텁 오른쪽 끝 ↔ 막대 왼쪽 끝」이라는
|
|
14
|
-
**다른 종류** 모서리의 인접 관계인데, P2 는 왼↔왼·오른↔오른만 봐서 구조적으로 못 잡는다.
|
|
15
|
-
그 관계는 P3 가 이미 덮는다. 새 레인을 짤 때 «같은 종류끼리»가 기본값이라는 것을 의심하라.
|
|
16
|
-
|
|
17
|
-
🟥 둘 다 advisory. 임계는 사람이 정한다(기본 JIT=200,000 EMU · ALN=63,500 EMU=5pt ·
|
|
18
|
-
1pt=12,700 EMU). `surfaces.yaml` 의 `geometry.jitter_emu` / `geometry.align_emu` 로 바꾼다.
|
|
19
|
-
🟥 «차이 0» 은 정상이고 «차이 큼» 도 정상이다. 이 검사가 보는 것은 그 사이뿐이다.
|
|
20
|
-
|
|
21
|
-
## 🟥 목록으로 읽지 마라 — 델타로 읽어라
|
|
22
|
-
|
|
23
|
-
실측: P3 는 원 코퍼스에서 기저 오탐이 76건이다(아이콘 무리 내부 · 맵 라벨의 «의도된» 겹침).
|
|
24
|
-
그 목록에서 진짜를 골라낼 방법이 없다. 그런데 편집 전/후를 대조하면 **차이가 정확히 그
|
|
25
|
-
한 줄**이었다. ⇒ 절대 목록은 회귀 도구로 못 쓰고 **편집 전후 델타는 정확하다.**
|
|
26
|
-
`surfaces.yaml` 의 `geometry.baseline`(이전 판 pptx 경로)이 있으면 이 레인은 델타 모드로 돈다
|
|
27
|
-
— 「지금 몇 건이냐」가 아니라 «내가 방금 뭘 어긋냈냐»를 묻는 회귀 도구다.
|
|
28
|
-
|
|
29
|
-
## 🟥 P1 이 «비교 안 함»을 «튐 없음»으로 속일 수 있는 자리 (계기 결함, 미리 적는다)
|
|
30
|
-
|
|
31
|
-
`shapes()` 는 **한 장 안에서 이름이 겹치는 도형을 통째로 버린다**(`len(v) == 1` 만 남긴다) —
|
|
32
|
-
겹치는 이름끼리는 대조할 대상을 하나로 못 정하기 때문이다. python-pptx 기본 이름
|
|
33
|
-
(`TextBox 1` 류)은 장마다 새로 매겨져 **이름이 우연히 안 겹칠 수 있고**, 대조 자체가 0건이
|
|
34
|
-
되는 입력에서도 「후보 0건」이 똑같이 출력된다 — 그 0 은 **「튐 없음」이 아니라 「비교 안 함」**
|
|
35
|
-
이다(이 저장소가 이름 붙인 「레인이 초록인 이유는 셋」의 ②). ⇒ `scan()` 은 **비교된 도형-쌍
|
|
36
|
-
수**를 항상 같이 낸다 — 0 이면 UNMEASURED 로 읽어야 한다는 뜻이다.
|
|
37
|
-
"""
|
|
38
|
-
import zipfile, re, os, collections
|
|
39
|
-
|
|
40
|
-
EMU_PT = 12700
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def shapes(z, sn):
|
|
44
|
-
"""한 슬라이드의 도형을 {이름: (x,y,cx,cy,text)} 로. 이름 중복은 대조 못 하니 뺀다."""
|
|
45
|
-
x = z.read('ppt/slides/slide%d.xml' % sn).decode('utf-8')
|
|
46
|
-
out = {}
|
|
47
|
-
for m in re.finditer(r'<p:(sp|cxnSp)>.*?</p:\1>', x, re.S):
|
|
48
|
-
b = m.group(0)
|
|
49
|
-
nm = re.search(r'name="([^"]*)"', b)
|
|
50
|
-
o = re.search(r'<a:off x="(-?\d+)" y="(-?\d+)"/><a:ext cx="(-?\d+)" cy="(-?\d+)"', b)
|
|
51
|
-
if not (nm and o):
|
|
52
|
-
continue
|
|
53
|
-
t = re.sub(r'\s+', ' ', ' '.join(re.findall(r'<a:t>([^<]*)</a:t>', b))).strip()
|
|
54
|
-
out.setdefault(nm.group(1), []).append(
|
|
55
|
-
(int(o.group(1)), int(o.group(2)), int(o.group(3)), int(o.group(4)), t))
|
|
56
|
-
return {k: v[0] for k, v in out.items() if len(v) == 1}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _order(z):
|
|
60
|
-
rels = dict(re.findall(r'Id="([^"]+)"[^>]*Target="slides/slide(\d+)\.xml"',
|
|
61
|
-
z.read('ppt/_rels/presentation.xml.rels').decode('utf-8')))
|
|
62
|
-
lst = re.search(r'<p:sldIdLst>.*?</p:sldIdLst>',
|
|
63
|
-
z.read('ppt/presentation.xml').decode('utf-8'), re.S).group(0)
|
|
64
|
-
return [int(rels[r]) for r in re.findall(r'<p:sldId id="\d+" r:id="([^"]+)"/>', lst)]
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def load_slides(path):
|
|
68
|
-
z = zipfile.ZipFile(path)
|
|
69
|
-
return [shapes(z, sn) for sn in _order(z)]
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def p1_lines(S, JIT):
|
|
73
|
-
"""build-jitter 후보. 반환: (lines, compared) — compared=0 이면 대조 대상이 아예 없었다는
|
|
74
|
-
뜻이라 «튐 0건»과 «UNMEASURED」를 갈라야 한다."""
|
|
75
|
-
lines, compared = [], 0
|
|
76
|
-
for i in range(1, len(S)):
|
|
77
|
-
for nm, cur in S[i].items():
|
|
78
|
-
prv = S[i - 1].get(nm)
|
|
79
|
-
if not prv:
|
|
80
|
-
continue
|
|
81
|
-
compared += 1
|
|
82
|
-
d = [cur[k] - prv[k] for k in range(4)]
|
|
83
|
-
mx = max(abs(v) for v in d)
|
|
84
|
-
if 0 < mx <= JIT:
|
|
85
|
-
lab = ['x', 'y', 'cx', 'cy']
|
|
86
|
-
moved = ' · '.join(f'{lab[k]} {d[k]:+d}' for k in range(4) if d[k])
|
|
87
|
-
lines.append(f" {i:>3}p→{i+1:<3}p {nm[:16]:16} {moved} ({mx/EMU_PT:.2f}pt) "
|
|
88
|
-
f"«{cur[4][:26]}»")
|
|
89
|
-
return lines, compared
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def p3_lines(S, ALN):
|
|
93
|
-
"""adjacency 후보 — A 오른쪽 끝 ↔ B 왼쪽 끝, 세로로 겹치는 띠에 있을 때만."""
|
|
94
|
-
lines = []
|
|
95
|
-
for i, sh in enumerate(S):
|
|
96
|
-
items = [(nm, v) for nm, v in sh.items() if v[2] > 0 and v[3] > 0]
|
|
97
|
-
for a in range(len(items)):
|
|
98
|
-
for b in range(len(items)):
|
|
99
|
-
if a == b:
|
|
100
|
-
continue
|
|
101
|
-
na, va = items[a]
|
|
102
|
-
nb, vb = items[b]
|
|
103
|
-
oy = min(va[1] + va[3], vb[1] + vb[3]) - max(va[1], vb[1])
|
|
104
|
-
if oy <= 0:
|
|
105
|
-
continue
|
|
106
|
-
gap = vb[0] - (va[0] + va[2]) # A 오른쪽 → B 왼쪽
|
|
107
|
-
if 0 < abs(gap) <= ALN:
|
|
108
|
-
kind = '겹침' if gap < 0 else '틈'
|
|
109
|
-
lines.append(f" {i+1:>3}p {na[:14]:14} 오른끝 → {nb[:14]:14} 왼끝 "
|
|
110
|
-
f"{kind} {abs(gap):>6} EMU ({abs(gap)/EMU_PT:.2f}pt)")
|
|
111
|
-
return lines
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def collect_lines(path, JIT, ALN):
|
|
115
|
-
"""(lines, compared) — P1+P3 합친 후보 줄과, P1 이 실제로 대조한 도형-쌍 수."""
|
|
116
|
-
S = load_slides(path)
|
|
117
|
-
l1, compared = p1_lines(S, JIT)
|
|
118
|
-
l3 = p3_lines(S, ALN)
|
|
119
|
-
return l1 + l3, compared
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def delta(base_path, cur_path, JIT, ALN):
|
|
123
|
-
"""편집 전/후 델타 — 절대 목록이 아니라 **차이**로 읽는 용법(정확했던 것). P3 는 SYS 를 안 쓰니
|
|
124
|
-
양쪽에 같은 규칙을 그대로 적용하면 된다(계기 자기결함이 P2 전용이라 여기 안 옮는다)."""
|
|
125
|
-
bl, cmp_b = collect_lines(base_path, JIT, ALN)
|
|
126
|
-
cur, cmp_c = collect_lines(cur_path, JIT, ALN)
|
|
127
|
-
b = set(bl)
|
|
128
|
-
new = [l for l in cur if l not in b]
|
|
129
|
-
gone = [l for l in bl if l not in set(cur)]
|
|
130
|
-
return new, gone, cmp_b, cmp_c
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _resolve(root, p):
|
|
134
|
-
return os.path.normpath(os.path.join(root, os.path.expanduser(p)))
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def scan(cfg, root):
|
|
138
|
-
"""preprep.py 가 부르는 레인 진입점. (findings, notes) — 🟥 **advisory 고정**:
|
|
139
|
-
호출자는 findings 를 버리고 notes 만 취한다(L8·L11 관례)."""
|
|
140
|
-
deck_s = (cfg.get('surfaces_by_id') or {}).get('built_deck')
|
|
141
|
-
if not deck_s:
|
|
142
|
-
return [], ['P1/P3 geometry : built_deck 미선언 — NOT_CONFIGURED (0 아님)']
|
|
143
|
-
deck = _resolve(root, deck_s['path'])
|
|
144
|
-
if not os.path.exists(deck):
|
|
145
|
-
return [], [f'P1/P3 geometry : built_deck 실물 없음({deck}) — UNMEASURED (0 아님)']
|
|
146
|
-
spec = cfg.get('geometry') or {}
|
|
147
|
-
JIT = int(spec.get('jitter_emu', 200000))
|
|
148
|
-
ALN = int(spec.get('align_emu', 63500))
|
|
149
|
-
base_p = spec.get('baseline')
|
|
150
|
-
notes = []
|
|
151
|
-
|
|
152
|
-
if base_p:
|
|
153
|
-
base = _resolve(root, base_p)
|
|
154
|
-
if not os.path.exists(base):
|
|
155
|
-
return [], [f'P1/P3 geometry : baseline 실물 없음({base}) — UNMEASURED (0 아님)']
|
|
156
|
-
try:
|
|
157
|
-
new, gone, cmp_b, cmp_c = delta(base, deck, JIT, ALN)
|
|
158
|
-
except Exception as e:
|
|
159
|
-
return [], [f'P1/P3 geometry : 계기 오류({type(e).__name__}: {e}) — UNMEASURED (0 아님)']
|
|
160
|
-
notes.append(f'P1/P3 geometry(델타) : 기준 {os.path.basename(base)} 대비 '
|
|
161
|
-
f'새 어긋남 {len(new)}건 · 사라진 어긋남 {len(gone)}건 🟥 advisory '
|
|
162
|
-
f'(P1 대조된 도형-쌍: 기준 {cmp_b} · 현재 {cmp_c}'
|
|
163
|
-
+ ('' if cmp_b and cmp_c else ' — 🟥 0건이면 UNMEASURED, 「튐 없음」이 아니다') + ')')
|
|
164
|
-
for l in new:
|
|
165
|
-
notes.append(' + ' + l.strip())
|
|
166
|
-
for l in gone:
|
|
167
|
-
notes.append(' - ' + l.strip())
|
|
168
|
-
return [], notes
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
lines, compared = collect_lines(deck, JIT, ALN)
|
|
172
|
-
except Exception as e:
|
|
173
|
-
return [], [f'P1/P3 geometry : 계기 오류({type(e).__name__}: {e}) — UNMEASURED (0 아님)']
|
|
174
|
-
notes.append(f'P1/P3 geometry(목록) : 후보 {len(lines)}건 — 🟥 절대 목록으로 읽지 마라, '
|
|
175
|
-
f'P3 기저 오탐이 코퍼스마다 수십 건일 수 있다(원 코퍼스 실측 76건). '
|
|
176
|
-
f'`geometry.baseline` 을 쓰는 편집-델타 용법이 정확하다 '
|
|
177
|
-
f'(P1 대조된 도형-쌍 {compared}'
|
|
178
|
-
+ ('' if compared else ' — 🟥 UNMEASURED, 「튐 없음」이 아니다') + ')')
|
|
179
|
-
for l in lines:
|
|
180
|
-
notes.append(l)
|
|
181
|
-
return [], notes
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""R1~R5 · P1/P3 self-test — known-pair 계량 + 실물 재현.
|
|
3
|
-
|
|
4
|
-
exit 0 = 전부 통과 · 1 = 실패 있음 · 2 = 픽스처 생성 불가(python-pptx 부재 등, UNMEASURED)
|
|
5
|
-
|
|
6
|
-
구성:
|
|
7
|
-
① known-pair — `fixtures/mk_slide_fixtures.py` 로 R1·R2·R4·R5·P1·P3 를 테스트 시점에 만든다
|
|
8
|
-
(R3 는 예외 — 실물 구조 2장을 줄인 `fixtures/fixture_R3_{positive,negative}.pptx` 를 쓴다.
|
|
9
|
-
선두 줄 판별이 절대좌표·srgbClr 런 구조에 기대므로, 검출기 가정대로 «생성»한 픽스처는
|
|
10
|
-
검출기 자신을 검증 못 한다 — 자기참조. `tracks-meta` 원장이 지목한 방침)
|
|
11
|
-
② --baseline 델타 모드 — 편집 전/후 한 쌍에서 새 어긋남 1건만 잡히는지
|
|
12
|
-
③ 실물 재현 — 실제 백업 pptx(환경변수 PREPREP_REAL_CORPUS 가 가리키는 디렉터리, 있을 때만)에 돌려 원장이 인용한 실사고 4+1건이
|
|
13
|
-
그대로 뜨는지 확인한다. 그 코퍼스가 없는 머신에서는 SKIP(«통과»로 세지 않는다).
|
|
14
|
-
"""
|
|
15
|
-
import os
|
|
16
|
-
import sys
|
|
17
|
-
|
|
18
|
-
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
19
|
-
sys.path.insert(0, HERE)
|
|
20
|
-
sys.path.insert(0, os.path.join(HERE, 'fixtures'))
|
|
21
|
-
|
|
22
|
-
PASS, FAIL, SKIP = 0, 0, 0
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def ok(msg):
|
|
26
|
-
global PASS
|
|
27
|
-
PASS += 1
|
|
28
|
-
print(' ✅', msg)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def ng(msg):
|
|
32
|
-
global FAIL
|
|
33
|
-
FAIL += 1
|
|
34
|
-
print(' ❌', msg)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def sk(msg):
|
|
38
|
-
global SKIP
|
|
39
|
-
SKIP += 1
|
|
40
|
-
print(' ⏭ ', msg, '— SKIPPED (PASS 아님)')
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def codes_of(deck_path, LSR):
|
|
44
|
-
deck = LSR.Deck(deck_path)
|
|
45
|
-
F = LSR.analyze(deck)
|
|
46
|
-
return F, {k for k, _, _ in F}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def run_known_pairs(fx_dir):
|
|
50
|
-
print('\n[1] known-pair — R1·R2·R4·R5·P1·P3 (생성 픽스처) + R3 (실물 픽스처)')
|
|
51
|
-
import lane_slide_relations as LSR
|
|
52
|
-
import lane_geometry as LG
|
|
53
|
-
|
|
54
|
-
# R1
|
|
55
|
-
_, cp = codes_of(os.path.join(fx_dir, 'r1_pos.pptx'), LSR)
|
|
56
|
-
_, cn = codes_of(os.path.join(fx_dir, 'r1_neg.pptx'), LSR)
|
|
57
|
-
ok('R1 known-positive 검출') if 'R1' in cp else ng(f'R1 known-positive 미검출 ({cp})')
|
|
58
|
-
ok('R1 known-negative 무검출') if 'R1' not in cn else ng(f'R1 known-negative에서 오탐 ({cn})')
|
|
59
|
-
|
|
60
|
-
# R2
|
|
61
|
-
_, cp = codes_of(os.path.join(fx_dir, 'r2_pos.pptx'), LSR)
|
|
62
|
-
_, cn = codes_of(os.path.join(fx_dir, 'r2_neg.pptx'), LSR)
|
|
63
|
-
ok('R2 known-positive 검출') if 'R2' in cp else ng(f'R2 known-positive 미검출 ({cp})')
|
|
64
|
-
ok('R2 known-negative 무검출') if 'R2' not in cn else ng(f'R2 known-negative에서 오탐 ({cn})')
|
|
65
|
-
|
|
66
|
-
# R3 — 실물 픽스처 (자기참조 회피)
|
|
67
|
-
r3p = os.path.join(HERE, 'fixtures', 'fixture_R3_positive.pptx')
|
|
68
|
-
r3n = os.path.join(HERE, 'fixtures', 'fixture_R3_negative.pptx')
|
|
69
|
-
if os.path.exists(r3p) and os.path.exists(r3n):
|
|
70
|
-
_, cp = codes_of(r3p, LSR)
|
|
71
|
-
_, cn = codes_of(r3n, LSR)
|
|
72
|
-
ok('R3 known-positive(실물 2장) 검출') if 'R3' in cp else ng(f'R3 known-positive 미검출 ({cp})')
|
|
73
|
-
ok('R3 known-negative(실물 2장) 무검출') if 'R3' not in cn else ng(f'R3 known-negative에서 오탐 ({cn})')
|
|
74
|
-
else:
|
|
75
|
-
sk('R3 known-pair — fixture_R3_{positive,negative}.pptx 없음')
|
|
76
|
-
|
|
77
|
-
# R4
|
|
78
|
-
Fp, cp = codes_of(os.path.join(fx_dir, 'r4_pos.pptx'), LSR)
|
|
79
|
-
Fn, cn = codes_of(os.path.join(fx_dir, 'r4_neg.pptx'), LSR)
|
|
80
|
-
ok('R4 known-positive 검출(아웃라이어 500자)') if 'R4' in cp else ng(f'R4 known-positive 미검출 ({cp})')
|
|
81
|
-
ok('R4 known-negative 무검출(동값 분포)') if 'R4' not in cn else ng(f'R4 known-negative에서 오탐 ({cn})')
|
|
82
|
-
|
|
83
|
-
# R5
|
|
84
|
-
Fp, cp = codes_of(os.path.join(fx_dir, 'r5_pos.pptx'), LSR)
|
|
85
|
-
Fn, cn = codes_of(os.path.join(fx_dir, 'r5_neg.pptx'), LSR)
|
|
86
|
-
ok('R5 known-positive 검출(비율 30.0)') if 'R5' in cp else ng(f'R5 known-positive 미검출 ({cp})')
|
|
87
|
-
ok('R5 known-negative 무검출(동일 비율)') if 'R5' not in cn else ng(f'R5 known-negative에서 오탐 ({cn})')
|
|
88
|
-
|
|
89
|
-
# P1 — 3팔(임계 안 소폭 이동은 뜼다 · 임계 밖 큰 이동은 안 뜼다 · 무이동은 안 뜼다)
|
|
90
|
-
lines, compared = LG.collect_lines(os.path.join(fx_dir, 'p1.pptx'), 200000, 63500)
|
|
91
|
-
p1_lines = [l for l in lines if 'gate_bar' in l]
|
|
92
|
-
ok(f'P1 대조된 도형-쌍 {compared} > 0 (비교 안함 아님)') if compared > 0 else ng('P1 compared=0 — UNMEASURED')
|
|
93
|
-
ok(f'P1 후보 정확히 1건(임계 안 이동만)') if len(p1_lines) == 1 else ng(f'P1 후보 {len(p1_lines)}건 (want 1) — {p1_lines}')
|
|
94
|
-
if p1_lines:
|
|
95
|
-
ok('P1 유일 후보가 정확히 (1→2p) 소폭 이동 — 큰 이동(2→3p)·무이동(3→4p)은 안 뜬다') \
|
|
96
|
-
if '1p→2' in p1_lines[0].replace(' ', '') else ng(f'P1 후보가 엉뚱한 쌍을 가리킨다 {p1_lines}')
|
|
97
|
-
|
|
98
|
-
# P3
|
|
99
|
-
lines3, _ = LG.collect_lines(os.path.join(fx_dir, 'p3.pptx'), 200000, 63500)
|
|
100
|
-
p3_lines = [l for l in lines3 if 'stub' in l]
|
|
101
|
-
ok('P3 후보 정확히 1건(임계 안 틀/겹침만)') if len(p3_lines) == 1 else ng(f'P3 후보 {len(p3_lines)}건 (want 1) — {p3_lines}')
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def run_baseline_delta(fx_dir):
|
|
105
|
-
print('\n[2] --baseline 델타 모드 — 편집 전/후 한 쌍')
|
|
106
|
-
import lane_geometry as LG
|
|
107
|
-
new, gone, cmp_b, cmp_c = LG.delta(os.path.join(fx_dir, 'geo_base.pptx'),
|
|
108
|
-
os.path.join(fx_dir, 'geo_edited.pptx'), 200000, 63500)
|
|
109
|
-
ok('델타 새 어긋남 정확히 1건') if len(new) == 1 else ng(f'델타 새 어긋남 {len(new)}건 (want 1)')
|
|
110
|
-
ok('델타 사라진 어긋남 0건(깨끗한 기준본)') if len(gone) == 0 else ng(f'델타 사라진 어긋남 {len(gone)}건 (want 0)')
|
|
111
|
-
ok(f'기준본 대조 {cmp_b} > 0 · 현재본 대조 {cmp_c} > 0') if cmp_b > 0 and cmp_c > 0 \
|
|
112
|
-
else ng(f'기준본/현재본 대조 0 있음 — UNMEASURED (cmp_b={cmp_b}, cmp_c={cmp_c})')
|
|
113
|
-
# scan(cfg, root) 래퍼로도 동일하게 도는지 확인 (preprep.py 가 실제로 부르는 경로)
|
|
114
|
-
cfg = {'surfaces_by_id': {'built_deck': {'path': os.path.join(fx_dir, 'geo_edited.pptx')}},
|
|
115
|
-
'geometry': {'baseline': os.path.join(fx_dir, 'geo_base.pptx')}}
|
|
116
|
-
fP, nP = LG.scan(cfg, '.')
|
|
117
|
-
ok('scan(cfg, root) 래퍼가 델타 모드로 도고 새 어긋남 1건을 notes 에 낸다') \
|
|
118
|
-
if any('새 어긋남 1건' in n for n in nP) else ng(f'scan() 델타 출력 이상 — {nP}')
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
REAL_BASE = os.environ.get('PREPREP_REAL_CORPUS', '') # 기본값 없음 — 다른 머신에서 거짓 SKIP 방지(SKIP != PASS)
|
|
122
|
-
REAL_TARGETS = [
|
|
123
|
-
('ifkakao26_slides_v1.3_pre-engine-strip_20260904_210857.pptx', 'R1', 40,
|
|
124
|
-
'선두 줄이 «그러나»로 뒤집는데'),
|
|
125
|
-
('ifkakao26_slides_v1.3_pre-40p_20260904_213452.pptx', 'R2', 41,
|
|
126
|
-
'«이 셋/세 가지»라 부르는데 번호가 없다'),
|
|
127
|
-
('ifkakao26_slides_v1.3_pre-38trim_20260904_210428.pptx', 'R4', 38,
|
|
128
|
-
'화면 250자'),
|
|
129
|
-
('ifkakao26_slides_v1.3_pre-40p_20260904_213452.pptx', 'R5', 112,
|
|
130
|
-
'29.2자/초'),
|
|
131
|
-
]
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def run_real_corpus():
|
|
135
|
-
print('\n[3] 실물 재현 (PREPREP_REAL_CORPUS 백업, 있을 때만) — 원장이 인용한 실사고 4+1건')
|
|
136
|
-
if not os.path.isdir(REAL_BASE):
|
|
137
|
-
sk(f'실물 코퍼스 미지정/미존재({REAL_BASE or "PREPREP_REAL_CORPUS unset"}) — SKIP, 통과 아님')
|
|
138
|
-
return
|
|
139
|
-
import lane_slide_relations as LSR
|
|
140
|
-
import lane_geometry as LG
|
|
141
|
-
for fname, code, want_page, want_sub in REAL_TARGETS:
|
|
142
|
-
p = os.path.join(REAL_BASE, fname)
|
|
143
|
-
if not os.path.exists(p):
|
|
144
|
-
sk(f'{fname} 없음 — 재현 건너뜀')
|
|
145
|
-
continue
|
|
146
|
-
deck = LSR.Deck(p)
|
|
147
|
-
F = LSR.analyze(deck)
|
|
148
|
-
hit = [x for x in F if x[0] == code and abs(x[1] - want_page) <= 1 and want_sub in x[2]]
|
|
149
|
-
ok(f'{code} 실물 재현 — {fname} 약 {want_page}p — {hit[0][2][:60] if hit else ""}') \
|
|
150
|
-
if hit else ng(f'{code} 실물 미재현 — {fname}')
|
|
151
|
-
|
|
152
|
-
# P1/P3 36p (보너스 확인 — 원장이 명시적으로 요구한 4건에는 없지만 신호 자체의 원적 사건)
|
|
153
|
-
align36 = os.path.join(REAL_BASE, 'ifkakao26_slides_v1.3_pre-align36_222442.pptx')
|
|
154
|
-
if os.path.exists(align36):
|
|
155
|
-
lines, compared = LG.collect_lines(align36, 200000, 63500)
|
|
156
|
-
hit_p1 = any('s5017' in l and '35000' in l and '35p' in l.replace(' ', '') for l in lines)
|
|
157
|
-
hit_p3 = any('s5016' in l and 's5017' in l and '35000' in l for l in lines)
|
|
158
|
-
ok('P1 실물 재현 — 35p→36p s5017 x -35,000') if hit_p1 else ng('P1 실물 미재현')
|
|
159
|
-
ok('P3 실물 재현 — 36p s5016→s5017 겹침 35,000') if hit_p3 else ng('P3 실물 미재현')
|
|
160
|
-
else:
|
|
161
|
-
sk('ifkakao26_slides_v1.3_pre-align36_222442.pptx 없음 — P1/P3 보너스 재현 건너뜀')
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def main():
|
|
165
|
-
try:
|
|
166
|
-
import mk_slide_fixtures
|
|
167
|
-
except ImportError as e:
|
|
168
|
-
print(f'❌ python-pptx 또는 픽스처 모듈 부재 — UNMEASURED: {e}')
|
|
169
|
-
return 2
|
|
170
|
-
import tempfile
|
|
171
|
-
fx_dir = tempfile.mkdtemp(prefix='preprep_lanes_rp_')
|
|
172
|
-
mk_slide_fixtures.build_all(fx_dir)
|
|
173
|
-
print(f'픽스처 생성: {fx_dir}')
|
|
174
|
-
|
|
175
|
-
run_known_pairs(fx_dir)
|
|
176
|
-
run_baseline_delta(fx_dir)
|
|
177
|
-
run_real_corpus()
|
|
178
|
-
|
|
179
|
-
print(f'\n결과: {PASS} passed · {FAIL} failed · {SKIP} skipped (skip != pass)')
|
|
180
|
-
return 1 if FAIL else 0
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if __name__ == '__main__':
|
|
184
|
-
sys.exit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/progression_known_negative.md
RENAMED
|
File without changes
|
/package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/progression_known_positive.md
RENAMED
|
File without changes
|
/package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/promise_known_negative.md
RENAMED
|
File without changes
|
/package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/promise_known_positive.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|