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.
- hwpx_automation/__init__.py +61 -0
- hwpx_automation/__init__.pyi +27 -0
- hwpx_automation/__main__.py +8 -0
- hwpx_automation/agent_document.py +392 -0
- hwpx_automation/api.py +136 -0
- hwpx_automation/blind_eval.py +407 -0
- hwpx_automation/capabilities.py +110 -0
- hwpx_automation/compat.py +48 -0
- hwpx_automation/configuration.py +60 -0
- hwpx_automation/core/__init__.py +2 -0
- hwpx_automation/core/content.py +762 -0
- hwpx_automation/core/context.py +111 -0
- hwpx_automation/core/diff.py +53 -0
- hwpx_automation/core/document.py +37 -0
- hwpx_automation/core/formatting.py +513 -0
- hwpx_automation/core/handles.py +24 -0
- hwpx_automation/core/locations.py +205 -0
- hwpx_automation/core/locator.py +162 -0
- hwpx_automation/core/plan.py +680 -0
- hwpx_automation/core/resources.py +42 -0
- hwpx_automation/core/search.py +296 -0
- hwpx_automation/core/transactions.py +434 -0
- hwpx_automation/core/txn.py +48 -0
- hwpx_automation/document_state.py +95 -0
- hwpx_automation/errors.py +174 -0
- hwpx_automation/execution_lock.py +15 -0
- hwpx_automation/fastmcp_adapter.py +672 -0
- hwpx_automation/form_fill.py +1177 -0
- hwpx_automation/form_output_models.py +223 -0
- hwpx_automation/handlers/__init__.py +2 -0
- hwpx_automation/handlers/_shared.py +377 -0
- hwpx_automation/handlers/agent_document.py +257 -0
- hwpx_automation/handlers/authoring.py +750 -0
- hwpx_automation/handlers/content_edit.py +1078 -0
- hwpx_automation/handlers/form_fill.py +607 -0
- hwpx_automation/handlers/layout_style.py +660 -0
- hwpx_automation/handlers/quality_render.py +566 -0
- hwpx_automation/handlers/read_export.py +1295 -0
- hwpx_automation/handlers/specialized.py +624 -0
- hwpx_automation/handlers/tracked_changes.py +589 -0
- hwpx_automation/handlers/workflow.py +105 -0
- hwpx_automation/hwp_converter.py +227 -0
- hwpx_automation/hwp_support.py +94 -0
- hwpx_automation/hwpx_ops.py +1439 -0
- hwpx_automation/identity.json +263 -0
- hwpx_automation/identity.py +18 -0
- hwpx_automation/ingest_adapters.py +85 -0
- hwpx_automation/markdown_plan.py +216 -0
- hwpx_automation/mcp_cli.py +29 -0
- hwpx_automation/metadata/tools_meta.py +40 -0
- hwpx_automation/mixed_form.py +3007 -0
- hwpx_automation/mutation_models.py +401 -0
- hwpx_automation/network_policy.py +232 -0
- hwpx_automation/office/__init__.py +14 -0
- hwpx_automation/office/agent/__init__.py +125 -0
- hwpx_automation/office/agent/_batch_verification.py +383 -0
- hwpx_automation/office/agent/blueprint/__init__.py +58 -0
- hwpx_automation/office/agent/blueprint/bundle.py +282 -0
- hwpx_automation/office/agent/blueprint/catalog.py +136 -0
- hwpx_automation/office/agent/blueprint/dump.py +520 -0
- hwpx_automation/office/agent/blueprint/mapping.py +312 -0
- hwpx_automation/office/agent/blueprint/model.py +722 -0
- hwpx_automation/office/agent/blueprint/native.py +621 -0
- hwpx_automation/office/agent/blueprint/replay.py +622 -0
- hwpx_automation/office/agent/catalog.py +252 -0
- hwpx_automation/office/agent/cli.py +647 -0
- hwpx_automation/office/agent/commands.py +1383 -0
- hwpx_automation/office/agent/document.py +801 -0
- hwpx_automation/office/agent/form_plan.py +1760 -0
- hwpx_automation/office/agent/model.py +808 -0
- hwpx_automation/office/agent/path.py +155 -0
- hwpx_automation/office/agent/query.py +230 -0
- hwpx_automation/office/agent/story.py +207 -0
- hwpx_automation/office/authoring/__init__.py +3542 -0
- hwpx_automation/office/authoring/advanced_generators.py +154 -0
- hwpx_automation/office/authoring/builder/__init__.py +52 -0
- hwpx_automation/office/authoring/builder/core.py +996 -0
- hwpx_automation/office/authoring/builder/report.py +195 -0
- hwpx_automation/office/authoring/design/__init__.py +30 -0
- hwpx_automation/office/authoring/design/_support.py +144 -0
- hwpx_automation/office/authoring/design/composer.py +282 -0
- hwpx_automation/office/authoring/design/harvest.py +305 -0
- hwpx_automation/office/authoring/design/plan.py +69 -0
- hwpx_automation/office/authoring/design/profile.py +88 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +24 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/validator.py +107 -0
- hwpx_automation/office/authoring/presets/__init__.py +22 -0
- hwpx_automation/office/authoring/presets/proposal.py +538 -0
- hwpx_automation/office/authoring/report_parser.py +141 -0
- hwpx_automation/office/authoring/style_profile.py +437 -0
- hwpx_automation/office/authoring/template_analyzer.py +657 -0
- hwpx_automation/office/compliance/__init__.py +38 -0
- hwpx_automation/office/compliance/official_lint.py +478 -0
- hwpx_automation/office/compliance/pii.py +388 -0
- hwpx_automation/office/document_ops/__init__.py +13 -0
- hwpx_automation/office/document_ops/comparison.py +62 -0
- hwpx_automation/office/document_ops/mail_merge.py +73 -0
- hwpx_automation/office/document_ops/redline.py +35 -0
- hwpx_automation/office/evalplan/__init__.py +36 -0
- hwpx_automation/office/evalplan/runtime.py +2762 -0
- hwpx_automation/office/exam/__init__.py +44 -0
- hwpx_automation/office/exam/compose.py +282 -0
- hwpx_automation/office/exam/ir.py +44 -0
- hwpx_automation/office/exam/measure.py +163 -0
- hwpx_automation/office/exam/parser.py +150 -0
- hwpx_automation/office/exam/profile.py +123 -0
- hwpx_automation/office/form_fill/__init__.py +66 -0
- hwpx_automation/office/form_fill/classification.py +108 -0
- hwpx_automation/office/form_fill/fill_residue.py +242 -0
- hwpx_automation/office/form_fill/fit/__init__.py +36 -0
- hwpx_automation/office/form_fill/fit/apply.py +24 -0
- hwpx_automation/office/form_fill/fit/engine.py +24 -0
- hwpx_automation/office/form_fill/fit/measure.py +50 -0
- hwpx_automation/office/form_fill/fit/policy.py +28 -0
- hwpx_automation/office/form_fill/fit/report.py +28 -0
- hwpx_automation/office/form_fill/fit/seal.py +457 -0
- hwpx_automation/office/form_fill/fit/wordbox.py +1343 -0
- hwpx_automation/office/form_fill/guidance.py +704 -0
- hwpx_automation/office/form_fill/quality.py +961 -0
- hwpx_automation/office/form_fill/split_run.py +333 -0
- hwpx_automation/office/form_fill/template_formfit.py +656 -0
- hwpx_automation/office/house_style/__init__.py +196 -0
- hwpx_automation/office/house_style/composition.py +68 -0
- hwpx_automation/office/house_style/data/bank.json +625 -0
- hwpx_automation/office/house_style/data/genres.json +43 -0
- hwpx_automation/office/quality/__init__.py +14 -0
- hwpx_automation/office/quality/page_guard.py +277 -0
- hwpx_automation/office/rendering/__init__.py +145 -0
- hwpx_automation/office/rendering/_hancom_open_rate.ps1 +374 -0
- hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +162 -0
- hwpx_automation/office/rendering/_render_hwpx.ps1 +72 -0
- hwpx_automation/office/rendering/_render_hwpx_mac.applescript +249 -0
- hwpx_automation/office/rendering/block_splits.py +76 -0
- hwpx_automation/office/rendering/detectors.py +151 -0
- hwpx_automation/office/rendering/diff.py +153 -0
- hwpx_automation/office/rendering/fixture_corpus.py +215 -0
- hwpx_automation/office/rendering/oracle.py +909 -0
- hwpx_automation/office/rendering/page_qa.py +245 -0
- hwpx_automation/office/rendering/qa_contracts.py +293 -0
- hwpx_automation/office/rendering/qa_metrics.py +241 -0
- hwpx_automation/office/rendering/worker.py +290 -0
- hwpx_automation/office/utilities/__init__.py +12 -0
- hwpx_automation/office/utilities/table_compute.py +477 -0
- hwpx_automation/ops_services/__init__.py +1 -0
- hwpx_automation/ops_services/_border_fill.py +283 -0
- hwpx_automation/ops_services/composition.py +55 -0
- hwpx_automation/ops_services/content_layout.py +322 -0
- hwpx_automation/ops_services/context.py +213 -0
- hwpx_automation/ops_services/form_fields.py +557 -0
- hwpx_automation/ops_services/media.py +178 -0
- hwpx_automation/ops_services/memo_style.py +477 -0
- hwpx_automation/ops_services/package_validation.py +166 -0
- hwpx_automation/ops_services/planning.py +201 -0
- hwpx_automation/ops_services/preview_export.py +585 -0
- hwpx_automation/ops_services/read_query.py +601 -0
- hwpx_automation/ops_services/save_policy.py +604 -0
- hwpx_automation/ops_services/tables.py +539 -0
- hwpx_automation/ops_services/transactions.py +616 -0
- hwpx_automation/preview_output_models.py +69 -0
- hwpx_automation/public-modules.json +206 -0
- hwpx_automation/py.typed +1 -0
- hwpx_automation/quality.py +351 -0
- hwpx_automation/quality_generation.py +725 -0
- hwpx_automation/runtime.py +321 -0
- hwpx_automation/runtime_services.py +100 -0
- hwpx_automation/server.py +259 -0
- hwpx_automation/storage.py +747 -0
- hwpx_automation/tool_bindings.py +170 -0
- hwpx_automation/tool_contract.py +982 -0
- hwpx_automation/upstream.py +755 -0
- hwpx_automation/utils/__init__.py +2 -0
- hwpx_automation/utils/helpers.py +29 -0
- hwpx_automation/visual_qa.py +667 -0
- hwpx_automation/workflow/__init__.py +55 -0
- hwpx_automation/workflow/adapters.py +482 -0
- hwpx_automation/workflow/dispatcher.py +213 -0
- hwpx_automation/workflow/models.py +243 -0
- hwpx_automation/workflow/policy.py +198 -0
- hwpx_automation/workflow/render_contracts.py +173 -0
- hwpx_automation/workflow/render_metrics.py +196 -0
- hwpx_automation/workflow/render_queue.py +482 -0
- hwpx_automation/workflow/render_security.py +172 -0
- hwpx_automation/workflow/render_transport.py +369 -0
- hwpx_automation/workflow/rendering.py +206 -0
- hwpx_automation/workflow/service.py +758 -0
- hwpx_automation/workflow/state_machine.py +65 -0
- hwpx_automation/workflow/store.py +747 -0
- hwpx_automation/workspace.py +1694 -0
- python_hwpx_automation-6.0.3.dist-info/METADATA +279 -0
- python_hwpx_automation-6.0.3.dist-info/RECORD +217 -0
- python_hwpx_automation-6.0.3.dist-info/WHEEL +5 -0
- python_hwpx_automation-6.0.3.dist-info/entry_points.txt +3 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/LICENSE +178 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/NOTICE +14 -0
- python_hwpx_automation-6.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Authoring automation handlers for the optional MCP adapter."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from datetime import date
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from hwpx.tools import report_utils as hwpx_report_utils
|
|
13
|
+
from ..office.authoring.report_parser import (
|
|
14
|
+
parse_government_report_text as parse_hwpx_government_report_text,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from .. import quality as quality_contract
|
|
18
|
+
from ..core.content import (
|
|
19
|
+
copy_document_file,
|
|
20
|
+
)
|
|
21
|
+
from ..core.document import create_blank
|
|
22
|
+
from ..document_state import document_state_payload
|
|
23
|
+
from ..office.authoring import (
|
|
24
|
+
create_document_from_plan as build_document_from_plan,
|
|
25
|
+
)
|
|
26
|
+
from ..office.authoring import (
|
|
27
|
+
get_document_plan_schema as get_hwpx_document_plan_schema,
|
|
28
|
+
)
|
|
29
|
+
from ..office.authoring import (
|
|
30
|
+
inspect_document_authoring_quality as inspect_authoring_document_quality,
|
|
31
|
+
)
|
|
32
|
+
from ..office.authoring import (
|
|
33
|
+
normalize_document_plan as normalize_hwpx_document_plan,
|
|
34
|
+
)
|
|
35
|
+
from ..office.authoring import (
|
|
36
|
+
validate_document_plan as validate_hwpx_document_plan,
|
|
37
|
+
)
|
|
38
|
+
from ..office.authoring.presets import (
|
|
39
|
+
create_proposal_document as build_proposal_document,
|
|
40
|
+
)
|
|
41
|
+
from ..office.authoring.presets import (
|
|
42
|
+
inspect_proposal_quality as inspect_proposal_document_quality,
|
|
43
|
+
)
|
|
44
|
+
from ..office.authoring.style_profile import (
|
|
45
|
+
describe_template as describe_hwpx_template,
|
|
46
|
+
)
|
|
47
|
+
from ..office.authoring.style_profile import (
|
|
48
|
+
list_templates as list_hwpx_templates,
|
|
49
|
+
)
|
|
50
|
+
from ..office.authoring.style_profile import (
|
|
51
|
+
register_template as register_hwpx_template,
|
|
52
|
+
)
|
|
53
|
+
from ..office.document_ops import (
|
|
54
|
+
build_comparison_table_plan as build_hwpx_comparison_table_plan,
|
|
55
|
+
)
|
|
56
|
+
from ..quality_generation import (
|
|
57
|
+
create_quality_document_fallback,
|
|
58
|
+
)
|
|
59
|
+
from ..storage import (
|
|
60
|
+
build_hwpx_open_safety_report,
|
|
61
|
+
build_hwpx_verification_report,
|
|
62
|
+
)
|
|
63
|
+
from ..utils.helpers import resolve_path
|
|
64
|
+
from ._shared import (
|
|
65
|
+
_diff_sources,
|
|
66
|
+
_inspect_authoring_quality,
|
|
67
|
+
_proposal_quality_fallback,
|
|
68
|
+
_quality_profile_argument,
|
|
69
|
+
_revision_guard,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
_VERBOSITY_MODES = {"compact", "full"}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _normalize_verbosity(verbosity: str | None) -> str:
|
|
76
|
+
value = (verbosity or "compact").strip().lower()
|
|
77
|
+
if value not in _VERBOSITY_MODES:
|
|
78
|
+
expected = ", ".join(sorted(_VERBOSITY_MODES))
|
|
79
|
+
raise ValueError(f"verbosity must be one of: {expected}")
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _compact_open_safety(open_safety: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
84
|
+
if not isinstance(open_safety, dict):
|
|
85
|
+
return None
|
|
86
|
+
compact: dict[str, Any] = {
|
|
87
|
+
"ok": bool(open_safety.get("ok")),
|
|
88
|
+
"summary": open_safety.get("summary"),
|
|
89
|
+
}
|
|
90
|
+
for key in ("validatePackage", "validateDocument", "reopen"):
|
|
91
|
+
value = open_safety.get(key)
|
|
92
|
+
if isinstance(value, dict):
|
|
93
|
+
compact[key] = {"ok": bool(value.get("ok"))}
|
|
94
|
+
return compact
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _verification_is_successful(verification: dict[str, Any] | None) -> bool:
|
|
98
|
+
if not isinstance(verification, dict):
|
|
99
|
+
return False
|
|
100
|
+
open_safety = verification.get("openSafety")
|
|
101
|
+
if isinstance(open_safety, dict) and not bool(open_safety.get("ok")):
|
|
102
|
+
return False
|
|
103
|
+
return bool(verification.get("ok", True))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _compact_verification_report(verification: dict[str, Any]) -> dict[str, Any]:
|
|
107
|
+
if not _verification_is_successful(verification):
|
|
108
|
+
return verification
|
|
109
|
+
compact: dict[str, Any] = {
|
|
110
|
+
"ok": bool(verification.get("ok", True)),
|
|
111
|
+
"summary": verification.get("summary", "verification passed"),
|
|
112
|
+
}
|
|
113
|
+
for key in ("filePath", "fileSizeBytes", "warnings"):
|
|
114
|
+
if key in verification:
|
|
115
|
+
compact[key] = verification[key]
|
|
116
|
+
open_safety = _compact_open_safety(verification.get("openSafety"))
|
|
117
|
+
if open_safety is not None:
|
|
118
|
+
compact["openSafety"] = open_safety
|
|
119
|
+
return compact
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _apply_write_verbosity(
|
|
123
|
+
payload: dict[str, Any], verbosity: str | None
|
|
124
|
+
) -> dict[str, Any]:
|
|
125
|
+
if _normalize_verbosity(verbosity) == "full":
|
|
126
|
+
return payload
|
|
127
|
+
compacted = dict(payload)
|
|
128
|
+
for key in ("verification", "verificationReport"):
|
|
129
|
+
verification = compacted.get(key)
|
|
130
|
+
if isinstance(verification, dict):
|
|
131
|
+
compacted[key] = _compact_verification_report(verification)
|
|
132
|
+
open_safety = compacted.get("openSafety")
|
|
133
|
+
if isinstance(open_safety, dict) and bool(open_safety.get("ok")):
|
|
134
|
+
compacted["openSafety"] = _compact_open_safety(open_safety)
|
|
135
|
+
return compacted
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def create_document(
|
|
139
|
+
filename: str,
|
|
140
|
+
title: str = None,
|
|
141
|
+
author: str = None,
|
|
142
|
+
expected_revision: str = None,
|
|
143
|
+
) -> dict:
|
|
144
|
+
"""새 HWPX 문서를 생성하고 즉시 저장합니다."""
|
|
145
|
+
del title, author
|
|
146
|
+
path = resolve_path(filename)
|
|
147
|
+
if Path(path).exists():
|
|
148
|
+
guard = _revision_guard(path, expected_revision)
|
|
149
|
+
if guard is not None:
|
|
150
|
+
return guard
|
|
151
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
verification = create_blank(path)
|
|
153
|
+
return {
|
|
154
|
+
"filename": filename,
|
|
155
|
+
"created": True,
|
|
156
|
+
"verification": verification,
|
|
157
|
+
"openSafety": verification["openSafety"],
|
|
158
|
+
**document_state_payload(path),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _save_generated_document(doc: Any, path: str, *, quality: Any = None) -> dict:
|
|
163
|
+
target = Path(path)
|
|
164
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
# Phase F: generation also funnels through the one SavePipeline gate.
|
|
166
|
+
quality_contract.assert_write_capability()
|
|
167
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
168
|
+
prefix=f".{target.stem}.",
|
|
169
|
+
suffix=target.suffix or ".hwpx",
|
|
170
|
+
dir=str(target.parent),
|
|
171
|
+
)
|
|
172
|
+
tmp_path = Path(tmp_name)
|
|
173
|
+
try:
|
|
174
|
+
os.close(fd)
|
|
175
|
+
report = quality_contract.save_through_pipeline(doc, tmp_path, quality=quality)
|
|
176
|
+
verification = build_hwpx_verification_report(tmp_path)
|
|
177
|
+
if not verification["openSafety"]["ok"]:
|
|
178
|
+
raise RuntimeError(
|
|
179
|
+
"generated HWPX failed open-safety verification: "
|
|
180
|
+
+ verification["openSafety"]["summary"]
|
|
181
|
+
)
|
|
182
|
+
os.replace(tmp_path, target)
|
|
183
|
+
verification["filePath"] = str(target)
|
|
184
|
+
verification["visualComplete"] = quality_contract.visual_complete_block(report)
|
|
185
|
+
return verification
|
|
186
|
+
except BaseException:
|
|
187
|
+
tmp_path.unlink(missing_ok=True)
|
|
188
|
+
raise
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _handoff_status(quality: dict) -> str:
|
|
192
|
+
return "ready" if bool(quality.get("pass")) else "needs_revision"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _next_action(quality: dict) -> str:
|
|
196
|
+
if bool(quality.get("pass")):
|
|
197
|
+
return "structural handoff is ready; complete visual review before final submission"
|
|
198
|
+
return "review quality.gaps and profile repair_hints, then rerun validate/analyze/create"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_document_plan_schema() -> dict:
|
|
202
|
+
"""document_plan(생성 계획)의 JSON Schema를 반환합니다.
|
|
203
|
+
|
|
204
|
+
Structured Outputs/외부 검증 계약으로 직접 사용하세요 — 자유형 JSON을 만들고
|
|
205
|
+
validate_document_plan에서 거부당하는 대신, 이 스키마로 생성을 제약합니다.
|
|
206
|
+
"""
|
|
207
|
+
if get_hwpx_document_plan_schema is None:
|
|
208
|
+
raise RuntimeError(
|
|
209
|
+
"installed python-hwpx does not provide document-plan authoring"
|
|
210
|
+
)
|
|
211
|
+
return get_hwpx_document_plan_schema()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def validate_document_plan(document_plan: dict) -> dict:
|
|
215
|
+
"""선언형 hwpx.document_plan.v1 생성 계획을 검증합니다. 파일은 쓰지 않습니다."""
|
|
216
|
+
if validate_hwpx_document_plan is None or normalize_hwpx_document_plan is None:
|
|
217
|
+
raise RuntimeError(
|
|
218
|
+
"installed python-hwpx does not provide document-plan authoring"
|
|
219
|
+
)
|
|
220
|
+
report = validate_hwpx_document_plan(document_plan or {})
|
|
221
|
+
result = report.to_dict()
|
|
222
|
+
if report.ok:
|
|
223
|
+
result["can_create"] = True
|
|
224
|
+
result["normalizedPlan"] = normalize_hwpx_document_plan(
|
|
225
|
+
document_plan or {}
|
|
226
|
+
).to_dict()
|
|
227
|
+
result["next_tool"] = "create_document_from_plan"
|
|
228
|
+
else:
|
|
229
|
+
result["can_create"] = False
|
|
230
|
+
result["next_tool"] = "validate_document_plan"
|
|
231
|
+
result["next_action"] = (
|
|
232
|
+
"repair document_plan using repairHints, then rerun validate_document_plan"
|
|
233
|
+
)
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def markdown_to_document_plan(
|
|
238
|
+
markdown: str,
|
|
239
|
+
title: str = None,
|
|
240
|
+
metadata: dict = None,
|
|
241
|
+
style_preset: str = "standard_korean_business",
|
|
242
|
+
) -> dict:
|
|
243
|
+
"""Markdown을 검증 가능한 hwpx.document_plan.v1 초안으로 변환합니다. 파일은 쓰지 않습니다."""
|
|
244
|
+
if validate_hwpx_document_plan is None or normalize_hwpx_document_plan is None:
|
|
245
|
+
raise RuntimeError(
|
|
246
|
+
"installed python-hwpx does not provide document-plan authoring"
|
|
247
|
+
)
|
|
248
|
+
from ..markdown_plan import (
|
|
249
|
+
markdown_to_document_plan as _build_markdown_document_plan,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
converted = _build_markdown_document_plan(
|
|
253
|
+
markdown or "",
|
|
254
|
+
title=title,
|
|
255
|
+
metadata=metadata or {},
|
|
256
|
+
style_preset=style_preset,
|
|
257
|
+
)
|
|
258
|
+
plan = converted.plan
|
|
259
|
+
report = validate_hwpx_document_plan(plan)
|
|
260
|
+
validation = report.to_dict()
|
|
261
|
+
result: dict[str, Any] = {
|
|
262
|
+
"ok": report.ok,
|
|
263
|
+
"can_create": report.ok,
|
|
264
|
+
"document_plan": plan,
|
|
265
|
+
"validation": validation,
|
|
266
|
+
"warnings": list(converted.warnings),
|
|
267
|
+
"next_tool": "create_document_from_plan"
|
|
268
|
+
if report.ok
|
|
269
|
+
else "markdown_to_document_plan",
|
|
270
|
+
}
|
|
271
|
+
if report.ok:
|
|
272
|
+
result["normalizedPlan"] = normalize_hwpx_document_plan(plan).to_dict()
|
|
273
|
+
else:
|
|
274
|
+
result["next_action"] = (
|
|
275
|
+
"repair Markdown or document_plan using validation.repairHints, then rerun markdown_to_document_plan"
|
|
276
|
+
)
|
|
277
|
+
return result
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def analyze_document_plan(
|
|
281
|
+
document_plan: dict,
|
|
282
|
+
destination_filename: str = None,
|
|
283
|
+
style_preset: str = "standard_korean_business",
|
|
284
|
+
quality_profile: str = None,
|
|
285
|
+
profile: dict = None,
|
|
286
|
+
) -> dict:
|
|
287
|
+
"""선언형 document_plan을 파일 쓰기 없이 분석하고 품질 미리보기를 반환합니다."""
|
|
288
|
+
if (
|
|
289
|
+
build_document_from_plan is None
|
|
290
|
+
or validate_hwpx_document_plan is None
|
|
291
|
+
or normalize_hwpx_document_plan is None
|
|
292
|
+
):
|
|
293
|
+
raise RuntimeError(
|
|
294
|
+
"installed python-hwpx does not provide document-plan authoring"
|
|
295
|
+
)
|
|
296
|
+
validation = validate_hwpx_document_plan(document_plan or {})
|
|
297
|
+
validation_payload = validation.to_dict()
|
|
298
|
+
result = {
|
|
299
|
+
**validation_payload,
|
|
300
|
+
"mutated": False,
|
|
301
|
+
"destination": {
|
|
302
|
+
"filename": destination_filename,
|
|
303
|
+
"path": resolve_path(destination_filename)
|
|
304
|
+
if destination_filename
|
|
305
|
+
else None,
|
|
306
|
+
"required_for_create": bool(destination_filename),
|
|
307
|
+
},
|
|
308
|
+
"style_preset": style_preset,
|
|
309
|
+
"quality_profile": _quality_profile_argument(quality_profile, profile),
|
|
310
|
+
}
|
|
311
|
+
if not validation.ok:
|
|
312
|
+
result.update(
|
|
313
|
+
{
|
|
314
|
+
"can_create": False,
|
|
315
|
+
"handoff_status": "needs_revision",
|
|
316
|
+
"next_tool": "validate_document_plan",
|
|
317
|
+
"next_action": (
|
|
318
|
+
"repair document_plan using repairHints, then rerun analyze_document_plan"
|
|
319
|
+
),
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
return result
|
|
323
|
+
|
|
324
|
+
normalized = normalize_hwpx_document_plan(document_plan or {})
|
|
325
|
+
quality: dict | None = None
|
|
326
|
+
doc = build_document_from_plan(document_plan or {}, preset=style_preset)
|
|
327
|
+
try:
|
|
328
|
+
quality = _inspect_authoring_quality(
|
|
329
|
+
doc,
|
|
330
|
+
document_plan=document_plan or {},
|
|
331
|
+
quality_profile=quality_profile,
|
|
332
|
+
profile=profile,
|
|
333
|
+
)
|
|
334
|
+
finally:
|
|
335
|
+
doc.close()
|
|
336
|
+
|
|
337
|
+
result.update(
|
|
338
|
+
{
|
|
339
|
+
"can_create": True,
|
|
340
|
+
"normalizedPlan": normalized.to_dict(),
|
|
341
|
+
"quality_preview": quality,
|
|
342
|
+
"handoff_status": _handoff_status(quality),
|
|
343
|
+
"next_tool": "create_document_from_plan",
|
|
344
|
+
"next_action": _next_action(quality),
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
return result
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def create_document_from_plan(
|
|
351
|
+
filename: str,
|
|
352
|
+
document_plan: dict,
|
|
353
|
+
style_preset: str = "standard_korean_business",
|
|
354
|
+
quality_profile: str = None,
|
|
355
|
+
profile: dict = None,
|
|
356
|
+
verify_render: bool = False,
|
|
357
|
+
verbosity: str = "compact",
|
|
358
|
+
) -> dict:
|
|
359
|
+
"""선언형 document_plan으로 HWPX를 생성하고 즉시 저장/검증합니다.
|
|
360
|
+
|
|
361
|
+
document_plan.metadata.document_type 이 '공문'/'보고서'/'가정통신문'이면 실제
|
|
362
|
+
한컴-harvest 프로파일로 생성됩니다. 공문은 결문 메타
|
|
363
|
+
document_plan.gyeolmun = {issuer, productionNumber, enforcementDate,
|
|
364
|
+
disclosure} 를 지원하고, 공문서 작성규정 구조 hard-gate 결과가
|
|
365
|
+
quality.gongmun_structure(structure_pass)로 반환됩니다. 맞춤법은
|
|
366
|
+
quality.korean_proofing_status(정직 'unverified', 거짓 통과 없음)로 보고합니다.
|
|
367
|
+
출력은 **HWPX 전용**입니다(.odt/.docx/.pdf 등 비-HWPX 미지원; 기안문 ODT는
|
|
368
|
+
별도 트랙). verify_render=True 이고 Mac 한컴 오라클이 가용하면
|
|
369
|
+
quality.render_checked/visual_complete 가 실제 렌더 영수증이 됩니다.
|
|
370
|
+
"""
|
|
371
|
+
return _create_document_from_plan_impl(
|
|
372
|
+
filename,
|
|
373
|
+
document_plan,
|
|
374
|
+
style_preset=style_preset,
|
|
375
|
+
quality_profile=quality_profile,
|
|
376
|
+
profile=profile,
|
|
377
|
+
verify_render=verify_render,
|
|
378
|
+
verbosity=verbosity,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _create_document_from_plan_impl(
|
|
383
|
+
filename: str,
|
|
384
|
+
document_plan: dict,
|
|
385
|
+
*,
|
|
386
|
+
style_preset: str = "standard_korean_business",
|
|
387
|
+
quality_profile: str | dict | None = None,
|
|
388
|
+
profile: dict | None = None,
|
|
389
|
+
verify_render: bool = False,
|
|
390
|
+
verbosity: str | None = "compact",
|
|
391
|
+
) -> dict:
|
|
392
|
+
if (
|
|
393
|
+
build_document_from_plan is None
|
|
394
|
+
or inspect_authoring_document_quality is None
|
|
395
|
+
or validate_hwpx_document_plan is None
|
|
396
|
+
):
|
|
397
|
+
raise RuntimeError(
|
|
398
|
+
"installed python-hwpx does not provide document-plan authoring"
|
|
399
|
+
)
|
|
400
|
+
# FR-011: HWPX-only output. Non-HWPX formats (ODT/기안문, docx, pdf) are a
|
|
401
|
+
# separate track — never silently attempt them.
|
|
402
|
+
suffix = Path(filename).suffix.lower()
|
|
403
|
+
if suffix and suffix != ".hwpx":
|
|
404
|
+
return {
|
|
405
|
+
"filename": filename,
|
|
406
|
+
"created": False,
|
|
407
|
+
"error": f"unsupported output format {suffix!r}; only .hwpx is supported",
|
|
408
|
+
"handoff_status": "unsupported_format",
|
|
409
|
+
"next_action": "use a .hwpx filename (ODT 기안문 등 비-HWPX 포맷은 별도 트랙)",
|
|
410
|
+
}
|
|
411
|
+
validation = validate_hwpx_document_plan(document_plan or {})
|
|
412
|
+
if not validation.ok:
|
|
413
|
+
return {
|
|
414
|
+
"filename": filename,
|
|
415
|
+
"created": False,
|
|
416
|
+
"error": "document plan failed validation",
|
|
417
|
+
"plan_validation": validation.to_dict(),
|
|
418
|
+
"handoff_status": "needs_revision",
|
|
419
|
+
"next_tool": "validate_document_plan",
|
|
420
|
+
"next_action": (
|
|
421
|
+
"repair document_plan using repairHints, then rerun validate_document_plan"
|
|
422
|
+
),
|
|
423
|
+
}
|
|
424
|
+
path = resolve_path(filename)
|
|
425
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
426
|
+
doc = build_document_from_plan(document_plan or {}, preset=style_preset)
|
|
427
|
+
try:
|
|
428
|
+
verification = _save_generated_document(doc, path)
|
|
429
|
+
finally:
|
|
430
|
+
doc.close()
|
|
431
|
+
report = _inspect_authoring_quality(
|
|
432
|
+
path,
|
|
433
|
+
document_plan=document_plan or {},
|
|
434
|
+
quality_profile=quality_profile,
|
|
435
|
+
profile=profile,
|
|
436
|
+
verify_render=verify_render,
|
|
437
|
+
)
|
|
438
|
+
result = {
|
|
439
|
+
"filename": filename,
|
|
440
|
+
"created": True,
|
|
441
|
+
"style_preset": style_preset,
|
|
442
|
+
"quality_profile": _quality_profile_argument(quality_profile, profile),
|
|
443
|
+
"plan_validation": validation.to_dict(),
|
|
444
|
+
"handoff_status": _handoff_status(report),
|
|
445
|
+
"next_action": _next_action(report),
|
|
446
|
+
"quality": report,
|
|
447
|
+
"verification": verification,
|
|
448
|
+
}
|
|
449
|
+
return _apply_write_verbosity(result, verbosity)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def create_government_report_document(
|
|
453
|
+
filename: str,
|
|
454
|
+
document_plan: dict,
|
|
455
|
+
profile: dict = None,
|
|
456
|
+
verbosity: str = "compact",
|
|
457
|
+
) -> dict:
|
|
458
|
+
"""정부보고서 프리셋으로 document_plan을 생성하고 즉시 저장/검증합니다."""
|
|
459
|
+
return _create_document_from_plan_impl(
|
|
460
|
+
filename,
|
|
461
|
+
document_plan,
|
|
462
|
+
style_preset="government_report",
|
|
463
|
+
quality_profile="government_report",
|
|
464
|
+
profile=profile,
|
|
465
|
+
verbosity=verbosity,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _report_values(values: list | tuple | dict | None) -> tuple[list, dict]:
|
|
470
|
+
if isinstance(values, dict):
|
|
471
|
+
args = values.get("args")
|
|
472
|
+
if args is None:
|
|
473
|
+
args = values.get("values")
|
|
474
|
+
if args is None:
|
|
475
|
+
args = []
|
|
476
|
+
if not isinstance(args, list | tuple):
|
|
477
|
+
args = [args]
|
|
478
|
+
kwargs = {
|
|
479
|
+
str(key): value
|
|
480
|
+
for key, value in values.items()
|
|
481
|
+
if key not in {"args", "values"}
|
|
482
|
+
}
|
|
483
|
+
return list(args), kwargs
|
|
484
|
+
if isinstance(values, list | tuple):
|
|
485
|
+
args = list(values)
|
|
486
|
+
if args and isinstance(args[-1], dict):
|
|
487
|
+
kwargs = dict(args.pop())
|
|
488
|
+
else:
|
|
489
|
+
kwargs = {}
|
|
490
|
+
return args, kwargs
|
|
491
|
+
if values is None:
|
|
492
|
+
return [], {}
|
|
493
|
+
return [values], {}
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _optional_int(value: Any, default: int) -> int:
|
|
497
|
+
if value is None:
|
|
498
|
+
return default
|
|
499
|
+
return int(value)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def compute_report_value(
|
|
503
|
+
operation: str,
|
|
504
|
+
values: list | dict = None, # type: ignore[assignment] # Frozen ToolSpec default.
|
|
505
|
+
) -> dict:
|
|
506
|
+
"""정부보고서 표/문장에 넣을 계산값을 python-hwpx report_utils로 계산합니다."""
|
|
507
|
+
if hwpx_report_utils is None:
|
|
508
|
+
raise RuntimeError("installed python-hwpx does not provide report_utils")
|
|
509
|
+
|
|
510
|
+
normalized = str(operation or "").strip().lower().replace("-", "_")
|
|
511
|
+
args, kwargs = _report_values(values)
|
|
512
|
+
try:
|
|
513
|
+
if normalized in {"krw_hangul", "hangul_krw"}:
|
|
514
|
+
value = hwpx_report_utils.format_krw_hangul(args[0])
|
|
515
|
+
elif normalized in {"commas", "number_commas"}:
|
|
516
|
+
value = hwpx_report_utils.format_number_commas(args[0])
|
|
517
|
+
elif normalized == "age":
|
|
518
|
+
today_arg = kwargs.get("today")
|
|
519
|
+
today = date.fromisoformat(today_arg) if today_arg else None
|
|
520
|
+
value = hwpx_report_utils.calculate_age(args[0], today=today)
|
|
521
|
+
elif normalized == "delta":
|
|
522
|
+
value = hwpx_report_utils.format_delta(
|
|
523
|
+
args[0],
|
|
524
|
+
negative_prefix=str(kwargs.get("negative_prefix", "△")),
|
|
525
|
+
)
|
|
526
|
+
elif normalized == "delta_percent":
|
|
527
|
+
value = hwpx_report_utils.format_delta_percent(
|
|
528
|
+
args[0],
|
|
529
|
+
args[1],
|
|
530
|
+
digits=_optional_int(kwargs.get("digits"), 1),
|
|
531
|
+
)
|
|
532
|
+
elif normalized in {"ratio", "ratios"}:
|
|
533
|
+
value = hwpx_report_utils.calculate_ratios(
|
|
534
|
+
args[0],
|
|
535
|
+
args[1],
|
|
536
|
+
digits=_optional_int(kwargs.get("digits"), 1),
|
|
537
|
+
)
|
|
538
|
+
elif normalized in {"date", "normalize_date", "korean_date"}:
|
|
539
|
+
value = hwpx_report_utils.normalize_korean_date(args[0])
|
|
540
|
+
else:
|
|
541
|
+
return {
|
|
542
|
+
"operation": operation,
|
|
543
|
+
"value": None,
|
|
544
|
+
"warnings": [f"unsupported report value operation: {operation}"],
|
|
545
|
+
}
|
|
546
|
+
except (IndexError, KeyError, TypeError, ValueError) as exc:
|
|
547
|
+
return {"operation": operation, "value": None, "warnings": [str(exc)]}
|
|
548
|
+
|
|
549
|
+
return {"operation": normalized, "value": value, "warnings": []}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def register_template(
|
|
553
|
+
name: str,
|
|
554
|
+
source_filename: str,
|
|
555
|
+
registry_path: str = None,
|
|
556
|
+
description: str = "",
|
|
557
|
+
tags: list[str] = None,
|
|
558
|
+
) -> dict:
|
|
559
|
+
"""사용자 템플릿을 등록하고 style profile과 placeholder contract를 저장합니다."""
|
|
560
|
+
if register_hwpx_template is None:
|
|
561
|
+
raise RuntimeError(
|
|
562
|
+
"installed python-hwpx does not provide template registry tools"
|
|
563
|
+
)
|
|
564
|
+
return register_hwpx_template(
|
|
565
|
+
name,
|
|
566
|
+
resolve_path(source_filename),
|
|
567
|
+
registry_path=resolve_path(registry_path) if registry_path else None,
|
|
568
|
+
description=description,
|
|
569
|
+
tags=tags or [],
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def list_templates(registry_path: str = None) -> dict:
|
|
574
|
+
"""등록된 템플릿 목록을 반환합니다."""
|
|
575
|
+
if list_hwpx_templates is None:
|
|
576
|
+
raise RuntimeError(
|
|
577
|
+
"installed python-hwpx does not provide template registry tools"
|
|
578
|
+
)
|
|
579
|
+
return list_hwpx_templates(
|
|
580
|
+
registry_path=resolve_path(registry_path) if registry_path else None
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def describe_template(
|
|
585
|
+
name: str,
|
|
586
|
+
registry_path: str = None,
|
|
587
|
+
values: dict = None,
|
|
588
|
+
) -> dict:
|
|
589
|
+
"""등록 템플릿 상세와 placeholder 미충전 리포트를 반환합니다."""
|
|
590
|
+
if describe_hwpx_template is None:
|
|
591
|
+
raise RuntimeError(
|
|
592
|
+
"installed python-hwpx does not provide template registry tools"
|
|
593
|
+
)
|
|
594
|
+
return describe_hwpx_template(
|
|
595
|
+
name,
|
|
596
|
+
registry_path=resolve_path(registry_path) if registry_path else None,
|
|
597
|
+
values=values or {},
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def parse_government_report_text(text: str, title: str = "") -> dict:
|
|
602
|
+
"""붙여넣은 정부보고서 텍스트를 document_plan으로 파싱하고 검증합니다."""
|
|
603
|
+
if parse_hwpx_government_report_text is None or validate_hwpx_document_plan is None:
|
|
604
|
+
raise RuntimeError(
|
|
605
|
+
"installed python-hwpx does not provide government-report parsing"
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
document_plan = parse_hwpx_government_report_text(text or "", title=title or "")
|
|
609
|
+
validation = validate_hwpx_document_plan(document_plan)
|
|
610
|
+
result = {
|
|
611
|
+
"document_plan": document_plan,
|
|
612
|
+
"plan_validation": validation.to_dict(),
|
|
613
|
+
"can_create": bool(validation.ok),
|
|
614
|
+
}
|
|
615
|
+
if validation.ok:
|
|
616
|
+
result["next_tool"] = "create_government_report_document"
|
|
617
|
+
result["next_action"] = (
|
|
618
|
+
"review document_plan, then call create_government_report_document"
|
|
619
|
+
)
|
|
620
|
+
else:
|
|
621
|
+
result["next_tool"] = "parse_government_report_text"
|
|
622
|
+
result["next_action"] = (
|
|
623
|
+
"repair source text or document_plan using plan_validation"
|
|
624
|
+
)
|
|
625
|
+
return result
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def create_comparison_table_document(
|
|
629
|
+
filename: str,
|
|
630
|
+
old_filename: str = None,
|
|
631
|
+
new_filename: str = None,
|
|
632
|
+
old_paragraphs: list[str] = None,
|
|
633
|
+
new_paragraphs: list[str] = None,
|
|
634
|
+
title: str = "신구대조표",
|
|
635
|
+
include_equal: bool = True,
|
|
636
|
+
verbosity: str = "compact",
|
|
637
|
+
) -> dict:
|
|
638
|
+
"""두 문서/문단을 좌우 신구대조표 HWPX로 생성하고 검증합니다."""
|
|
639
|
+
if (
|
|
640
|
+
build_hwpx_comparison_table_plan is None
|
|
641
|
+
or build_document_from_plan is None
|
|
642
|
+
or validate_hwpx_document_plan is None
|
|
643
|
+
):
|
|
644
|
+
raise RuntimeError(
|
|
645
|
+
"installed python-hwpx does not provide comparison table generation"
|
|
646
|
+
)
|
|
647
|
+
old_source, new_source = _diff_sources(
|
|
648
|
+
old_filename=old_filename,
|
|
649
|
+
new_filename=new_filename,
|
|
650
|
+
old_paragraphs=old_paragraphs,
|
|
651
|
+
new_paragraphs=new_paragraphs,
|
|
652
|
+
)
|
|
653
|
+
document_plan = build_hwpx_comparison_table_plan(
|
|
654
|
+
old_source,
|
|
655
|
+
new_source,
|
|
656
|
+
title=title,
|
|
657
|
+
include_equal=include_equal,
|
|
658
|
+
)
|
|
659
|
+
validation = validate_hwpx_document_plan(document_plan)
|
|
660
|
+
if not validation.ok:
|
|
661
|
+
return {
|
|
662
|
+
"filename": filename,
|
|
663
|
+
"created": False,
|
|
664
|
+
"error": "comparison table plan failed validation",
|
|
665
|
+
"plan_validation": validation.to_dict(),
|
|
666
|
+
}
|
|
667
|
+
path = resolve_path(filename)
|
|
668
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
669
|
+
doc = build_document_from_plan(document_plan, preset="government_report")
|
|
670
|
+
try:
|
|
671
|
+
verification = _save_generated_document(doc, path)
|
|
672
|
+
finally:
|
|
673
|
+
doc.close()
|
|
674
|
+
result = {
|
|
675
|
+
"filename": filename,
|
|
676
|
+
"created": True,
|
|
677
|
+
"document_plan": document_plan,
|
|
678
|
+
"plan_validation": validation.to_dict(),
|
|
679
|
+
"verification": verification,
|
|
680
|
+
"openSafety": verification.get("openSafety"),
|
|
681
|
+
}
|
|
682
|
+
return _apply_write_verbosity(result, verbosity)
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def create_proposal_document(
|
|
686
|
+
filename: str,
|
|
687
|
+
proposal_spec: dict,
|
|
688
|
+
style_preset: str = "clean_korean_proposal",
|
|
689
|
+
verbosity: str = "compact",
|
|
690
|
+
) -> dict:
|
|
691
|
+
"""자연어에서 추출한 proposal_spec으로 제안서형 HWPX 문서를 생성합니다."""
|
|
692
|
+
path = resolve_path(filename)
|
|
693
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
694
|
+
doc = (
|
|
695
|
+
build_proposal_document(proposal_spec or {}, preset=style_preset)
|
|
696
|
+
if build_proposal_document is not None
|
|
697
|
+
else create_quality_document_fallback(proposal_spec or {})
|
|
698
|
+
)
|
|
699
|
+
try:
|
|
700
|
+
verification = _save_generated_document(doc, path)
|
|
701
|
+
finally:
|
|
702
|
+
doc.close()
|
|
703
|
+
|
|
704
|
+
report = (
|
|
705
|
+
inspect_proposal_document_quality(path)
|
|
706
|
+
if inspect_proposal_document_quality is not None
|
|
707
|
+
else _proposal_quality_fallback(path)
|
|
708
|
+
)
|
|
709
|
+
result = {
|
|
710
|
+
"filename": filename,
|
|
711
|
+
"created": True,
|
|
712
|
+
"style_preset": style_preset,
|
|
713
|
+
"quality": report,
|
|
714
|
+
"verification": verification,
|
|
715
|
+
}
|
|
716
|
+
return _apply_write_verbosity(result, verbosity)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def copy_document(source_filename: str, destination_filename: str = None) -> dict:
|
|
720
|
+
"""HWPX 문서를 새 경로로 복사합니다. 원본은 유지됩니다."""
|
|
721
|
+
source = resolve_path(source_filename)
|
|
722
|
+
destination = destination_filename
|
|
723
|
+
if destination_filename is not None:
|
|
724
|
+
destination = resolve_path(destination_filename)
|
|
725
|
+
dest = copy_document_file(source, destination)
|
|
726
|
+
open_safety = build_hwpx_open_safety_report(Path(dest))
|
|
727
|
+
return {
|
|
728
|
+
"source": source_filename,
|
|
729
|
+
"destination": os.path.basename(dest),
|
|
730
|
+
"openSafety": open_safety,
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
__all__ = [
|
|
735
|
+
"create_document",
|
|
736
|
+
"create_document_from_plan",
|
|
737
|
+
"copy_document",
|
|
738
|
+
"create_government_report_document",
|
|
739
|
+
"create_proposal_document",
|
|
740
|
+
"create_comparison_table_document",
|
|
741
|
+
"get_document_plan_schema",
|
|
742
|
+
"validate_document_plan",
|
|
743
|
+
"analyze_document_plan",
|
|
744
|
+
"markdown_to_document_plan",
|
|
745
|
+
"parse_government_report_text",
|
|
746
|
+
"compute_report_value",
|
|
747
|
+
"register_template",
|
|
748
|
+
"list_templates",
|
|
749
|
+
"describe_template",
|
|
750
|
+
]
|