python-hwpx-automation 6.0.3__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.
Files changed (217) hide show
  1. hwpx_automation/__init__.py +61 -0
  2. hwpx_automation/__init__.pyi +27 -0
  3. hwpx_automation/__main__.py +8 -0
  4. hwpx_automation/agent_document.py +392 -0
  5. hwpx_automation/api.py +136 -0
  6. hwpx_automation/blind_eval.py +407 -0
  7. hwpx_automation/capabilities.py +110 -0
  8. hwpx_automation/compat.py +48 -0
  9. hwpx_automation/configuration.py +60 -0
  10. hwpx_automation/core/__init__.py +2 -0
  11. hwpx_automation/core/content.py +762 -0
  12. hwpx_automation/core/context.py +111 -0
  13. hwpx_automation/core/diff.py +53 -0
  14. hwpx_automation/core/document.py +37 -0
  15. hwpx_automation/core/formatting.py +513 -0
  16. hwpx_automation/core/handles.py +24 -0
  17. hwpx_automation/core/locations.py +205 -0
  18. hwpx_automation/core/locator.py +162 -0
  19. hwpx_automation/core/plan.py +680 -0
  20. hwpx_automation/core/resources.py +42 -0
  21. hwpx_automation/core/search.py +296 -0
  22. hwpx_automation/core/transactions.py +434 -0
  23. hwpx_automation/core/txn.py +48 -0
  24. hwpx_automation/document_state.py +95 -0
  25. hwpx_automation/errors.py +174 -0
  26. hwpx_automation/execution_lock.py +15 -0
  27. hwpx_automation/fastmcp_adapter.py +672 -0
  28. hwpx_automation/form_fill.py +1177 -0
  29. hwpx_automation/form_output_models.py +223 -0
  30. hwpx_automation/handlers/__init__.py +2 -0
  31. hwpx_automation/handlers/_shared.py +377 -0
  32. hwpx_automation/handlers/agent_document.py +257 -0
  33. hwpx_automation/handlers/authoring.py +750 -0
  34. hwpx_automation/handlers/content_edit.py +1078 -0
  35. hwpx_automation/handlers/form_fill.py +607 -0
  36. hwpx_automation/handlers/layout_style.py +660 -0
  37. hwpx_automation/handlers/quality_render.py +566 -0
  38. hwpx_automation/handlers/read_export.py +1295 -0
  39. hwpx_automation/handlers/specialized.py +624 -0
  40. hwpx_automation/handlers/tracked_changes.py +589 -0
  41. hwpx_automation/handlers/workflow.py +105 -0
  42. hwpx_automation/hwp_converter.py +227 -0
  43. hwpx_automation/hwp_support.py +94 -0
  44. hwpx_automation/hwpx_ops.py +1439 -0
  45. hwpx_automation/identity.json +263 -0
  46. hwpx_automation/identity.py +18 -0
  47. hwpx_automation/ingest_adapters.py +85 -0
  48. hwpx_automation/markdown_plan.py +216 -0
  49. hwpx_automation/mcp_cli.py +29 -0
  50. hwpx_automation/metadata/tools_meta.py +40 -0
  51. hwpx_automation/mixed_form.py +3007 -0
  52. hwpx_automation/mutation_models.py +401 -0
  53. hwpx_automation/network_policy.py +232 -0
  54. hwpx_automation/office/__init__.py +14 -0
  55. hwpx_automation/office/agent/__init__.py +125 -0
  56. hwpx_automation/office/agent/_batch_verification.py +383 -0
  57. hwpx_automation/office/agent/blueprint/__init__.py +58 -0
  58. hwpx_automation/office/agent/blueprint/bundle.py +282 -0
  59. hwpx_automation/office/agent/blueprint/catalog.py +136 -0
  60. hwpx_automation/office/agent/blueprint/dump.py +520 -0
  61. hwpx_automation/office/agent/blueprint/mapping.py +312 -0
  62. hwpx_automation/office/agent/blueprint/model.py +722 -0
  63. hwpx_automation/office/agent/blueprint/native.py +621 -0
  64. hwpx_automation/office/agent/blueprint/replay.py +622 -0
  65. hwpx_automation/office/agent/catalog.py +252 -0
  66. hwpx_automation/office/agent/cli.py +647 -0
  67. hwpx_automation/office/agent/commands.py +1383 -0
  68. hwpx_automation/office/agent/document.py +801 -0
  69. hwpx_automation/office/agent/form_plan.py +1760 -0
  70. hwpx_automation/office/agent/model.py +808 -0
  71. hwpx_automation/office/agent/path.py +155 -0
  72. hwpx_automation/office/agent/query.py +230 -0
  73. hwpx_automation/office/agent/story.py +207 -0
  74. hwpx_automation/office/authoring/__init__.py +3542 -0
  75. hwpx_automation/office/authoring/advanced_generators.py +154 -0
  76. hwpx_automation/office/authoring/builder/__init__.py +52 -0
  77. hwpx_automation/office/authoring/builder/core.py +996 -0
  78. hwpx_automation/office/authoring/builder/report.py +195 -0
  79. hwpx_automation/office/authoring/design/__init__.py +30 -0
  80. hwpx_automation/office/authoring/design/_support.py +144 -0
  81. hwpx_automation/office/authoring/design/composer.py +282 -0
  82. hwpx_automation/office/authoring/design/harvest.py +305 -0
  83. hwpx_automation/office/authoring/design/plan.py +69 -0
  84. hwpx_automation/office/authoring/design/profile.py +88 -0
  85. hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +1 -0
  86. hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +1 -0
  87. hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +1 -0
  88. hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +1 -0
  89. hwpx_automation/office/authoring/design/profiles/application_form/profile.json +25 -0
  90. hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
  91. hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +1 -0
  92. hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +1 -0
  93. hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +1 -0
  94. hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +24 -0
  95. hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
  96. hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +1 -0
  97. hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +1 -0
  98. hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +1 -0
  99. hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +1 -0
  100. hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +25 -0
  101. hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
  102. hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +1 -0
  103. hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +1 -0
  104. hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +1 -0
  105. hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +1 -0
  106. hwpx_automation/office/authoring/design/profiles/report/profile.json +25 -0
  107. hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
  108. hwpx_automation/office/authoring/design/validator.py +107 -0
  109. hwpx_automation/office/authoring/presets/__init__.py +22 -0
  110. hwpx_automation/office/authoring/presets/proposal.py +538 -0
  111. hwpx_automation/office/authoring/report_parser.py +141 -0
  112. hwpx_automation/office/authoring/style_profile.py +437 -0
  113. hwpx_automation/office/authoring/template_analyzer.py +657 -0
  114. hwpx_automation/office/compliance/__init__.py +38 -0
  115. hwpx_automation/office/compliance/official_lint.py +478 -0
  116. hwpx_automation/office/compliance/pii.py +388 -0
  117. hwpx_automation/office/document_ops/__init__.py +13 -0
  118. hwpx_automation/office/document_ops/comparison.py +62 -0
  119. hwpx_automation/office/document_ops/mail_merge.py +73 -0
  120. hwpx_automation/office/document_ops/redline.py +35 -0
  121. hwpx_automation/office/evalplan/__init__.py +36 -0
  122. hwpx_automation/office/evalplan/runtime.py +2762 -0
  123. hwpx_automation/office/exam/__init__.py +44 -0
  124. hwpx_automation/office/exam/compose.py +282 -0
  125. hwpx_automation/office/exam/ir.py +44 -0
  126. hwpx_automation/office/exam/measure.py +163 -0
  127. hwpx_automation/office/exam/parser.py +150 -0
  128. hwpx_automation/office/exam/profile.py +123 -0
  129. hwpx_automation/office/form_fill/__init__.py +66 -0
  130. hwpx_automation/office/form_fill/classification.py +108 -0
  131. hwpx_automation/office/form_fill/fill_residue.py +242 -0
  132. hwpx_automation/office/form_fill/fit/__init__.py +36 -0
  133. hwpx_automation/office/form_fill/fit/apply.py +24 -0
  134. hwpx_automation/office/form_fill/fit/engine.py +24 -0
  135. hwpx_automation/office/form_fill/fit/measure.py +50 -0
  136. hwpx_automation/office/form_fill/fit/policy.py +28 -0
  137. hwpx_automation/office/form_fill/fit/report.py +28 -0
  138. hwpx_automation/office/form_fill/fit/seal.py +457 -0
  139. hwpx_automation/office/form_fill/fit/wordbox.py +1343 -0
  140. hwpx_automation/office/form_fill/guidance.py +704 -0
  141. hwpx_automation/office/form_fill/quality.py +961 -0
  142. hwpx_automation/office/form_fill/split_run.py +333 -0
  143. hwpx_automation/office/form_fill/template_formfit.py +656 -0
  144. hwpx_automation/office/house_style/__init__.py +196 -0
  145. hwpx_automation/office/house_style/composition.py +68 -0
  146. hwpx_automation/office/house_style/data/bank.json +625 -0
  147. hwpx_automation/office/house_style/data/genres.json +43 -0
  148. hwpx_automation/office/quality/__init__.py +14 -0
  149. hwpx_automation/office/quality/page_guard.py +277 -0
  150. hwpx_automation/office/rendering/__init__.py +145 -0
  151. hwpx_automation/office/rendering/_hancom_open_rate.ps1 +374 -0
  152. hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +162 -0
  153. hwpx_automation/office/rendering/_render_hwpx.ps1 +72 -0
  154. hwpx_automation/office/rendering/_render_hwpx_mac.applescript +249 -0
  155. hwpx_automation/office/rendering/block_splits.py +76 -0
  156. hwpx_automation/office/rendering/detectors.py +151 -0
  157. hwpx_automation/office/rendering/diff.py +153 -0
  158. hwpx_automation/office/rendering/fixture_corpus.py +215 -0
  159. hwpx_automation/office/rendering/oracle.py +909 -0
  160. hwpx_automation/office/rendering/page_qa.py +245 -0
  161. hwpx_automation/office/rendering/qa_contracts.py +293 -0
  162. hwpx_automation/office/rendering/qa_metrics.py +241 -0
  163. hwpx_automation/office/rendering/worker.py +290 -0
  164. hwpx_automation/office/utilities/__init__.py +12 -0
  165. hwpx_automation/office/utilities/table_compute.py +477 -0
  166. hwpx_automation/ops_services/__init__.py +1 -0
  167. hwpx_automation/ops_services/_border_fill.py +283 -0
  168. hwpx_automation/ops_services/composition.py +55 -0
  169. hwpx_automation/ops_services/content_layout.py +322 -0
  170. hwpx_automation/ops_services/context.py +213 -0
  171. hwpx_automation/ops_services/form_fields.py +557 -0
  172. hwpx_automation/ops_services/media.py +178 -0
  173. hwpx_automation/ops_services/memo_style.py +477 -0
  174. hwpx_automation/ops_services/package_validation.py +166 -0
  175. hwpx_automation/ops_services/planning.py +201 -0
  176. hwpx_automation/ops_services/preview_export.py +585 -0
  177. hwpx_automation/ops_services/read_query.py +601 -0
  178. hwpx_automation/ops_services/save_policy.py +604 -0
  179. hwpx_automation/ops_services/tables.py +539 -0
  180. hwpx_automation/ops_services/transactions.py +616 -0
  181. hwpx_automation/preview_output_models.py +69 -0
  182. hwpx_automation/public-modules.json +206 -0
  183. hwpx_automation/py.typed +1 -0
  184. hwpx_automation/quality.py +351 -0
  185. hwpx_automation/quality_generation.py +725 -0
  186. hwpx_automation/runtime.py +321 -0
  187. hwpx_automation/runtime_services.py +100 -0
  188. hwpx_automation/server.py +259 -0
  189. hwpx_automation/storage.py +747 -0
  190. hwpx_automation/tool_bindings.py +170 -0
  191. hwpx_automation/tool_contract.py +982 -0
  192. hwpx_automation/upstream.py +755 -0
  193. hwpx_automation/utils/__init__.py +2 -0
  194. hwpx_automation/utils/helpers.py +29 -0
  195. hwpx_automation/visual_qa.py +667 -0
  196. hwpx_automation/workflow/__init__.py +55 -0
  197. hwpx_automation/workflow/adapters.py +482 -0
  198. hwpx_automation/workflow/dispatcher.py +213 -0
  199. hwpx_automation/workflow/models.py +243 -0
  200. hwpx_automation/workflow/policy.py +198 -0
  201. hwpx_automation/workflow/render_contracts.py +173 -0
  202. hwpx_automation/workflow/render_metrics.py +196 -0
  203. hwpx_automation/workflow/render_queue.py +482 -0
  204. hwpx_automation/workflow/render_security.py +172 -0
  205. hwpx_automation/workflow/render_transport.py +369 -0
  206. hwpx_automation/workflow/rendering.py +206 -0
  207. hwpx_automation/workflow/service.py +758 -0
  208. hwpx_automation/workflow/state_machine.py +65 -0
  209. hwpx_automation/workflow/store.py +747 -0
  210. hwpx_automation/workspace.py +1694 -0
  211. python_hwpx_automation-6.0.3.dist-info/METADATA +279 -0
  212. python_hwpx_automation-6.0.3.dist-info/RECORD +217 -0
  213. python_hwpx_automation-6.0.3.dist-info/WHEEL +5 -0
  214. python_hwpx_automation-6.0.3.dist-info/entry_points.txt +3 -0
  215. python_hwpx_automation-6.0.3.dist-info/licenses/LICENSE +178 -0
  216. python_hwpx_automation-6.0.3.dist-info/licenses/NOTICE +14 -0
  217. python_hwpx_automation-6.0.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,607 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Form-fill automation handlers for the optional MCP adapter."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+ from typing import Annotated
8
+
9
+ from pydantic import Field
10
+
11
+ from .. import quality as quality_contract
12
+ from ..core.content import (
13
+ fill_by_path_in_doc,
14
+ find_cell_by_label_in_doc,
15
+ )
16
+ from ..core.document import open_doc
17
+ from ..form_fill import (
18
+ CanonicalFormFillInput,
19
+ FormFillAnalyzeOptions,
20
+ FormFillPlanInput,
21
+ analyze_form_fill_workflow,
22
+ apply_form_fill_workflow,
23
+ )
24
+ from ..form_output_models import (
25
+ AnalyzeFormFillOutput,
26
+ ApplyBodyOpsOutput,
27
+ ApplyEvalplanFillOutput,
28
+ ApplyFormFillOutput,
29
+ ApplyTableOpsOutput,
30
+ VerifyFormFillOutput,
31
+ )
32
+ from ..mixed_form import (
33
+ MixedFormApplyInput,
34
+ MixedFormCompiledPlanInput,
35
+ MixedFormPlanInput,
36
+ analyze_mixed_form_plan,
37
+ apply_canonical_mixed_form_plan,
38
+ run_specialized_form_operation,
39
+ verify_canonical_mixed_form_plan,
40
+ )
41
+ from ..mutation_models import (
42
+ BodyOperation,
43
+ TableOperation,
44
+ operation_payloads,
45
+ )
46
+
47
+ # Import the template-formfit callables from their submodule rather than the
48
+ # hwpx top level: at the core 4.0.0 boundary the top-level re-exports become
49
+ # deprecated shims that emit a DeprecationWarning on access, and these MCP tools
50
+ # (now DEPRECATED but still functional) must not trigger a runtime warning. The
51
+ # submodule path is stable across core 3.3.1 and 4.0.0.
52
+ from ..office.form_fill import (
53
+ analyze_template_formfit as analyze_hwpx_template_formfit,
54
+ )
55
+ from ..office.form_fill import (
56
+ apply_template_formfit as apply_hwpx_template_formfit,
57
+ )
58
+ from ..runtime_services import RUNTIME_SERVICES
59
+ from ..utils.helpers import resolve_path
60
+ from ._shared import (
61
+ _idempotency_fingerprint,
62
+ _idempotency_replay,
63
+ _idempotency_scope,
64
+ _idempotency_store,
65
+ _normalize_fill_mappings,
66
+ _revision_guard,
67
+ _save_doc_verification,
68
+ _with_document_state,
69
+ _with_dry_run_verification,
70
+ _with_save_verification,
71
+ )
72
+
73
+ _TABLE_LABEL_DIRECTIONS = ("right", "down")
74
+
75
+
76
+ def _normalize_table_label_direction(direction: str | None) -> str:
77
+ value = (direction or "right").strip().lower()
78
+ if value not in _TABLE_LABEL_DIRECTIONS:
79
+ expected = ", ".join(_TABLE_LABEL_DIRECTIONS)
80
+ raise ValueError(f"direction must be one of: {expected}")
81
+ return value
82
+
83
+
84
+ def _template_formfit_baseline_arg(baseline: dict | str) -> dict | str:
85
+ if isinstance(baseline, dict):
86
+ return baseline
87
+ text = str(baseline or "").strip()
88
+ if text.endswith(".json") or Path(text).exists():
89
+ return resolve_path(text)
90
+ return text
91
+
92
+
93
+ def analyze_template_formfit(
94
+ source_filename: str,
95
+ baseline: dict | str,
96
+ content: dict,
97
+ destination_filename: str = None,
98
+ options: dict = None,
99
+ ) -> dict:
100
+ """P6 baseline 기반 양식 보존 생성 계획을 비파괴 분석합니다."""
101
+ if analyze_hwpx_template_formfit is None:
102
+ raise RuntimeError("installed python-hwpx does not provide template form-fit")
103
+ return analyze_hwpx_template_formfit(
104
+ resolve_path(source_filename),
105
+ baseline=_template_formfit_baseline_arg(baseline),
106
+ content=content or {},
107
+ destination=resolve_path(destination_filename)
108
+ if destination_filename
109
+ else None,
110
+ options=options,
111
+ )
112
+
113
+
114
+ def apply_template_formfit(
115
+ analysis: dict = None,
116
+ source_filename: str = None,
117
+ baseline: dict | str = None, # type: ignore[assignment] # Frozen ToolSpec default.
118
+ content: dict = None,
119
+ destination_filename: str = None,
120
+ confirm: bool = True,
121
+ ) -> dict:
122
+ """양식 보존 생성 계획을 복사본 destination에만 적용하고 검증합니다."""
123
+ if apply_hwpx_template_formfit is None:
124
+ raise RuntimeError("installed python-hwpx does not provide template form-fit")
125
+ return apply_hwpx_template_formfit(
126
+ analysis=analysis,
127
+ source=resolve_path(source_filename) if source_filename else None,
128
+ baseline=_template_formfit_baseline_arg(baseline)
129
+ if baseline is not None
130
+ else None,
131
+ content=content,
132
+ destination=resolve_path(destination_filename)
133
+ if destination_filename
134
+ else None,
135
+ confirm=confirm,
136
+ )
137
+
138
+
139
+ def apply_table_ops(
140
+ filename: str,
141
+ ops: list[TableOperation],
142
+ output: str | None = None,
143
+ render_check: str = "off",
144
+ dry_run: bool = False,
145
+ ) -> ApplyTableOpsOutput:
146
+ """바이트 보존으로 표 셀/행/열/표 구조 연산을 원자 적용합니다."""
147
+
148
+ source = Path(resolve_path(filename))
149
+ target = Path(resolve_path(output)) if output else source
150
+ if not dry_run:
151
+ quality_contract.assert_write_capability()
152
+ return run_specialized_form_operation(
153
+ operation="apply_table_ops",
154
+ source=source,
155
+ output=target,
156
+ dry_run=dry_run,
157
+ execute_with_guard=lambda output_guard, publication_sink: (
158
+ RUNTIME_SERVICES.ops.apply_table_ops(
159
+ str(source),
160
+ operation_payloads(ops),
161
+ output=str(target) if output else None,
162
+ render_check=render_check,
163
+ dry_run=dry_run,
164
+ output_guard=output_guard,
165
+ publication_sink=publication_sink,
166
+ )
167
+ ),
168
+ )
169
+
170
+
171
+ def verify_form_fill(
172
+ filename: str = None,
173
+ before_path: str = None,
174
+ require: bool = False,
175
+ plan: MixedFormCompiledPlanInput = None,
176
+ expected_output_revision: Annotated[
177
+ str | None,
178
+ Field(pattern=r"^sha256:[0-9a-f]{64}$"),
179
+ ] = None,
180
+ ) -> VerifyFormFillOutput:
181
+ """Compiled mixed-form 결과를 검증합니다. filename/before_path는 렌더 호환 경로입니다."""
182
+
183
+ if plan is not None:
184
+ if filename is not None or before_path is not None:
185
+ raise ValueError(
186
+ "plan cannot be combined with legacy filename/before_path arguments"
187
+ )
188
+ return verify_canonical_mixed_form_plan(
189
+ plan,
190
+ require=require,
191
+ expected_output_revision=expected_output_revision,
192
+ render_verifier=lambda after, before, required: (
193
+ RUNTIME_SERVICES.ops.verify_form_fill(
194
+ after,
195
+ before,
196
+ require=required,
197
+ )
198
+ ),
199
+ )
200
+ if filename is None or before_path is None:
201
+ raise ValueError("provide plan or both filename and before_path")
202
+ if expected_output_revision is not None:
203
+ raise ValueError("expected_output_revision requires plan")
204
+ result = RUNTIME_SERVICES.ops.verify_form_fill(
205
+ resolve_path(filename), resolve_path(before_path), require=require
206
+ )
207
+ result["compatibility"] = {
208
+ "status": "retained-render-verifier",
209
+ "canonicalInput": "plan: hwpx.mixed-form-compiled-plan/v1",
210
+ }
211
+ return result
212
+
213
+
214
+ def score_form_fill(
215
+ filename: str,
216
+ gold_path: str,
217
+ blank_path: str,
218
+ run_render: bool = True,
219
+ expected_pages: int | None = None,
220
+ ) -> dict:
221
+ """채움본을 gold/blank와 비교해 렌더·서식·구조·내용·규정 5축으로 채점합니다."""
222
+
223
+ return RUNTIME_SERVICES.ops.score_form_fill(
224
+ resolve_path(filename),
225
+ resolve_path(gold_path),
226
+ resolve_path(blank_path),
227
+ run_render=run_render,
228
+ expected_pages=expected_pages,
229
+ )
230
+
231
+
232
+ def apply_body_ops(
233
+ filename: str,
234
+ ops: list[BodyOperation],
235
+ output: str | None = None,
236
+ dry_run: bool = False,
237
+ ) -> ApplyBodyOpsOutput:
238
+ """표 밖 본문 문단에 바이트 보존 연산을 적용합니다."""
239
+
240
+ source = Path(resolve_path(filename))
241
+ target = Path(resolve_path(output)) if output else source
242
+ if not dry_run:
243
+ quality_contract.assert_write_capability()
244
+ return run_specialized_form_operation(
245
+ operation="apply_body_ops",
246
+ source=source,
247
+ output=target,
248
+ dry_run=dry_run,
249
+ execute_with_guard=lambda output_guard, publication_sink: (
250
+ RUNTIME_SERVICES.ops.apply_body_ops(
251
+ str(source),
252
+ operation_payloads(ops),
253
+ output=str(target) if output else None,
254
+ dry_run=dry_run,
255
+ output_guard=output_guard,
256
+ publication_sink=publication_sink,
257
+ )
258
+ ),
259
+ )
260
+
261
+
262
+ def inspect_fill_residue(
263
+ filename: str,
264
+ blank_path: str | None = None,
265
+ ) -> dict:
266
+ """채움본의 삭제색 안내문·미수정 샘플·placeholder 잔존을 검사합니다."""
267
+
268
+ return RUNTIME_SERVICES.ops.inspect_fill_residue(
269
+ resolve_path(filename),
270
+ blank_path=resolve_path(blank_path) if blank_path else None,
271
+ )
272
+
273
+
274
+ def scan_form_guidance(filename: str, max_items: int = 60) -> dict:
275
+ """처음 보는 양식의 색 범례·삭제/수정 후보·빈 셀·질문을 비변형 정찰합니다."""
276
+
277
+ return RUNTIME_SERVICES.ops.scan_form_guidance(
278
+ resolve_path(filename), max_items=max_items
279
+ )
280
+
281
+
282
+ def apply_evalplan_fill(
283
+ filename: str,
284
+ review_md: str,
285
+ output: str | None = None,
286
+ render_check: str = "off",
287
+ score_gold_path: str | None = None,
288
+ expected_pages: int | None = None,
289
+ phase: str = "all",
290
+ ) -> ApplyEvalplanFillOutput:
291
+ """빈 평가계획 양식과 검토용 Markdown을 바이트 보존 채움본으로 만듭니다.
292
+
293
+ phase: "structural"|"all"(기본)|"clean". "clean"은 채움 뒤 core의 결정론적
294
+ 정리(제목/교사/정의적 채움·지시문 스캐폴딩 prune·빨강 제거·파랑→검정·캡션 strip)까지
295
+ 한 번에 실행해 제출 가능한 채움본을 돌려줍니다(정리 리포트=contentReport.finalize)."""
296
+
297
+ quality_contract.assert_write_capability()
298
+ source = Path(resolve_path(filename))
299
+ target = Path(resolve_path(output)) if output else source
300
+ return run_specialized_form_operation(
301
+ operation="apply_evalplan_fill",
302
+ source=source,
303
+ output=target,
304
+ dry_run=False,
305
+ execute_with_guard=lambda output_guard, publication_sink: (
306
+ RUNTIME_SERVICES.ops.apply_evalplan_fill(
307
+ str(source),
308
+ resolve_path(review_md),
309
+ phase=phase,
310
+ output=str(target) if output else None,
311
+ render_check=render_check,
312
+ score_gold_path=resolve_path(score_gold_path)
313
+ if score_gold_path
314
+ else None,
315
+ expected_pages=expected_pages,
316
+ output_guard=output_guard,
317
+ publication_sink=publication_sink,
318
+ )
319
+ ),
320
+ )
321
+
322
+
323
+ def find_cell_by_label(
324
+ filename: str, label_text: str, direction: str = "right"
325
+ ) -> dict:
326
+ """양식 문서에서 라벨 기준 인접 셀 후보를 조회합니다. direction: right 또는 down."""
327
+ path = resolve_path(filename)
328
+ doc = open_doc(path)
329
+ safe_direction = _normalize_table_label_direction(direction)
330
+ return _with_document_state(
331
+ find_cell_by_label_in_doc(doc, label_text, direction=safe_direction), path
332
+ )
333
+
334
+
335
+ def fill_by_path(
336
+ filename: str,
337
+ mappings: dict[str, str],
338
+ dry_run: bool = False,
339
+ expected_revision: str = None,
340
+ idempotency_key: str = None,
341
+ ) -> dict:
342
+ """라벨 경로 문법으로 셀을 채웁니다. dry_run=True이면 원본을 저장하지 않습니다."""
343
+ path = resolve_path(filename)
344
+ scope = _idempotency_scope("fill_by_path", path, idempotency_key)
345
+ fingerprint = _idempotency_fingerprint(
346
+ {
347
+ "filename": filename,
348
+ "mappings": mappings,
349
+ "dry_run": dry_run,
350
+ "expected_revision": expected_revision,
351
+ }
352
+ )
353
+ replay = _idempotency_replay(scope, fingerprint=fingerprint)
354
+ if replay is not None:
355
+ return replay
356
+ guard = _revision_guard(path, expected_revision)
357
+ if guard is not None:
358
+ return guard
359
+ doc = open_doc(path)
360
+ result = fill_by_path_in_doc(doc, _normalize_fill_mappings(mappings))
361
+ if result.get("applied_count", 0) > 0:
362
+ if dry_run:
363
+ return _idempotency_store(
364
+ scope,
365
+ fingerprint=fingerprint,
366
+ payload=_with_dry_run_verification(result, doc, path),
367
+ )
368
+ verification = _save_doc_verification(doc, path)
369
+ return _idempotency_store(
370
+ scope,
371
+ fingerprint=fingerprint,
372
+ payload=_with_save_verification(result, verification),
373
+ )
374
+ result["dryRun"] = dry_run
375
+ return _idempotency_store(scope, fingerprint=fingerprint, payload=result)
376
+
377
+
378
+ def list_form_fields(filename: str) -> dict:
379
+ """문서의 네이티브 누름틀/FORM 필드 목록과 현재 값을 반환합니다."""
380
+ path = resolve_path(filename)
381
+ return _with_document_state(RUNTIME_SERVICES.ops.list_form_fields(path), path)
382
+
383
+
384
+ def fill_form_field(
385
+ filename: str,
386
+ value: str,
387
+ field_index: int = None,
388
+ field_id: str = None,
389
+ name: str = None,
390
+ dry_run: bool = False,
391
+ expected_revision: str = None,
392
+ ) -> dict:
393
+ """네이티브 누름틀/FORM 필드 하나를 채우고 open-safety 검증 증거를 반환합니다."""
394
+ path = resolve_path(filename)
395
+ guard = _revision_guard(path, expected_revision)
396
+ if guard is not None:
397
+ return guard
398
+ result = RUNTIME_SERVICES.ops.fill_form_field(
399
+ path,
400
+ value=value,
401
+ field_index=field_index,
402
+ field_id=field_id,
403
+ name=name,
404
+ dry_run=dry_run,
405
+ )
406
+ result["compatibility"] = {
407
+ "status": "retained-native-field-facade",
408
+ "canonicalTarget": "nativeField",
409
+ "reason": "in-place/index compatibility semantics are preserved",
410
+ }
411
+ return _with_document_state(result, path)
412
+
413
+
414
+ def analyze_form_fill(
415
+ source_filename: str = None,
416
+ input_json: CanonicalFormFillInput | str = None, # type: ignore[assignment] # Frozen ToolSpec default.
417
+ input_json_path: str = None,
418
+ input_docx: str = None,
419
+ destination_filename: str = None,
420
+ options: FormFillAnalyzeOptions = None,
421
+ plan: MixedFormPlanInput = None,
422
+ ) -> AnalyzeFormFillOutput:
423
+ """엄격한 혼합 양식 계획을 비변경 분석합니다. 기존 formfill.v1 인자는 호환 경로입니다."""
424
+ if plan is not None:
425
+ compatibility_args = (
426
+ source_filename,
427
+ input_json,
428
+ input_json_path,
429
+ input_docx,
430
+ destination_filename,
431
+ options,
432
+ )
433
+ if any(value is not None for value in compatibility_args):
434
+ raise ValueError(
435
+ "plan cannot be combined with hwpx.formfill.v1 compatibility arguments"
436
+ )
437
+ return analyze_mixed_form_plan(plan)
438
+ if source_filename is None:
439
+ raise ValueError(
440
+ "provide plan or source_filename for the hwpx.formfill.v1 compatibility path"
441
+ )
442
+ result = analyze_form_fill_workflow(
443
+ source_filename=source_filename,
444
+ input_json=input_json,
445
+ input_json_path=input_json_path,
446
+ input_docx=input_docx,
447
+ destination_filename=destination_filename,
448
+ options=options,
449
+ )
450
+ result["compatibility"] = {
451
+ "schemaVersion": "hwpx.formfill.v1",
452
+ "status": "retained",
453
+ "canonicalInput": "plan: hwpx.mixed-form-plan/v1",
454
+ }
455
+ return result
456
+
457
+
458
+ def apply_form_fill(
459
+ plan_id: str = None,
460
+ analysis: FormFillPlanInput = None,
461
+ source_filename: str = None,
462
+ destination_filename: str = None,
463
+ canonical_input: CanonicalFormFillInput | str = None, # type: ignore[assignment] # Frozen ToolSpec default.
464
+ confirm: bool = True,
465
+ plan: MixedFormApplyInput = None,
466
+ ) -> ApplyFormFillOutput:
467
+ """엄격한 public/compiled 혼합 양식 계획을 원자 적용합니다. 기존 인자는 호환 경로입니다."""
468
+ if plan is not None:
469
+ compatibility_args = (
470
+ plan_id,
471
+ analysis,
472
+ source_filename,
473
+ destination_filename,
474
+ canonical_input,
475
+ )
476
+ if (
477
+ any(value is not None for value in compatibility_args)
478
+ or confirm is not True
479
+ ):
480
+ raise ValueError(
481
+ "plan cannot be combined with hwpx.formfill.v1 compatibility arguments"
482
+ )
483
+ quality_contract.assert_write_capability()
484
+ return apply_canonical_mixed_form_plan(plan)
485
+ result = apply_form_fill_workflow(
486
+ plan_id=plan_id,
487
+ analysis=analysis,
488
+ source_filename=source_filename,
489
+ destination_filename=destination_filename,
490
+ canonical_input=canonical_input,
491
+ confirm=confirm,
492
+ )
493
+ result["compatibility"] = {
494
+ "schemaVersion": "hwpx.formfill.v1",
495
+ "status": "retained",
496
+ "canonicalInput": "plan: hwpx.mixed-form-plan/v1 or hwpx.mixed-form-compiled-plan/v1",
497
+ }
498
+ return result
499
+
500
+
501
+ verify_form_fill.__hwpx_input_schema_extra__ = { # type: ignore[attr-defined]
502
+ "oneOf": [
503
+ {
504
+ "required": ["plan"],
505
+ "properties": {"plan": {"not": {"type": "null"}}},
506
+ "not": {
507
+ "anyOf": [
508
+ {
509
+ "required": [name],
510
+ "properties": {name: {"not": {"type": "null"}}},
511
+ }
512
+ for name in ("filename", "before_path")
513
+ ]
514
+ },
515
+ },
516
+ {
517
+ "required": ["filename", "before_path"],
518
+ "properties": {
519
+ "filename": {"not": {"type": "null"}},
520
+ "before_path": {"not": {"type": "null"}},
521
+ "plan": {"type": "null"},
522
+ "expected_output_revision": {"type": "null"},
523
+ },
524
+ },
525
+ ]
526
+ }
527
+
528
+
529
+ analyze_form_fill.__hwpx_input_schema_extra__ = { # type: ignore[attr-defined]
530
+ "oneOf": [
531
+ {
532
+ "required": ["plan"],
533
+ "properties": {"plan": {"not": {"type": "null"}}},
534
+ "not": {
535
+ "anyOf": [
536
+ {
537
+ "required": [name],
538
+ "properties": {name: {"not": {"type": "null"}}},
539
+ }
540
+ for name in (
541
+ "source_filename",
542
+ "input_json",
543
+ "input_json_path",
544
+ "input_docx",
545
+ "destination_filename",
546
+ "options",
547
+ )
548
+ ]
549
+ },
550
+ },
551
+ {
552
+ "required": ["source_filename"],
553
+ "properties": {
554
+ "source_filename": {"not": {"type": "null"}},
555
+ "plan": {"type": "null"},
556
+ },
557
+ },
558
+ ]
559
+ }
560
+
561
+
562
+ apply_form_fill.__hwpx_input_schema_extra__ = { # type: ignore[attr-defined]
563
+ "oneOf": [
564
+ {
565
+ "required": ["plan"],
566
+ "properties": {
567
+ "plan": {"not": {"type": "null"}},
568
+ "confirm": {"const": True},
569
+ },
570
+ "not": {
571
+ "anyOf": [
572
+ {
573
+ "required": [name],
574
+ "properties": {name: {"not": {"type": "null"}}},
575
+ }
576
+ for name in (
577
+ "plan_id",
578
+ "analysis",
579
+ "source_filename",
580
+ "destination_filename",
581
+ "canonical_input",
582
+ )
583
+ ]
584
+ },
585
+ },
586
+ {"properties": {"plan": {"type": "null"}}},
587
+ ]
588
+ }
589
+
590
+
591
+ __all__ = [
592
+ "analyze_form_fill",
593
+ "analyze_template_formfit",
594
+ "apply_body_ops",
595
+ "apply_evalplan_fill",
596
+ "apply_form_fill",
597
+ "apply_table_ops",
598
+ "apply_template_formfit",
599
+ "fill_by_path",
600
+ "fill_form_field",
601
+ "find_cell_by_label",
602
+ "inspect_fill_residue",
603
+ "list_form_fields",
604
+ "scan_form_guidance",
605
+ "score_form_fill",
606
+ "verify_form_fill",
607
+ ]