@chrono-meta/fh-gate 3.0.0 โ 3.1.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/regression/probes_live.yaml +137 -0
- package/.claude/rules/.residency-patterns.defaults +7 -0
- package/.claude/rules/fh_4axis_gate.md +50 -1
- package/.claude-plugin/marketplace.json +8 -2
- package/AGENTS.md +27 -0
- package/CATALOG.md +17 -0
- package/CLAUDE.md +12 -2
- package/README.ja.md +51 -7
- package/README.ko.md +48 -7
- package/README.md +37 -5
- package/README.zh.md +45 -8
- package/docs/STANDARDS_ALIGNMENT.md +61 -0
- package/docs/USER_GUIDE.md +3 -0
- package/docs/USE_CASES.md +50 -0
- package/docs/model_tier_expectations.md +60 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +30 -10
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +8 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
- package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
- package/knowledge/shared/harness-core/measurement-integrity-checklist.md +20 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +441 -4
- package/package.json +40 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/skills/preprep/README.md +4 -1
- package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
- package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
- package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
- package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
- package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
- package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
- package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
- package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
- package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
- package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
- package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
- package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +43 -1
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
- package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
- package/plugins/fh-qp/README.md +71 -0
- package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
- package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
- package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
- package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
- package/plugins/fh-qp/qp_profile.example.yaml +29 -0
- package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
- package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
- package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
- package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
- package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
- package/scripts/chamber_run.sh +14 -5
- package/scripts/com.forge-harness.live-eval.plist +84 -0
- package/scripts/compaction_probe.sh +9 -35
- package/scripts/directional_diff_gate.sh +14 -2
- package/scripts/frontier_digest_autopilot.sh +4 -1
- package/scripts/map_postprocess.py +90 -0
- package/scripts/outbound_query_guard.sh +131 -0
- package/scripts/outbound_query_hook.sh +373 -0
- package/scripts/package_coverage_check.sh +55 -16
- package/scripts/pipe_verdict_guard.sh +41 -1
- package/scripts/probe_live_eval.sh +240 -0
- package/scripts/probe_live_eval_lib.py +579 -0
- package/scripts/proposal_hook.sh +120 -17
- package/scripts/push_zone_check.sh +78 -0
- package/scripts/residency_closure_scan.py +252 -0
- package/scripts/selfcheck.sh +41 -1
- package/scripts/session_close_check.sh +100 -0
- package/scripts/sim_isolated_run.sh +98 -2
- package/scripts/test_action_yml_lanes.sh +97 -0
- package/scripts/test_fh_qp_lanes.sh +105 -0
- package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
- package/scripts/test_map_postprocess_lanes.sh +143 -0
- package/scripts/test_marker_affected_lanes.sh +93 -0
- package/scripts/test_marker_crossfamily_lanes.sh +90 -6
- package/scripts/test_marker_oracle_lanes.sh +136 -0
- package/scripts/test_outbound_query_hook_lanes.sh +433 -0
- package/scripts/test_outbound_query_lanes.sh +87 -0
- package/scripts/test_pipe_verdict_guard_lanes.sh +26 -0
- package/scripts/test_preprep_diagram_lanes.sh +87 -0
- package/scripts/test_preprep_drift_anchor.sh +3 -3
- package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
- package/scripts/test_probe_live_eval_lanes.sh +437 -0
- package/scripts/test_proposal_hook_lanes.sh +22 -1
- package/scripts/test_push_zone_lanes.sh +304 -0
- package/scripts/test_residency_closure_lanes.sh +70 -0
- package/scripts/test_sim_isolated_run_lanes.sh +119 -0
- package/scripts/test_utterance_intake_lanes.sh +414 -0
- package/scripts/test_worktree_reclaim_lanes.sh +70 -0
- package/scripts/transcript_utterances.py +222 -0
- package/scripts/utterance_intake.sh +424 -0
- package/scripts/validate_yaml.sh +27 -0
- package/scripts/worktree_reclaim.sh +95 -0
- package/templates/.git-hooks/pre-commit +353 -1
- package/templates/.git-hooks/pre-push +91 -0
- package/templates/RED_TEAM_REPORT.md +49 -0
- package/templates/settings.PreToolUse.snippet.json +65 -1
|
@@ -22,18 +22,46 @@ usage: interslide_deps.py <draft.md> <builder.py> [--all]
|
|
|
22
22
|
import sys, re, ast, os
|
|
23
23
|
|
|
24
24
|
# ์ดํ๋ **์ค๋ฌผ์์ ๋ฝ์๋ค**(pre-์๋ฆฝํ S9 ์๋ฌธ). ์ง์ด๋ด์ง ์์๋ค.
|
|
25
|
-
|
|
25
|
+
# ๐ฅ 2026-09-04 ์๋ฆฌ(์ ํธ ยง4) โ ๋ฐ๊นฅ ์๋ฆฌ์ ์๋ **๋งจ๋ชธ ใ๊ทธ๋๋กใ๋ฅผ ๋บ๋ค**. 121์ฅ ์ค๋ฌผ ์ฝํผ์ค์์
|
|
26
|
+
# ใ๊ฒฝ๊ณ ๊ฐ ๋ ๋ ๊ทธ๋๋ก ์น์ธํ๋คใยทใ์ ์ถ๋ณธ ๊ทธ๋๋ก ๋จธ์งใ์ฒ๋ผ **ํ๋ฒํ ๋ถ์ฌ**๋ก ํจ์ฌ ๋ ์์ฃผ ์ฐ์ฌ,
|
|
27
|
+
# L8 UNRESOLVED 9๊ฑด ์ค ๋ค์๊ฐ ์ด ๋ฑ๋ง ํ๋๊ฐ ๋ง๋ค์๋ค(์๊ฒ์ฆ โ ์ ๋ถ ์คํ). ๋งจ๋ชธ์ ๋นผ๊ณ
|
|
28
|
+
# **ํ๋ฉด์ ๊ฒฐ๋ฐ๋ ํ๊ธฐ๋ง** ๋จ๊ธด๋ค โ ใ๊ทธ๋๋กใ ์์ ํ๋ฉด์ ๊ฐ๋ฆฌํค๋ ๋ง์ด ์์ผ๋ฉด ํ๋ฉด ์ฌ์ฌ์ฉ์ด
|
|
29
|
+
# ์๋๋ผ ๊ทธ๋ฅ ๋ถ์ฌ๋ค. [[feedback_regex_bind_not_add.md]] ์ ๊ฐ์ ์๋ฆฌ(๊ฒฐ๋ฐ, ์ดํ ์ถ๊ฐ๊ฐ ์๋๋ผ).
|
|
30
|
+
SCREEN_REUSE = ('๊ฐ์ ํ๋ฉด', '์ด์ด์', 'ํ๋ฉด ์ ์ง',
|
|
31
|
+
'ํ๋ฉด ๊ทธ๋๋ก', '์ด ํ๋ฉด ๊ทธ๋๋ก', '์ผ์ชฝ ๊ทธ๋๋ก', '์ค๋ฅธ์ชฝ ๊ทธ๋๋ก')
|
|
26
32
|
DIRECTION = ('์ค๋ฅธ์ชฝ', '์ผ์ชฝ', '์์์', '์๋', '์')
|
|
27
33
|
VISUAL_VERB = ('๋ณด์
จ', '๋ณด์ ', '๋ณด์๋', '๋ณด์๋ฉด', '๋ณด์')
|
|
28
34
|
RECALL = ('๋ฐฉ๊ธ', '์๊น', '์กฐ๊ธ ์ ', '์์', '์์์', '์ง๊ธ๊น์ง')
|
|
29
35
|
SOFT_ONLY = ('๋ง์๋๋ฆฐ', '๋ง์๋๋ ธ', '์ค๋ช
๋๋ฆฐ', '๊ทธ ์ธ', '์์')
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
_DEFAULT_UNIT_PATTERN = r'^### (S[0-9]+(?:-[a-z])?)\s*ยท'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def units(src, pattern=None):
|
|
41
|
+
"""(sid, ํค๋ฉ, ๋ณธ๋ฌธ) ์์๋๋ก. ๐ซ ์ํด ํ๊ธฐ ์ ์ ยซ์ฃฝ์ ๊ฒยป์ผ๋ก ํ์ํด ๊ฐ์ด ๋ธ๋ค.
|
|
42
|
+
|
|
43
|
+
๐ฅ 2026-09-04 ์๋ฆฌ(์ ํธ ยง3-1) โ ์ดํ์ ์ ํค๋ฉ ์ ๊ท์์ **์ฌ๊ธฐ ํ๋์ฝ๋ฉ**ํ๋ค. ๊ฐ์
|
|
44
|
+
`surfaces.yaml unit_pattern` ์ `preprep.py manuscript_ids()` ๋ ์ด๋ฏธ ์ฝ๋๋ฐ ์ด ํจ์๋
|
|
45
|
+
์ ์ฝ์ด์, ยซ์ ์ ๋ฌด์์ผ๋ก ๋ณด๋ยป์ ๋ํ ๊ด๋ํจ์ด **๋ ๊ณณ์์ ๋ฐ๋ก ๊ฐ๋ ธ๋ค**
|
|
46
|
+
([[feedback_divergent_leniency_duplicate_normalizers]]). `pattern` ์ธ์๋ก ๋ฐ๋๋ค โ
|
|
47
|
+
๋ฏธ์ง์ ์ด๋ฉด ์ด ์ฝํผ์ค์ ๊ธฐ์กด ํจํด์ผ๋ก ํ์ํธํ.
|
|
48
|
+
๐ฅ ์ ๊ท์์ ๋ค์ ์ง์ง ์๋๋ค โ `manuscript_ids()`(preprep.py)์ **๊ฐ์ head/slice ๊ธฐ๋ฒ**
|
|
49
|
+
(ํค๋ฉ ์์น๋ฅผ ์ ๋ถ ์ฐพ๊ณ ๋ค์ ํค๋ฉ ์์ ์ ๊น์ง๋ฅผ ๋ณธ๋ฌธ์ผ๋ก ์ฌ๋ผ์ด์ค)์ ์ด๋ค. lookahead ๋ก
|
|
50
|
+
"๋ค์ ํค๋ฉ" ์ ๋ค์ ์ ์ํ๋ฉด ๊ทธ ์์ฒด๊ฐ ์ธ ๋ฒ์งธ ๊ฐ๋๊ฐ ๋๋ค.
|
|
51
|
+
`pattern`: ์ ํค๋ฉ์ ์ฌ๋ ์ ๊ท์, **์บก์ฒ ๊ทธ๋ฃน ํ๋**(์ id)๋ง ๊ฐ์ง๋ค
|
|
52
|
+
(์: r'^###\\s+(S[0-9]+(?:-[a-z])?)\\s*ยท').
|
|
53
|
+
"""
|
|
54
|
+
pat = pattern or _DEFAULT_UNIT_PATTERN
|
|
33
55
|
out = []
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
heads = list(re.finditer(pat + r'([^\n]*)', src, re.M))
|
|
57
|
+
for i, m in enumerate(heads):
|
|
58
|
+
end = heads[i + 1].start() if i + 1 < len(heads) else len(src)
|
|
59
|
+
head_line = m.group(2)
|
|
60
|
+
body = src[m.end():end]
|
|
61
|
+
if body.startswith('\n'):
|
|
62
|
+
body = body[1:] # ํค๋ฉ ์ค ์์ ์ ๊ฐํ โ ์ ๊ตฌํ์ ๋ณธ๋ฌธ์ ์ ๋ด์๋ค(๋์น ์ ์ง)
|
|
63
|
+
retired = any(t in head_line for t in ('๐ซ', '์ ๊ฑฐ๋จ', 'ํ๊ธฐ'))
|
|
64
|
+
out.append((m.group(1), head_line.strip(), body, retired))
|
|
37
65
|
return out
|
|
38
66
|
|
|
39
67
|
# ๐ฅ ๋ฐ #14 ์์ ์ด๋ฏธ ํ ๋ฒ ๊ฒช์ ๊ฒฐํจ์ด ์ฌ๊ธฐ์ ์ฌํ๋๋ค: ์๊ณ ์ **์ฃผ์ ๋ธ๋ก**๋ `>` ๋ผ์
|
|
@@ -107,7 +135,12 @@ def referents(line):
|
|
|
107
135
|
t = re.sub(DEICTIC_PREFIX, '', m.group(1).strip(' .,ยทใใยซยป')).strip()
|
|
108
136
|
# ์ง์์ด๋ง ๋ฒ๊ธฐ๋ฉด ๋จ๋ ๋ง์ด ๋๋ฌด ์งง๊ฑฐ๋ ํํ๋ฉด ๋์์ด ๋ชป ๋๋ค
|
|
109
137
|
if len(t) >= 2: cands.append(t)
|
|
110
|
-
|
|
138
|
+
# ๐ฅ 2026-09-04 ์๋ฆฌ(์ ํธ ยง4) โ ์ดํ์ `[๊ทธ์ ]` ๋ก **ใ์ ใ๋ ์ง์์ด**๋ก ์ก์๋ค. ํ๊ตญ์ด
|
|
139
|
+
# ์กด๋๋ง 1์ธ์นญ ใ์ ํฌใ๊ฐ ใ์ + ํฌโฆใ๋ก ๋ฌผ๋ ค ๋์์ด๊ฐ ยซํฌ ํ๋ค์ค์ ํ์ฉ ๊ตฌ์กฐ์
ยป ๊ฐ์
|
|
140
|
+
# ํํธ์ด ๋๋ค(์ค์ธก: S25). ใ์ ใ๋ ์ง์์ด(์ ๊ฒ ยท ์ ์ฌ๋)์ 1์ธ์นญ ๊ฒธ์์ด(์ ยท ์ ํฌ)๋ฅผ
|
|
141
|
+
# **๊ฐ์ ๊ธ์๋ก ๊ฒธํ๋ ๋ค์์ด**๋ผ ์ด ์ ๊ท์ ์๋ฆฌ์์ ์๋ฆฌ์ ์ผ๋ก ๊ฐ๋ฆฌ์ง ์๋๋ค โ ์ง์์ด
|
|
142
|
+
# ์ชฝ๋ง ์ํ๋ฉด ใ๊ทธใ๋ง ๋จ๊ธฐ๊ณ ใ์ ใ๋ ๋บ๋ค(๋์น๋ ์ง์์ด๋ณด๋ค ์กด๋๋ง ์คํ์ด ๋ ํํ๋ค).
|
|
143
|
+
for m in re.finditer(r'๊ทธ\s*([๊ฐ-ํฃA-Za-z][๊ฐ-ํฃA-Za-z0-9 ]{1,12})', line):
|
|
111
144
|
cands.append(m.group(1).strip())
|
|
112
145
|
return [c for c in dict.fromkeys(cands) if c not in COMMON and len(c) >= 2]
|
|
113
146
|
|
|
@@ -141,15 +174,16 @@ def wired_ids(builder):
|
|
|
141
174
|
return {v[0] for v in ast.literal_eval(src[start:i+1]).values() if v[0]}
|
|
142
175
|
raise RuntimeError('NOTE_MAP ๊ดํธ ๋ถ๊ท ํ')
|
|
143
176
|
|
|
144
|
-
def analyze(draft, builder, show_all=False):
|
|
177
|
+
def analyze(draft, builder, show_all=False, pattern=None):
|
|
145
178
|
"""๊ณ์ฐ๋ถ โ ์ถ๋ ฅํ์ง ์๋๋ค. ๋ ์ธ ๋ฐฐ์ ์ด ์ด๊ฑธ ๋ถ๋ฅธ๋ค(main ์ ์ด๊ฑธ ยซ์ฐ๊ธฐ๋งยป ํ๋ค).
|
|
146
179
|
|
|
147
180
|
๐ฅ ๋ก์ง์ ๋ณต์ ํ์ง ์์ผ๋ ค๊ณ ๋บ ๊ฒ์ด๋ค. CLI ์ ๋ ์ธ์ด ๊ฐ์ ๊ณ์ฐํ๋ฉด
|
|
148
181
|
๊ด๋ํจ์ด ๊ฐ๋ ค์ ํ์ชฝ๋ง ํต๊ณผํ๋ ์
๋ ฅ์ด ์กฐ์ฉํ ์๊ธด๋ค.
|
|
182
|
+
`pattern`: ์๊ณ ํ๋ฉด์ `unit_pattern` ์ ์ธ(์์ผ๋ฉด) โ units() ๋ก ๊ทธ๋๋ก ์ ๋ฌํ๋ค.
|
|
149
183
|
๋ฐํ: dict(error, n_units, n_retired, n_wired, edges, broken, unresolved)
|
|
150
184
|
"""
|
|
151
185
|
try:
|
|
152
|
-
us = units(open(draft, encoding='utf-8').read())
|
|
186
|
+
us = units(open(draft, encoding='utf-8').read(), pattern=pattern)
|
|
153
187
|
wired = wired_ids(builder)
|
|
154
188
|
except Exception as e:
|
|
155
189
|
return {'error': f'{e}', 'edges': [], 'broken': [], 'unresolved': []}
|
|
@@ -188,7 +222,62 @@ def analyze(draft, builder, show_all=False):
|
|
|
188
222
|
'n_wired': len(wired), 'edges': edges, 'broken': broken, 'unresolved': unresolved}
|
|
189
223
|
|
|
190
224
|
|
|
225
|
+
def selftest():
|
|
226
|
+
"""known-pair โ ์ ํธ ยง3-1ยทยง4 ์๋ฆฌ๊ฐ ์ค์ ๋ก ์ง์ ๊ฐ๋ฅด๋์ง. ์ ๊ฐ๋ฅด๋ฉด ์ฅ์์ด๋ค."""
|
|
227
|
+
ok = True
|
|
228
|
+
|
|
229
|
+
# โโ L8 ยง4-a: ใ์ ใ๋ฅผ ์ง์์ด ์ ๊ท์์์ ๋บ๋ค โ ์กด๋๋ง ใ์ ํฌใ์คํ ์ฌ๋ฐ ๋ฐฉ์ง โโโโโโโโโโ
|
|
230
|
+
cases_ref = [
|
|
231
|
+
('known-positive(์กด๋๋ง ใ์ ํฌใ โ ์ง์ ๋์์ด๋ฉด ์ ๋๋ค)',
|
|
232
|
+
'์ ํฌ ํ๋ค์ค์ ํ์ฉ ๊ตฌ์กฐ์
๋๋ค . ๋ณด์๋ ๊ฒ์ฒ๋ผ ์ธ ์ธต์
๋๋ค .', False),
|
|
233
|
+
('known-negative(์ง์ง ์ง์์ด ใ๊ทธใ โ ์ฌ์ ํ ์กํ์ผ ํ๋ค)',
|
|
234
|
+
'๊ทธ ํ๋ค์ค๋ ์ด๋ ๊ฒ ๋์ํฉ๋๋ค .', True),
|
|
235
|
+
]
|
|
236
|
+
for name, line, want_hit in cases_ref:
|
|
237
|
+
got = referents(line)
|
|
238
|
+
hit = bool(got) == want_hit
|
|
239
|
+
ok &= hit
|
|
240
|
+
print(f' {"PASS" if hit else "FAIL"} referents {name}: {got}')
|
|
241
|
+
|
|
242
|
+
# โโ L8 ยง4-b: SCREEN_REUSE ๋งจ๋ชธ ใ๊ทธ๋๋กใ๋ฅผ ๋บ๋ค โ ํ๋ฉด ๊ฒฐ๋ฐ ํ๊ธฐ๋ง ๋จ๋๋ค โโโโโโโโโโ
|
|
243
|
+
cases_scr = [
|
|
244
|
+
('known-positive(ํ๋ฒํ ๋ถ์ฌ โ ํ๋ฉด ์ฌ์ฌ์ฉ ์๋๋ค)', '๊ฒฝ๊ณ ๊ฐ ๋ ๋ ๊ทธ๋๋ก ์น์ธํ๋ค', False),
|
|
245
|
+
('known-positive(๋ค๋ฅธ ๋ฌธ๋งฅ์ ๋ถ์ฌ)', '์ ์ถ๋ณธ ๊ทธ๋๋ก ๋จธ์ง', False),
|
|
246
|
+
('known-negative(ํ๋ฉด ๊ฒฐ๋ฐ โ ์ฌ์ ํ ์กํ์ผ ํ๋ค)', '์ผ์ชฝ ๊ทธ๋๋ก', True),
|
|
247
|
+
('known-negative(ํ๋ฉด ๊ฒฐ๋ฐ ๋ณํ)', '์ด ํ๋ฉด ๊ทธ๋๋ก ๋๋ฉด ๋ฉ๋๋ค', True),
|
|
248
|
+
]
|
|
249
|
+
for name, line, want_hit in cases_scr:
|
|
250
|
+
got = any(c in line for c in SCREEN_REUSE)
|
|
251
|
+
hit = got == want_hit
|
|
252
|
+
ok &= hit
|
|
253
|
+
print(f' {"PASS" if hit else "FAIL"} SCREEN_REUSE {name}: hit={got}')
|
|
254
|
+
|
|
255
|
+
# โโ ยง3-1: units() ๊ฐ pattern ์ธ์๋ฅผ ๋ฐ๊ณ , ๋ฏธ์ง์ ์ ํ์ํธํ โโโโโโโโโโโโโโโโโโโโโโ
|
|
256
|
+
src_default = '### S1 ยท ์ ๋ชฉ\n๐ฃ\n> ๋ณธ๋ฌธ\n\n### S2 ยท ๋์งธ\n๐ฃ\n> ๋ณธ๋ฌธ2\n'
|
|
257
|
+
default_ids = [u[0] for u in units(src_default)]
|
|
258
|
+
ok3 = default_ids == ['S1', 'S2']
|
|
259
|
+
ok &= ok3
|
|
260
|
+
print(f' {"PASS" if ok3 else "FAIL"} units() ๊ธฐ๋ณธ ํจํด ํ์ํธํ: {default_ids}')
|
|
261
|
+
|
|
262
|
+
src_custom = '### U1 ยท ์ ๋ชฉ\n๐ฃ\n> ๋ณธ๋ฌธ\n\n### U2 ยท ๋์งธ\n๐ฃ\n> ๋ณธ๋ฌธ2\n'
|
|
263
|
+
custom_ids = [u[0] for u in units(src_custom, pattern=r'^###\s+(U[0-9]+)\s*ยท')]
|
|
264
|
+
ok4 = custom_ids == ['U1', 'U2']
|
|
265
|
+
ok &= ok4
|
|
266
|
+
print(f' {"PASS" if ok4 else "FAIL"} units() ์ปค์คํ
unit_pattern ์์ฉ: {custom_ids}')
|
|
267
|
+
# ๊ธฐ๋ณธ ํจํด์ผ๋ก๋ U1/U2 ์ฝํผ์ค๋ฅผ ๋ชป ์ฝ์ด์ผ ํ๋ค โ ยซํญ์ ํต๊ณผยปํ๋ ๊ณ๊ธฐ๊ฐ ์๋์ ํ์ธ
|
|
268
|
+
default_on_custom = units(src_custom)
|
|
269
|
+
ok5 = default_on_custom == []
|
|
270
|
+
ok &= ok5
|
|
271
|
+
print(f' {"PASS" if ok5 else "FAIL"} ๊ธฐ๋ณธ ํจํด์ U-์ ๋๋ฅผ ๋ชป ์ฝ๋๋ค(๊ณ๊ธฐ๊ฐ ์ง์ง pattern ์ ์): '
|
|
272
|
+
f'{len(default_on_custom)}๊ฑด')
|
|
273
|
+
|
|
274
|
+
print('interslide_deps SELFTEST:', 'PASS' if ok else 'FAIL โ ์ด ๊ณ๊ธฐ์ ์ถ๋ ฅ์ ์ฐ์ง ๋ง๋ผ')
|
|
275
|
+
return 0 if ok else 1
|
|
276
|
+
|
|
277
|
+
|
|
191
278
|
def main():
|
|
279
|
+
if '--self-test' in sys.argv:
|
|
280
|
+
return selftest()
|
|
192
281
|
if len(sys.argv) < 3: print(__doc__); return 2
|
|
193
282
|
draft, builder = sys.argv[1], sys.argv[2]
|
|
194
283
|
show_all = '--all' in sys.argv
|
|
@@ -47,7 +47,10 @@ def scan(cfg, texts, surf_meta, mod):
|
|
|
47
47
|
findings, notes = [], []
|
|
48
48
|
for sid in sorted(spoken):
|
|
49
49
|
text = texts[sid][0]
|
|
50
|
-
|
|
50
|
+
# ๐ฅ 2026-09-04 โ units() ๊ฐ pattern ์ ๋ฐ๋๋ค. ์ด ํ๋ฉด์ unit_pattern ์ ์ธ์ ๋๊ธด๋ค
|
|
51
|
+
# (interslide_deps ์ ํธ ยง3-1 ๊ณผ ๊ฐ์ ์๋ฆฌ โ ๋ ๊ณณ์ด ๊ฐ์ ๊ด๋ํจ์ ๊ฐ์ง ์๊ฒ ํ๋ค).
|
|
52
|
+
_pat = surf_meta.get(sid, {}).get('unit_pattern')
|
|
53
|
+
us = [u for u in mod.units(text, pattern=_pat) if not u[3]] # ์ํด ์ ์ ์ธ
|
|
51
54
|
if len(us) < 2:
|
|
52
55
|
notes.append(f'L10 adjacent-dup : {sid} โ ์ {len(us)}๊ฐ, ์ธ์ ์ ์์ (0 ์๋)')
|
|
53
56
|
continue
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""L12 diagram โ **๋ํด๊ฐ ยซ์ง๊ธ JSONยป ์์ ๊ฒ์ฆ์ ๊ฑฐ์ณ ๊ตฌ์์ก๋.**
|
|
2
|
+
|
|
3
|
+
## ์กด์ฌ ๊ทผ๊ฑฐ (2026-09-05, archify ร preprep ๊ฒฐํฉ ์ค์ธก)
|
|
4
|
+
|
|
5
|
+
๋ํด๋ฅผ ํ์
์๋ JSON โ ์ธ๋ถ ๋ ๋๋ฌ(archify) โ PNG โ pptx ๋ก ๋ฃ๋ ๊ฒฝ๋ก๊ฐ ๋ซ๋ ธ๋ค. ๊ทธ ๊ฒฝ๋ก์ ์ฌ๊ฐ์
|
|
6
|
+
์
์ด๊ณ , ์
๋ค ยซPNG ๊ฐ ์๋คยป๋ก๋ ์ ๋ณด์ธ๋ค:
|
|
7
|
+
โ JSON ์ ๊ณ ์ณค๋๋ฐ PNG ๋ ์ ๊ฒ(์ฌ์์ฑ ์ ํจ) โ ๋น๋๋ ์ฑ๊ณต, ๊ทธ๋ฆผ์ ๊ฑฐ์ง
|
|
8
|
+
โก validate ๋ฅผ ๋ชป ํต๊ณผํ JSON ์ ์์ผ๋ก ๋ ๋ํด ๋ฃ์ โ ๊ฒน์นจยท๋ผ๋ฒจ ๋ง์คํน์ด ํ๋ฉด์
|
|
9
|
+
โข ํฝ์
์ ์ถฉ๋ถํ๋ฐ viewBox ๊ฐ ๋์ด ๊ธ์๊ฐ 12pt ๋ก ๋จ์ด์ง โ ยซํด์๋ OKยป ์ธ๋ฐ ๋ชป ์ฝ๋๋ค
|
|
10
|
+
|
|
11
|
+
## ๋ฌด์์ ์ฌ๊ณ ๋ฌด์์ ์ ์ฌ๋
|
|
12
|
+
|
|
13
|
+
- ์ฐ๋ค โ `kind: diagram_source` ํ๋ฉด๋ง๋ค `render:` PNG ์์ **๊ตฝ๊ธฐ ์์์ฆ**(`*.receipt.json`,
|
|
14
|
+
`diagram_from_json.py` ๊ฐ ์ด๋ค)์ ์ฝ์ด: ์์์ฆ ์กด์ฌ ยท JSON sha ์ผ์น ยท validate ok(showcase) ยท
|
|
15
|
+
PNG ์ค์ ํญ(IHDR ์ ์ง์ ์ฝ๋๋ค โ ์์์ฆ ์๊ธฐ์ ๊ณ ๊ฐ ์๋๋ค) โฅ min_px ยท viewBox ํญ โค max ยท
|
|
16
|
+
pad โฅ min.
|
|
17
|
+
- ์ ์ฐ๋ค โ ๊ทธ๋ฆผ์ด ยซ์ข์๊ฐยป. ๊ทธ๊ฑด ยง๋ฐฉ๋ฒ๋ก โ ยซ๋ํด๋ฅผ ๊ฑด๋๋ ธ์ผ๋ฉด ๋ ๋ํ๋คยป ์ ์ฌ๋ ๋ชซ์ด๋ค.
|
|
18
|
+
- ๐ฅ ์์์ฆ์ด ์๋ PNG(์์ผ๋ก ๋ฃ์ ๊ทธ๋ฆผ)๋ **UNMEASURED ๋ก ๋ธ๋ค** โ 0 ์ด ์๋๊ณ , ํต๊ณผ๋ ์๋๋ค.
|
|
19
|
+
์ด ๋ ์ธ์ ยซ์์ฑ๊ธฐ๋ฅผ ๊ฑฐ์น ๊ทธ๋ฆผยป๋ง ๋ณธ๋ค. ์ฌ๊ฐ์ ์ข์์ง์ง ์ข์์ง์ง ๋ซํ์ง ์๋๋ค(SKILL.md ยง๊ฐ๋ผ ์ ๋ ๊ฒ).
|
|
20
|
+
|
|
21
|
+
## ์ฐจ๋จ์ธ ์ด์
|
|
22
|
+
์ ์ธ๋ ํ๋ฉด์ ํํด ยซ์ง๋ฌธ์ด ๋ค๋ฅด๋ค / validate ๊ฐ ์๋๋ค / ํญ์ด ๋ชจ์๋๋คยป ๋ ์ ๋งคํ์ง ์์ ์ฌ์ค์ด๋ค.
|
|
23
|
+
์คํ์ ์ต์ ๋น์ฉ ๋ฌด๋ง๋ ยซ๋ค์ ๊ตฝ๋ ๊ฒยป์ด๋ผ ์๊ณ ๋ฅผ ๋ง๊ฐ๋จ๋ฆฌ๋ ๋ฐฉํฅ์ด ์๋๋ค.
|
|
24
|
+
"""
|
|
25
|
+
import hashlib, json, os, struct
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _sha(p):
|
|
29
|
+
h = hashlib.sha256(); h.update(open(p, 'rb').read()); return h.hexdigest()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _png_w(p):
|
|
33
|
+
with open(p, 'rb') as f: head = f.read(24)
|
|
34
|
+
if head[:8] != b'\x89PNG\r\n\x1a\n': raise ValueError('PNG ์๋')
|
|
35
|
+
return struct.unpack('>II', head[16:24])[0]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _resolve(root, p):
|
|
39
|
+
return os.path.normpath(os.path.join(root, os.path.expanduser(p)))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def scan(cfg, root):
|
|
43
|
+
"""(findings, notes). findings ํํ = (sid, kind, ref, lit, why) โ preprep.py ์ USE ๊ณ์ด."""
|
|
44
|
+
surfs = [s for s in cfg.get('surfaces', []) if s.get('kind') == 'diagram_source']
|
|
45
|
+
if not surfs:
|
|
46
|
+
return [], ['L12 diagram : diagram_source ํ๋ฉด ์์ โ NOT_CONFIGURED (0 ์๋)']
|
|
47
|
+
spec = cfg.get('diagram') or {}
|
|
48
|
+
min_px = int(spec.get('min_px', 3840)); max_vb = float(spec.get('max_viewbox_w', 720)); min_pad = float(spec.get('min_pad', 0.02))
|
|
49
|
+
findings, notes = [], []
|
|
50
|
+
n_ok = n_unm = 0
|
|
51
|
+
for s in surfs:
|
|
52
|
+
sid = s['id']; jp = _resolve(root, s['path'])
|
|
53
|
+
if not os.path.exists(jp):
|
|
54
|
+
notes.append(f'L12 diagram : {sid} JSON ์์({jp}) โ UNMEASURED (0 ์๋)'); n_unm += 1; continue
|
|
55
|
+
rp_png = s.get('render')
|
|
56
|
+
if not rp_png:
|
|
57
|
+
notes.append(f'L12 diagram : {sid} render(PNG) ๋ฏธ์ ์ธ โ UNMEASURED (0 ์๋)'); n_unm += 1; continue
|
|
58
|
+
png = _resolve(root, rp_png); rec = png[:-4] + '.receipt.json' if png.endswith('.png') else png + '.receipt.json'
|
|
59
|
+
if not os.path.exists(png):
|
|
60
|
+
findings.append((sid, 'diagram', 'render', os.path.basename(png), 'L12 ์ ์ธ๋ PNG ๊ฐ ๋์คํฌ์ ์๋ค โ ๊ตฝ์ง ์์๊ฑฐ๋ ๊ฒฝ๋ก๊ฐ ํ๋ ธ๋ค')); continue
|
|
61
|
+
if not os.path.exists(rec):
|
|
62
|
+
notes.append(f'L12 diagram : {sid} ๊ตฝ๊ธฐ ์์์ฆ ์์({os.path.basename(rec)}) โ ์์ผ๋ก ๋ฃ์ ๊ทธ๋ฆผ์ ์ด ๋ ์ธ์ด ๋ชป ๋ณธ๋ค. UNMEASURED (0 ์๋)'); n_unm += 1; continue
|
|
63
|
+
try: r = json.load(open(rec, encoding='utf-8'))
|
|
64
|
+
except Exception as e:
|
|
65
|
+
findings.append((sid, 'diagram', 'receipt', os.path.basename(rec), f'L12 ์์์ฆ ํ์ฑ ์คํจ({type(e).__name__}) โ ์ฑ๋์ด ๊นจ์ก๋ค')); continue
|
|
66
|
+
bad = False
|
|
67
|
+
if r.get('spec_sha256') != _sha(jp):
|
|
68
|
+
findings.append((sid, 'diagram', 'stale', os.path.basename(png), 'L12 JSON ์ง๋ฌธ์ด ์์์ฆ๊ณผ ๋ค๋ฅด๋ค โ JSON ์ ๊ณ ์น ๋ค ๋ค์ ๊ตฝ์ง ์์๋ค')); bad = True
|
|
69
|
+
if not (r.get('validate') or {}).get('ok') or (r.get('validate') or {}).get('profile') != 'showcase':
|
|
70
|
+
findings.append((sid, 'diagram', 'validate', os.path.basename(png), 'L12 showcase validate ํต๊ณผ ๊ธฐ๋ก์ด ์๋ค โ ๊ฒ์ฆ ์ ๋ ๊ทธ๋ฆผ')); bad = True
|
|
71
|
+
try: w = _png_w(png)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
findings.append((sid, 'diagram', 'png', os.path.basename(png), f'L12 PNG ํค๋ ์ฝ๊ธฐ ์คํจ({e})')); continue
|
|
74
|
+
if w < min_px:
|
|
75
|
+
# ์๋ฅธ(crop) PNG ๋ ์บ๋ฒ์ค๋ณด๋ค ์ข๋ค โ ์์์ฆ์ png_size ๊ฐ ์๋๋ผ ยซcrop ์ ์บ๋ฒ์คยป๊ฐ ํํ์ ๋๊ฒผ๋์ง ๋ณธ๋ค.
|
|
76
|
+
# ๐ฅ ์บ๋ฒ์ค ํญ์ ์์์ฆ ์๊ธฐ์ ๊ณ ๋ค. IHDR ์ค์ธก(w)๊ณผ ํจ๊ป ๋ ๋ค ์ ์ด ๊ฐ์ฌ์๊ฐ ๊ฐ๋ฅด๊ฒ ํ๋ค.
|
|
77
|
+
canvas_w = None
|
|
78
|
+
cv = r.get('canvas')
|
|
79
|
+
if isinstance(cv, str) and 'x' in cv.lower() and cv.lower() != 'auto':
|
|
80
|
+
canvas_w = int(cv.lower().split('x')[0])
|
|
81
|
+
elif r.get('crop'):
|
|
82
|
+
canvas_w = min_px if r.get('png_size') else None # auto ์บ๋ฒ์ค๋ ์คํฌ๋ฆฝํธ๊ฐ ํํ ์ด์์ผ๋ก๋ง ๋ง๋ ๋ค
|
|
83
|
+
if not (r.get('crop') and canvas_w and canvas_w >= min_px):
|
|
84
|
+
findings.append((sid, 'diagram', 'resolution', os.path.basename(png), f'L12 PNG ํญ {w} < ํํ {min_px} (์ฅํ 2ร)')); bad = True
|
|
85
|
+
else:
|
|
86
|
+
notes.append(f'L12 diagram : {sid} ์๋ฅธ PNG ํญ {w}(IHDR) โ ์บ๋ฒ์ค {canvas_w}(์์์ฆ ์๊ธฐ์ ๊ณ ) ๋ก ํํ ํ์ ')
|
|
87
|
+
vbw = r.get('viewbox_w')
|
|
88
|
+
if vbw is None or float(vbw) > max_vb:
|
|
89
|
+
findings.append((sid, 'diagram', 'viewbox', os.path.basename(png), f'L12 viewBox ํญ {vbw} > ์ํ {max_vb:.0f} โ ๊ธ์ ๋ฐฐ์จ ๋ฏธ๋ฌ(๋ผ๋ฒจโ{12*1728/float(vbw or 1):.0f}pt)')); bad = True
|
|
90
|
+
if float(r.get('pad', 0)) < min_pad:
|
|
91
|
+
findings.append((sid, 'diagram', 'pad', os.path.basename(png), f'L12 ์ฌ๋ฐฑ {r.get("pad")} < ํํ {min_pad}')); bad = True
|
|
92
|
+
if not bad: n_ok += 1
|
|
93
|
+
notes.append(f'L12 diagram : ํ๋ฉด {len(surfs)} ยท ํต๊ณผ {n_ok} ยท UNMEASURED {n_unm} ยท ๋ฐ๊ฒฌ {len(findings)} '
|
|
94
|
+
f'(ํํ {min_px}px ยท viewBoxโค{max_vb:.0f} ยท padโฅ{min_pad})')
|
|
95
|
+
return findings, notes
|
|
@@ -0,0 +1,181 @@
|
|
|
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
|
|
@@ -74,7 +74,10 @@ def scan(texts, surf_meta, max_report=8, mod=None):
|
|
|
74
74
|
text = texts[sid][0]
|
|
75
75
|
if mod is not None:
|
|
76
76
|
speech = []
|
|
77
|
-
|
|
77
|
+
# ๐ฅ 2026-09-04 โ units() ๊ฐ ์ด์ pattern ์ ๋ฐ๋๋ค. ์ด ํ๋ฉด์ surfaces.yaml
|
|
78
|
+
# unit_pattern ์ ์ธ์ ๊ทธ๋๋ก ๋๊ธด๋ค(interslide_deps ์ ํธ ยง3-1 ๊ณผ ๊ฐ์ ์๋ฆฌ).
|
|
79
|
+
_pat = surf_meta.get(sid, {}).get('unit_pattern')
|
|
80
|
+
for _uid, _h, body, _r in mod.units(text, pattern=_pat):
|
|
78
81
|
_sc, sp = mod.blocks(body)
|
|
79
82
|
speech.append(sp if isinstance(sp, str) else '\n'.join(sp))
|
|
80
83
|
text = '\n'.join(speech) or text
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""L13 โ ยซN ํ์ด์งยป ๋ฆฌํฐ๋ด ์ฅ ๋ฒํธ ์ฐธ์กฐ๊ฐ ์ค์ฌํ๋ ์ฅ์ ๊ฐ๋ฆฌํค๋ (2026-09-06 ์ ์ค).
|
|
3
|
+
|
|
4
|
+
WHY: ์ฅ์ ํ๋ ๋นผ๋ฉด ๊ทธ ๋ค ๋ฒํธ๊ฐ ํต์งธ๋ก ๋น๊ฒจ์ง๋ค. preprep ์ ์ด์ ๋ ์ธ๋ค์ **๋ง๋ก ๊ฐ๋ฆฌํค๋
|
|
5
|
+
๊ฒ**๋ง ๋ณธ๋ค โ L5/L8 ์ ํ๋ฉด ์ฌ์ฌ์ฉยท์๊ฐ ์ง์๋ฅผ, R1~R5 ๋ ์ ์์ดยท์์ ๊ถ์. **์ซ์๋ก ๊ฐ๋ฆฌํค๋
|
|
6
|
+
๊ฒ์ ์ด๋ ๋ ์ธ๋ ์ ๋ดค๋ค.** ์ค์ฌ๊ณ ๊ณ๊ธฐ: if(kakao) ๋ฑ์์ 42์ฅ์ ๋นผ์ 43~121 ์ด ํ ์นธ์ฉ
|
|
7
|
+
๋น๊ฒจ์ก๊ณ , ใ๊นจ์ง ์ฐธ์กฐ๊ฐ ์ด๋๋ใ๋ฅผ ์์ผ๋ก ํ์ด์ผ ํ๋ค(2026-09-06, ์ด์์ ์ง์ ).
|
|
8
|
+
|
|
9
|
+
๐ฅ ์ด ๋ ์ธ์ ๋์ ์ ๊ฒ์ถ์ด ์๋๋ผ **๋ถ๋ฅ**๋ค. ๊ฐ์ ยซ์ซ์+๋จ์ยป๊ฐ ์ธ ๊ฐ์ง ๋ค๋ฅธ ๊ฒ์ ๊ฐ๋ฆฌํจ๋ค โ
|
|
10
|
+
์ค์ธก ์ฝํผ์ค(๊ทธ ๋ฑ 121์ฅ)์์ ์
์ด **์ ๋ถ** ๋์๋ค:
|
|
11
|
+
|
|
12
|
+
SLIDE ใ40p ๋ถ์ ๊ฐ โฆใ โ ์ง์ง ์ฅ ์ฐธ์กฐ. ์ด๊ฒ๋ง ๊ฒ์ฌ ๋์์ด๋ค
|
|
13
|
+
SECTION ใ3์ฅ์์ ๊ฒ์ดํธ๋ถํฐใ โ ์ฌ๋ผ์ด๋๊ฐ ์๋๋ผ **์น์
**(๊ทธ ๋ฑ์ sec-guide ใ3. ์ด๋ป๊ฒ ๋ง๋ค์๋ใ)
|
|
14
|
+
CITATION ใarXiv:2606.05647 (4p)ใ โ ์ธ์ฉ **๋
ผ๋ฌธ ์ชฝ์**
|
|
15
|
+
|
|
16
|
+
์
์ ์ ๊ฐ๋ฅด๋ฉด ์คํ ๋ฉ์ด๋ฆฌ๊ฐ ๋๋ค. ๊ทธ๋์ ํ๋ณ์๋ ์ฝํผ์ค์์ ๊ธฐ๊ณ๋ก ๋ฝ๋๋ค:
|
|
17
|
+
ยท SECTION โ ๋ฑ์ ์น์
๋ผ๋ฒจ(ใN. ์ ๋ชฉใ)์์ N ์งํฉ์ ๋ชจ์ผ๊ณ , ใN์ฅใ ํํ์ด๋ฉด์ ๊ทธ ์งํฉ์
|
|
18
|
+
์ํ๋ฉด ์น์
์ด๋ค. ๋ผ๋ฒจ์ด ์์ผ๋ฉด ์ด ๋ถ๊ธฐ๋ **๋นํ์ฑ**์ด๊ณ ๊ทธ ์ฌ์ค์ ๋
ธํธ์ ์ ๋๋ค.
|
|
19
|
+
ยท CITATION โ ๊ฐ์ ์ค์ ์ธ์ฉ ํ์ง(arXiv/doi/ใใ/pp./4์๋ฆฌ ์ฐ๋)๊ฐ ์์ผ๋ฉด ์ธ์ฉ ์ชฝ์๋ค.
|
|
20
|
+
|
|
21
|
+
๐ฅ **ํ์ ์ ยซ์กด์ฌยป๋ง ํ๋ค** โ ยซ๊ทธ ๋ฒํธ๊ฐ ๋ง๋ ์ฅ์ ๊ฐ๋ฆฌํค๋ยป๋ ์๋ฏธ ํ๋จ์ด๋ผ ์ ๋ณธ๋ค
|
|
22
|
+
(ยงMechanization Boundary: ๊ธฐ๋ก์ ์์ฑ๋ง ๋จ์ธํ๋ค). ๊ทธ๋์ finding ์ **๋ฒ์ ๋ฐ ์ฐธ์กฐ**
|
|
23
|
+
ํ๋๋ฟ์ด๊ณ , ์๊ธฐ ์์ ์ฐธ์กฐยท์ ์ ์ฐธ์กฐ๋ ๋
ธํธ๋ก๋ง ๋๊ฐ๋ค.
|
|
24
|
+
|
|
25
|
+
๐ฅ **์ฐธ์กฐ 0๊ฑด์ ยซํต๊ณผยป๊ฐ ์๋๋ค** โ ์ถ์ถ์ด ์ฃฝ์ด๋ 0์ด ๋์จ๋ค. 0์ด๋ฉด UNMEASURED ๋ก ์ ๋๋ค.
|
|
26
|
+
"""
|
|
27
|
+
import os
|
|
28
|
+
import re
|
|
29
|
+
|
|
30
|
+
# ใ40pใ ใ40 ํ์ด์งใ ใ40์ชฝใ ใ3์ฅ์์ใโฆ
|
|
31
|
+
REF = re.compile(r'(?<![0-9A-Za-z])(\d{1,3})\s*(p\b|ํ์ด์ง|์ชฝ(?!์)|์ฅ(?=์์|์|์|๋ถํฐ|์))')
|
|
32
|
+
# ๐ฅ `\s+` ๊ฐ ํ์ค์ ์ง๋ค โ ์ดํ์ `\s*` ์๊ณ ์ค์ธก ์ฝํผ์ค์ ยซ62.5%ยป ๋ฅผ ยซ62๋ฒ ์น์
ยป์ผ๋ก
|
|
33
|
+
# ๋จน์๋ค. ๊ทธ๋ฌ๋ฉด ์ง์ง ใ62์ฅใ ์ฐธ์กฐ๊ฐ ์กฐ์ฉํ SECTION ์ผ๋ก ๋ถ๋ฅ๋ผ ์ฌ๋ผ์ง๋ค.
|
|
34
|
+
# ์น์
๋ผ๋ฒจ์ ์ ๋ค์ ๊ณต๋ฐฑ์ด ์๋ค(ใ3. ์ด๋ป๊ฒ ๋ง๋ค์๋ใ). ์์์ ์ ์๋ค.
|
|
35
|
+
SECTION_LABEL = re.compile(r'^\s*(\d{1,2})\.\s+\S')
|
|
36
|
+
SECTION_MAX = 20 # ์น์
์ด 20์ ๋๋ ๋ฑ์ ์๋ค โ ๋์ผ๋ฉด ๋ผ๋ฒจ์ด ์๋๋ค
|
|
37
|
+
SECTION_MIN_SLIDES = 2 # ์น์
๋ผ๋ฒจ์ ์ฌ๋ฌ ์ฅ์ ๋ฐ๋ณต๋๋ค(ํ ์ฅ์ง๋ฆฌ๋ ๋ผ๋ฒจ์ด ์๋๋ค)
|
|
38
|
+
CITE = re.compile(r'arXiv|doi|ใ|ใ|pp\.|\b(19|20)\d{2}\b', re.I)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _texts(slide):
|
|
42
|
+
scr, note = [], ''
|
|
43
|
+
for sh in slide.shapes:
|
|
44
|
+
if sh.has_text_frame:
|
|
45
|
+
t = ' '.join(r.text for p in sh.text_frame.paragraphs for r in p.runs)
|
|
46
|
+
if t.strip():
|
|
47
|
+
scr.append(t)
|
|
48
|
+
if slide.has_notes_slide:
|
|
49
|
+
note = slide.notes_slide.notes_text_frame.text or ''
|
|
50
|
+
return scr, note
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def section_numbers(prs):
|
|
54
|
+
"""์น์
๋ผ๋ฒจ(ใN. ์ ๋ชฉใ)์์ N ์ ๋ชจ์๋ค. ๋ชป ๋ชจ์ผ๋ฉด ๋น ์งํฉ โ ๊ทธ ์ฌ์ค์ด ๋
ธํธ์ ์ ํ๋ค.
|
|
55
|
+
|
|
56
|
+
์ธ ์กฐ๊ฑด์ **ํจ๊ป** ๊ฑด๋ค: ์ ๋ค ๊ณต๋ฐฑ(์์์ ๋ฐฐ์ ) ยท N โค SECTION_MAX ยท ์ฌ๋ฌ ์ฅ์ ๋ฐ๋ณต.
|
|
57
|
+
ํ๋๋ง ๊ฑธ๋ฉด ์ค์ธก์์ ๋ซ๋ฆฐ๋ค(์ ์ฃผ์์ 62.5% ์ฌ๋ก).
|
|
58
|
+
"""
|
|
59
|
+
seen = {}
|
|
60
|
+
for i, s in enumerate(prs.slides, 1):
|
|
61
|
+
for sh in s.shapes:
|
|
62
|
+
if not sh.has_text_frame:
|
|
63
|
+
continue
|
|
64
|
+
for p in sh.text_frame.paragraphs:
|
|
65
|
+
line = ''.join(r.text for r in p.runs).strip()
|
|
66
|
+
m = SECTION_LABEL.match(line)
|
|
67
|
+
if m and len(line) < 40:
|
|
68
|
+
n = int(m.group(1))
|
|
69
|
+
if n <= SECTION_MAX:
|
|
70
|
+
seen.setdefault(n, set()).add(i)
|
|
71
|
+
return {n for n, slides in seen.items() if len(slides) >= SECTION_MIN_SLIDES}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def classify(num, unit, line, secs):
|
|
75
|
+
if CITE.search(line):
|
|
76
|
+
return 'CITATION'
|
|
77
|
+
if unit.startswith('์ฅ') and num in secs:
|
|
78
|
+
return 'SECTION'
|
|
79
|
+
return 'SLIDE'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def analyze(prs):
|
|
83
|
+
total = len(prs.slides)
|
|
84
|
+
secs = section_numbers(prs)
|
|
85
|
+
rows = []
|
|
86
|
+
for i, s in enumerate(prs.slides, 1):
|
|
87
|
+
scr, note = _texts(s)
|
|
88
|
+
for where, chunks in (('ํ๋ฉด', scr), ('๋๋ณธ', note.split('\n'))):
|
|
89
|
+
for line in chunks:
|
|
90
|
+
for m in REF.finditer(line):
|
|
91
|
+
n, unit = int(m.group(1)), m.group(2)
|
|
92
|
+
kind = classify(n, unit, line, secs)
|
|
93
|
+
rows.append((i, where, n, unit, kind, line.strip()[:70]))
|
|
94
|
+
return total, secs, rows
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def scan(cfg, root):
|
|
98
|
+
deck_s = (cfg.get('surfaces_by_id') or {}).get('built_deck')
|
|
99
|
+
if not deck_s:
|
|
100
|
+
return [], ['L13 slide-refs : built_deck ๋ฏธ์ ์ธ โ NOT_CONFIGURED (0 ์๋)']
|
|
101
|
+
path = os.path.normpath(os.path.join(root, os.path.expanduser(deck_s['path'])))
|
|
102
|
+
if not os.path.exists(path):
|
|
103
|
+
return [], [f'L13 slide-refs : built_deck ์ค๋ฌผ ์์({path}) โ UNMEASURED (0 ์๋)']
|
|
104
|
+
try:
|
|
105
|
+
from pptx import Presentation
|
|
106
|
+
prs = Presentation(path)
|
|
107
|
+
total, secs, rows = analyze(prs)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
return [], [f'L13 slide-refs : ๊ณ๊ธฐ ์ค๋ฅ({type(e).__name__}: {e}) โ UNMEASURED (0 ์๋)']
|
|
110
|
+
|
|
111
|
+
per = {}
|
|
112
|
+
for _, _, _, _, k, _ in rows:
|
|
113
|
+
per[k] = per.get(k, 0) + 1
|
|
114
|
+
notes = [f'L13 slide-refs : {total}์ฅ ยท ์ฐธ์กฐ {len(rows)}๊ฑด '
|
|
115
|
+
f'(SLIDE {per.get("SLIDE",0)} ยท SECTION {per.get("SECTION",0)} ยท CITATION {per.get("CITATION",0)}) '
|
|
116
|
+
f'ยท ์น์
๋ฒํธ ์งํฉ {sorted(secs) if secs else "์์ โ SECTION ๋ถ๊ธฐ ๋นํ์ฑ"}']
|
|
117
|
+
if not rows:
|
|
118
|
+
notes.append(' โ ๏ธ ์ฐธ์กฐ 0๊ฑด โ ใ๊นจ๋ํ๋คใ๊ฐ ์๋๋ผ **UNMEASURED** ๋ค(์ถ์ถ์ด ์ฃฝ์ด๋ 0์ด ๋์จ๋ค). '
|
|
119
|
+
'๋ฑ์ ๋ฒํธ ์ฐธ์กฐ๊ฐ ์๋ ์์ผ๋ฉด ์ ์์ด๊ณ , ๊ทธ ์ฌ์ค์ ์ฌ๊ธฐ์ ํ์ธํด๋ผ')
|
|
120
|
+
return [], notes
|
|
121
|
+
|
|
122
|
+
findings = []
|
|
123
|
+
for i, where, n, unit, kind, line in rows:
|
|
124
|
+
if kind != 'SLIDE':
|
|
125
|
+
continue
|
|
126
|
+
if n < 1 or n > total:
|
|
127
|
+
findings.append((f'{path}#s{i}', 'use',
|
|
128
|
+
f'L13 ์ฅ ๋ฒํธ ์ฐธ์กฐ๊ฐ ๋ฒ์ ๋ฐ์ด๋ค โ s{i} [{where}] โ{n} (์ด {total}์ฅ) : {line}'))
|
|
129
|
+
notes.append(f' โ s{i:3} [{where}] โ{n:3} ๋ฒ์ ๋ฐ(์ด {total}) : {line}')
|
|
130
|
+
elif n == i:
|
|
131
|
+
notes.append(f' โ ๏ธ s{i:3} [{where}] โ{n:3} ์๊ธฐ ์์ ์ฐธ์กฐ : {line}')
|
|
132
|
+
else:
|
|
133
|
+
notes.append(f' ยท s{i:3} [{where}] โ{n:3} : {line}')
|
|
134
|
+
return findings, notes
|