moleg-api 0.2.3__tar.gz → 0.3.0__tar.gz
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.
- {moleg_api-0.2.3 → moleg_api-0.3.0}/PKG-INFO +14 -10
- {moleg_api-0.2.3 → moleg_api-0.3.0}/README.md +13 -9
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/__init__.py +14 -0
- moleg_api-0.3.0/moleg_api/_cli/brief_mode.py +53 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/catalog.py +12 -5
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/constants.py +7 -2
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/dispatch.py +26 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/foundation.py +0 -7
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/main.py +28 -3
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/parser.py +45 -7
- moleg_api-0.3.0/moleg_api/_cli/payload_size.py +57 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/signals.py +83 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/signals_meta.py +22 -0
- moleg_api-0.3.0/moleg_api/_laws/adjudication_registry.py +76 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api.py +6 -0
- moleg_api-0.3.0/moleg_api/_laws/api_adjudications.py +117 -0
- moleg_api-0.3.0/moleg_api/_laws/api_law_toc.py +66 -0
- moleg_api-0.3.0/moleg_api/_laws/api_revision_reason.py +120 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/foundation.py +10 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/__init__.py +2 -1
- moleg_api-0.3.0/moleg_api/_models/adjudications.py +75 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/laws.py +59 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/serialization.py +11 -1
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/__init__.py +1 -0
- moleg_api-0.3.0/moleg_api/_normalization/adjudications.py +164 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/article_units.py +20 -4
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/unwrap.py +29 -1
- moleg_api-0.3.0/moleg_api/_version.py +18 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/models.py +6 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api.egg-info/PKG-INFO +14 -10
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api.egg-info/SOURCES.txt +15 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/pyproject.toml +4 -1
- moleg_api-0.3.0/tests/test_adjudications_0_3_0.py +278 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_cli.py +40 -0
- moleg_api-0.3.0/tests/test_command_smoke_0_3_0.py +195 -0
- moleg_api-0.3.0/tests/test_context_efficiency_0_3_0.py +220 -0
- moleg_api-0.3.0/tests/test_exit_classification_0_3_0.py +116 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_refactor_compat.py +4 -1
- moleg_api-0.3.0/tests/test_revision_reason_0_3_0.py +156 -0
- moleg_api-0.3.0/tests/test_version_signal_0_3_0.py +100 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/LICENSE +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/__main__.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/__init__.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/data.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_cli/signal_helpers.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/__init__.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/admin_scope.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/annex_tables.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_admin_rules.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_annex.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_article_context.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_authority_context.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_bundle.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_comparable.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_compare.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_delegated_criteria.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_followups.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_history.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_institutional.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_interpretations.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_judicial.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_law_loaders.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_query_expansion.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_search.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_structure.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/api_versions.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/article_gaps.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/authority_article_gaps.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/authority_context_details.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/authority_context_pipeline.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/authority_sources.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/authority_temporal_filters.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/authority_temporal_gaps.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/bridge.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/bundle_candidates.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/bundle_eager.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/bundle_finalize.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/bundle_modes.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/bundle_primary.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/bundle_state.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/candidates.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/config.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/context_load_gaps.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/delegated_criteria_details.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/delegated_criteria_pipeline.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/delegated_scope.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/followup_basic.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/followup_hits.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/followup_identities.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/followup_routing.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/followup_routing_authority.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/followup_routing_bundle.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/followup_searches.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/history_identity.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/identity_params.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/institutional_candidates.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/institutional_pipeline.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/institutional_resolution.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/limits_intents.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/ranking.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/requested_load_gaps.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/source_matching.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/support.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/temporal_gaps.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_laws/validation.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/admin.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/annex.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/authority.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/bundles.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/followups.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/query.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_models/types.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/annex.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/articles.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/authority.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/candidates.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/delegation.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/history_events.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/history_html.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/identities.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/primitives.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/references.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/row_format.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/source_refs.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/_normalization/structure.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/cli.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/errors.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/laws.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/normalization.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/py.typed +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api/source.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api.egg-info/dependency_links.txt +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api.egg-info/entry_points.txt +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api.egg-info/requires.txt +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/moleg_api.egg-info/top_level.txt +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/setup.cfg +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_laws.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_live_smoke.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_models.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_sdk_fixes_0_2_1.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_sdk_fixes_0_2_2.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_source.py +0 -0
- {moleg_api-0.2.3 → moleg_api-0.3.0}/tests/test_versions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moleg-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Task-level Python interface for loading Korean legal sources from law.go.kr
|
|
5
5
|
Author: MOLEG-API maintainers
|
|
6
6
|
License-Expression: MIT
|
|
@@ -41,7 +41,7 @@ The PyPI package is `moleg-api`; import it as `moleg_api`.
|
|
|
41
41
|
pip install moleg-api
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Live calls use a law.go.kr OpenAPI credential (the "OC" — a free, non-secret account id). The package ships a shared default, so calls work out of the box with **no registration required**. To use your own OC (recommended for heavy use), register at law.go.kr and set `MOLEG_OC`, or pass `oc=` to `LawGoKrClient`. See [Installation](wiki/Installation.md).
|
|
44
|
+
Live calls use a law.go.kr OpenAPI credential (the "OC" — a free, non-secret account id). The package ships a shared default, so calls work out of the box with **no registration required**. To use your own OC (recommended for heavy use), register at law.go.kr and set `MOLEG_OC`, or pass `oc=` to `LawGoKrClient`. See [Installation](docs/wiki/Installation.md).
|
|
45
45
|
|
|
46
46
|
## Quickstart
|
|
47
47
|
|
|
@@ -65,7 +65,7 @@ python -m moleg_api get-article --law 001248 제3조 # load the current artic
|
|
|
65
65
|
python -m moleg_api get-article --law 001248 --as-of 2021-01-01 제3조 # the version in force then
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
The CLI exposes
|
|
68
|
+
The CLI exposes every interface as a subcommand plus `catalog`; `catalog` is the source of truth for the command list, routing rules, and result `kind` values. Each envelope carries the `version` that produced it.
|
|
69
69
|
|
|
70
70
|
Public dataclasses serialize recursively with `to_dict()` / `to_json_string()` (raw payloads omitted unless `include_raw=True`).
|
|
71
71
|
|
|
@@ -77,19 +77,23 @@ Public dataclasses serialize recursively with `to_dict()` / `to_json_string()` (
|
|
|
77
77
|
- Delegated rules, legal hierarchy, and administrative rules (고시·훈령·예규)
|
|
78
78
|
- Law and administrative-rule annex/form bodies (별표·서식)
|
|
79
79
|
- MOLEG and ministry legal interpretations, Supreme Court cases, and Constitutional Court (헌재) decisions
|
|
80
|
+
- Amendment rationale — the 「개정이유 및 주요내용」 text for a specific statute version
|
|
81
|
+
- Committee decisions from twelve regulators (개인정보보호위·공정위·금융위·인권위·노동위 등) and 행정심판 재결례 including four special tribunals — the record of an agency applying the law it administers, kept distinct from precedent
|
|
80
82
|
- Query expansion, comparable-mechanism discovery, and staged context bundles with executable follow-up lookups
|
|
81
83
|
|
|
82
|
-
|
|
84
|
+
Context budgeting: `get-law --toc` returns a statute's article map without the text (276KB → 19KB on 개인정보 보호법), `--brief` returns a decision's 요지 without the full body, and `flags.large_payload` warns when a narrowing option was available.
|
|
85
|
+
|
|
86
|
+
Out of scope: legal advice, National Assembly bill data (status, votes, minutes), legislative pre-announcement (입법예고 — a separate 국민참여입법센터 source), and latest statistics/news.
|
|
83
87
|
|
|
84
88
|
## Documentation
|
|
85
89
|
|
|
86
|
-
Full documentation is in the [`wiki/`](wiki/Home.md) folder:
|
|
90
|
+
Full documentation is in the [`docs/wiki/`](docs/wiki/Home.md) folder:
|
|
87
91
|
|
|
88
|
-
- [Installation](wiki/Installation.md) · [Quickstart](wiki/Quickstart.md) · [Core Concepts](wiki/Core-Concepts.md)
|
|
89
|
-
- [CLI Reference](wiki/CLI-Reference.md) · [API Reference](wiki/API-Reference.md)
|
|
90
|
-
- [Historical Versions](wiki/Historical-Versions.md) · [Sources & Coverage](wiki/Sources-and-Coverage.md)
|
|
91
|
-
- [Gotchas](wiki/Gotchas.md) · [Error Handling](wiki/Error-Handling.md)
|
|
92
|
-
- [Maintainer Notes](wiki/Maintainer-Notes.md)
|
|
92
|
+
- [Installation](docs/wiki/Installation.md) · [Quickstart](docs/wiki/Quickstart.md) · [Core Concepts](docs/wiki/Core-Concepts.md)
|
|
93
|
+
- [CLI Reference](docs/wiki/CLI-Reference.md) · [API Reference](docs/wiki/API-Reference.md)
|
|
94
|
+
- [Historical Versions](docs/wiki/Historical-Versions.md) · [Sources & Coverage](docs/wiki/Sources-and-Coverage.md)
|
|
95
|
+
- [Gotchas](docs/wiki/Gotchas.md) · [Error Handling](docs/wiki/Error-Handling.md)
|
|
96
|
+
- [Maintainer Notes](docs/wiki/Maintainer-Notes.md)
|
|
93
97
|
|
|
94
98
|
## Status
|
|
95
99
|
|
|
@@ -12,7 +12,7 @@ The PyPI package is `moleg-api`; import it as `moleg_api`.
|
|
|
12
12
|
pip install moleg-api
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Live calls use a law.go.kr OpenAPI credential (the "OC" — a free, non-secret account id). The package ships a shared default, so calls work out of the box with **no registration required**. To use your own OC (recommended for heavy use), register at law.go.kr and set `MOLEG_OC`, or pass `oc=` to `LawGoKrClient`. See [Installation](wiki/Installation.md).
|
|
15
|
+
Live calls use a law.go.kr OpenAPI credential (the "OC" — a free, non-secret account id). The package ships a shared default, so calls work out of the box with **no registration required**. To use your own OC (recommended for heavy use), register at law.go.kr and set `MOLEG_OC`, or pass `oc=` to `LawGoKrClient`. See [Installation](docs/wiki/Installation.md).
|
|
16
16
|
|
|
17
17
|
## Quickstart
|
|
18
18
|
|
|
@@ -36,7 +36,7 @@ python -m moleg_api get-article --law 001248 제3조 # load the current artic
|
|
|
36
36
|
python -m moleg_api get-article --law 001248 --as-of 2021-01-01 제3조 # the version in force then
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
The CLI exposes
|
|
39
|
+
The CLI exposes every interface as a subcommand plus `catalog`; `catalog` is the source of truth for the command list, routing rules, and result `kind` values. Each envelope carries the `version` that produced it.
|
|
40
40
|
|
|
41
41
|
Public dataclasses serialize recursively with `to_dict()` / `to_json_string()` (raw payloads omitted unless `include_raw=True`).
|
|
42
42
|
|
|
@@ -48,19 +48,23 @@ Public dataclasses serialize recursively with `to_dict()` / `to_json_string()` (
|
|
|
48
48
|
- Delegated rules, legal hierarchy, and administrative rules (고시·훈령·예규)
|
|
49
49
|
- Law and administrative-rule annex/form bodies (별표·서식)
|
|
50
50
|
- MOLEG and ministry legal interpretations, Supreme Court cases, and Constitutional Court (헌재) decisions
|
|
51
|
+
- Amendment rationale — the 「개정이유 및 주요내용」 text for a specific statute version
|
|
52
|
+
- Committee decisions from twelve regulators (개인정보보호위·공정위·금융위·인권위·노동위 등) and 행정심판 재결례 including four special tribunals — the record of an agency applying the law it administers, kept distinct from precedent
|
|
51
53
|
- Query expansion, comparable-mechanism discovery, and staged context bundles with executable follow-up lookups
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
Context budgeting: `get-law --toc` returns a statute's article map without the text (276KB → 19KB on 개인정보 보호법), `--brief` returns a decision's 요지 without the full body, and `flags.large_payload` warns when a narrowing option was available.
|
|
56
|
+
|
|
57
|
+
Out of scope: legal advice, National Assembly bill data (status, votes, minutes), legislative pre-announcement (입법예고 — a separate 국민참여입법센터 source), and latest statistics/news.
|
|
54
58
|
|
|
55
59
|
## Documentation
|
|
56
60
|
|
|
57
|
-
Full documentation is in the [`wiki/`](wiki/Home.md) folder:
|
|
61
|
+
Full documentation is in the [`docs/wiki/`](docs/wiki/Home.md) folder:
|
|
58
62
|
|
|
59
|
-
- [Installation](wiki/Installation.md) · [Quickstart](wiki/Quickstart.md) · [Core Concepts](wiki/Core-Concepts.md)
|
|
60
|
-
- [CLI Reference](wiki/CLI-Reference.md) · [API Reference](wiki/API-Reference.md)
|
|
61
|
-
- [Historical Versions](wiki/Historical-Versions.md) · [Sources & Coverage](wiki/Sources-and-Coverage.md)
|
|
62
|
-
- [Gotchas](wiki/Gotchas.md) · [Error Handling](wiki/Error-Handling.md)
|
|
63
|
-
- [Maintainer Notes](wiki/Maintainer-Notes.md)
|
|
63
|
+
- [Installation](docs/wiki/Installation.md) · [Quickstart](docs/wiki/Quickstart.md) · [Core Concepts](docs/wiki/Core-Concepts.md)
|
|
64
|
+
- [CLI Reference](docs/wiki/CLI-Reference.md) · [API Reference](docs/wiki/API-Reference.md)
|
|
65
|
+
- [Historical Versions](docs/wiki/Historical-Versions.md) · [Sources & Coverage](docs/wiki/Sources-and-Coverage.md)
|
|
66
|
+
- [Gotchas](docs/wiki/Gotchas.md) · [Error Handling](docs/wiki/Error-Handling.md)
|
|
67
|
+
- [Maintainer Notes](docs/wiki/Maintainer-Notes.md)
|
|
64
68
|
|
|
65
69
|
## Status
|
|
66
70
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Public MOLEG-API interface."""
|
|
2
2
|
|
|
3
|
+
from ._version import __version__
|
|
3
4
|
from .errors import (
|
|
4
5
|
AmbiguousLawError,
|
|
5
6
|
AsOfBeforeCoverageError,
|
|
@@ -13,6 +14,9 @@ from .errors import (
|
|
|
13
14
|
)
|
|
14
15
|
from .laws import MolegApi
|
|
15
16
|
from .models import (
|
|
17
|
+
AdjudicationHit,
|
|
18
|
+
AdjudicationIdentity,
|
|
19
|
+
AdjudicationText,
|
|
16
20
|
AdministrativeRuleArticleText,
|
|
17
21
|
AdministrativeRuleContext,
|
|
18
22
|
AdministrativeRuleHit,
|
|
@@ -63,6 +67,9 @@ from .models import (
|
|
|
63
67
|
LawStructure,
|
|
64
68
|
LawStructureNode,
|
|
65
69
|
LawText,
|
|
70
|
+
LawToc,
|
|
71
|
+
LawTocEntry,
|
|
72
|
+
RevisionReason,
|
|
66
73
|
SupplementaryProvision,
|
|
67
74
|
StructuredTableData,
|
|
68
75
|
)
|
|
@@ -120,7 +127,13 @@ __all__ = [
|
|
|
120
127
|
"LawIdentity",
|
|
121
128
|
"LawStructure",
|
|
122
129
|
"LawStructureNode",
|
|
130
|
+
"AdjudicationHit",
|
|
131
|
+
"AdjudicationIdentity",
|
|
132
|
+
"AdjudicationText",
|
|
123
133
|
"LawText",
|
|
134
|
+
"LawToc",
|
|
135
|
+
"LawTocEntry",
|
|
136
|
+
"RevisionReason",
|
|
124
137
|
"MolegApi",
|
|
125
138
|
"MolegApiError",
|
|
126
139
|
"NoResultError",
|
|
@@ -132,4 +145,5 @@ __all__ = [
|
|
|
132
145
|
"SupplementaryProvision",
|
|
133
146
|
"StructuredTableData",
|
|
134
147
|
"UnsupportedFormatError",
|
|
148
|
+
"__version__",
|
|
135
149
|
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import MISSING, fields, replace
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
# A decision detail ships the same reasoning twice over: `text` and `full_text`
|
|
7
|
+
# are different sections of the document, not duplicates, but between them they
|
|
8
|
+
# are ~82% of the payload (29,321 of 35,866 characters on decision 193332). The
|
|
9
|
+
# structured extract — 판시사항, 결정요지, referenced authority — is what a reader
|
|
10
|
+
# needs to decide whether the decision is even on point. Loading 82,700 bytes to
|
|
11
|
+
# find out costs more than the answer is worth.
|
|
12
|
+
#
|
|
13
|
+
# Brief mode drops the full-body fields and keeps the extract. It does *not* drop
|
|
14
|
+
# `summary`: 요지 is the entire reason to ask for a brief, and cutting it would
|
|
15
|
+
# shrink the payload further while removing the only thing brief mode is for.
|
|
16
|
+
_BRIEF_DROPPED_FIELDS = ("text", "full_text", "reasoning")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def to_brief(result: Any) -> Any:
|
|
20
|
+
"""Blank the full-body fields on a loaded authority document.
|
|
21
|
+
|
|
22
|
+
Returns the same type rather than a narrower one, so the model, the kind, and
|
|
23
|
+
every consumer's field access stay valid — the difference is signalled on the
|
|
24
|
+
envelope, not encoded in a second shape of the same thing.
|
|
25
|
+
"""
|
|
26
|
+
# Reset each field to its declared default rather than to a blanket "" or
|
|
27
|
+
# None. `text: str = ""` and `reasoning: str | None = None` mean different
|
|
28
|
+
# things by absence, and blanking a nullable field to "" turns "not loaded"
|
|
29
|
+
# into "loaded and empty" — a distinction a caller reads as a fact about the
|
|
30
|
+
# document rather than about the request.
|
|
31
|
+
defaults = {f.name: f.default for f in fields(result)}
|
|
32
|
+
blanked = {
|
|
33
|
+
name: defaults[name]
|
|
34
|
+
for name in _BRIEF_DROPPED_FIELDS
|
|
35
|
+
if name in defaults and defaults[name] is not MISSING
|
|
36
|
+
}
|
|
37
|
+
return replace(result, **blanked) if blanked else result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def brief_dropped(result: Any) -> list[str]:
|
|
41
|
+
"""Which full-body fields this document actually carried, for the flag.
|
|
42
|
+
|
|
43
|
+
Reported rather than assumed: a decision with no `full_text` on file must not
|
|
44
|
+
claim brief mode withheld one, or the caller will go looking for a section
|
|
45
|
+
that never existed.
|
|
46
|
+
"""
|
|
47
|
+
return [
|
|
48
|
+
name
|
|
49
|
+
for name in _BRIEF_DROPPED_FIELDS
|
|
50
|
+
if getattr(result, name, None)
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
__all__ = [name for name in globals() if not name.startswith("__")]
|
|
@@ -6,20 +6,23 @@ CATALOG = {
|
|
|
6
6
|
"convention": [
|
|
7
7
|
"kind 접미사가 후보≠본문을 구조로 표시: *_hit/*_candidate/*_planning = 검색 후보(인용 불가), *_text/*_context/*_identity = 로드된 본문.",
|
|
8
8
|
"인용은 항상 로드된 본문에서만 — search/expand/find-comparable 결과는 '다음에 무엇을 로드할지의 메뉴'일 뿐.",
|
|
9
|
-
"출처 권위 보존: 법제처 법령 > 법제처 해석 > 부처 해석 ≠ 판례 ≠
|
|
10
|
-
"0건·호출 실패 ≠ 부재. 종료코드로 구분: 0 ok(0건 포함) · 2 모호 · 3
|
|
9
|
+
"출처 권위 보존: 법제처 법령 > 법제처 해석 > 부처 해석 ≠ 판례 ≠ 헌재 ≠ 위원회 결정 ≠ 행정심판 재결. flags.source_type/source_authority를 답에 반영하고 평탄화 금지 — 위원회 결정·행정심판 재결은 행정기관의 판단이라 행정소송으로 뒤집힐 수 있고 판례가 아니다.",
|
|
10
|
+
"0건·호출 실패 ≠ 부재. 종료코드로 구분: 0 ok(0건 포함) · 2 모호 · 3 소스 접근·응답 문제 · 4 no-result · 5 usage/순서위반.",
|
|
11
|
+
"exit 3의 두 얼굴을 kind로 갈라라 — source_access_error=진짜 일시 장애(타임아웃·429·5xx)라 재시도가 맞고, parse_error=인식 불가 응답이라 재시도해도 그대로다(식별자 오류부터 의심). 불량 식별자로 본문이 없으면 exit 3이 아니라 no_result(exit 4)로 온다 — '일시 장애'로 읽지 마라.",
|
|
11
12
|
"load 계열에 법령명을 주면 needs_search_first(exit 5) — 먼저 search-laws로 law_id를 얻어라.",
|
|
12
13
|
"deferred는 data.deferred[i]를 load-followup --json -로 파이프(손타이핑 금지).",
|
|
14
|
+
"컨텍스트 예산: 큰 법 전체 로드(get-law) 전에 --toc로 조문 지도부터, 판결·헌재·해석은 --brief로 요지부터. flags.large_payload가 뜨면 범위를 좁힐 인자가 있다는 신호다(검색은 후보 반환이 설계라 예외).",
|
|
13
15
|
"로더(get-law/get-article)는 기본이 현행 통합본, --as-of <날짜>(YYYY-MM-DD/YYYYMMDD만)면 그 시점 시행 버전을 로드한다. 반환 effective_date를 오늘과 대조해 현행/미시행을 판정하라 — flags.not_effective_as_of=공포됐으나 미시행(미래 시행일, 현행 인용 금지), version_mismatch={requested,loaded}=요청 시점과 반환 버전 시행일 불일치, version_request_unfulfilled=요청보다 이후 버전이 반환됨. 요청 시점이 통합본 커버리지보다 이르면 kind:version_request_unfulfilled+earliest_available(→trace-law-history).",
|
|
14
16
|
"검색 스코프 뉘앙스: 헌재·판례는 기본이 제목(사건명) 검색이라 doctrine·본문 키워드는 --search-body(본문 전체)라야 매칭(0건이면 next가 재시도 제시). 별표는 --search-scope title(별표 제목)/source(소관 법령명, 토큰 근접)이라 0건이면 반대 스코프로. 부처 해석은 --source all_ministries(법제처+부처 혼합)로만 나오고, 부처 본문 로드는 --source ministry --ministry <기관> 필수(검색 hit의 follow_up.filters를 그대로 로더 인자로).",
|
|
15
17
|
"개정 전후 델타=compare-law-versions는 소스가 주는 최근 두 버전만 비교하고 changes[].article은 본문에서 파싱한 실제 조문번호(매핑 불가 시 null). 임의 두 시점 델타는 get-article --as-of를 전후 날짜로 두 번 로드해 대조.",
|
|
16
18
|
],
|
|
17
19
|
"routing_rules": [
|
|
18
20
|
"본문 로드: 살아있는 조문=get-article / 이동·삭제 가능성 있는 조문=load-article-context(기본이 이동추적).",
|
|
19
|
-
"
|
|
21
|
+
"개정 3분해: 왜 바뀌었나(제안 취지 원문)=get-revision-reason / 무엇이 바뀌었나(전후 문구 델타)=compare-law-versions / 어떤 개정들이 있었나(연혁)=trace-law-history. 연혁 이벤트의 identity.mst가 곧 get-revision-reason --mst 입력이다.",
|
|
20
22
|
"이 법 아래 무엇이 있나: 계층 조망=get-law-structure(위임 증명 아님) / 조문 단위 위임 규정=find-delegated-rules.",
|
|
21
23
|
"넓은 탐색: 넓은 질의의 용어·관련법 조사계획=expand-legal-query / 유사 제도(비슷한 기제)를 가진 법 후보(설계용)=find-comparable-mechanisms.",
|
|
22
24
|
"묶음 로더 — authority=특정 조문의 해석/판례/헌재 권위 / bundle=진입점 모를 때 단일법·넓은 질문(--mode) / institutional=명시된 다법령 집합(--statute 반복) / delegated=단일법의 하위규칙·별표 집행기준 본문.",
|
|
25
|
+
"감독기관이 실제로 판단했나(견제): 규제기관의 처분·의결서=search-committee-decisions --committee <ppc|ftc|fsc|sfc|kcc|nhrck|acr|nlrc|eiac|iaciac|oclt|ecc> / 그 처분에 불복한 재결=search-administrative-appeals --tribunal <decc|acr|adap|tt|kmst>. 둘 다 0건이어도 부재의 증명이 아니다(비공개·미접수 가능).",
|
|
23
26
|
"별표 금액·기준표: 위임된 시행령·시행규칙의 별표를 search-annex-forms --search-scope source <법령명> → get-annex-form-body --id(=--annex-id)로 로드. 표 파싱이 무너지면 structured_data.parsing_confidence=low — 금액은 text를 1차로 인용하라. bare id 로드는 소관법령ID·pdf_link 등 링크 메타를 복구하지 못하니 현행성은 모법 버전으로 확인.",
|
|
24
27
|
],
|
|
25
28
|
"commands": {
|
|
@@ -27,21 +30,25 @@ CATALOG = {
|
|
|
27
30
|
"search-laws", "resolve-promulgated-law", "search-administrative-rules", "search-annex-forms",
|
|
28
31
|
"search-interpretations", "search-cases", "search-constitutional-decisions",
|
|
29
32
|
"expand-legal-query", "find-comparable-mechanisms",
|
|
33
|
+
"search-committee-decisions", "search-administrative-appeals",
|
|
30
34
|
],
|
|
31
35
|
"본문 로드": [
|
|
32
36
|
"get-law", "get-article", "load-article-context", "get-administrative-rule",
|
|
33
37
|
"load-administrative-rule-context", "get-annex-form-body", "get-interpretation",
|
|
34
38
|
"get-case", "get-constitutional-decision",
|
|
39
|
+
"get-committee-decision", "get-administrative-appeal",
|
|
35
40
|
],
|
|
36
|
-
"연혁·체계·위임": ["trace-law-history", "compare-law-versions", "find-delegated-rules", "get-law-structure"],
|
|
41
|
+
"연혁·체계·위임": ["trace-law-history", "get-revision-reason", "compare-law-versions", "find-delegated-rules", "get-law-structure"],
|
|
37
42
|
"권위·묶음": ["load-authority-context", "load-legal-context-bundle", "load-institutional-system", "load-delegated-criteria", "load-followup"],
|
|
38
43
|
},
|
|
39
44
|
"kinds": [
|
|
40
45
|
"law_hit_list", "admin_rule_hit_list", "annex_form_hit_list", "interpretation_hit_list",
|
|
41
46
|
"case_hit_list", "constitutional_hit_list", "comparable_planning_list", "query_expansion_planning",
|
|
47
|
+
"committee_decision_hit_list", "administrative_appeal_hit_list",
|
|
48
|
+
"committee_decision_text", "administrative_appeal_text",
|
|
42
49
|
"law_text", "article_text", "article_context", "admin_rule_text", "admin_rule_context",
|
|
43
50
|
"annex_form_text", "interpretation_text", "case_text", "constitutional_text", "law_identity",
|
|
44
|
-
"law_history", "law_diff", "delegation_graph", "law_structure_hierarchy_only",
|
|
51
|
+
"law_history", "revision_reason_text", "law_toc_map", "law_diff", "delegation_graph", "law_structure_hierarchy_only",
|
|
45
52
|
"legal_context_bundle", "authority_context",
|
|
46
53
|
"ambiguous", "source_access_error", "parse_error", "no_result",
|
|
47
54
|
"needs_search_first", "usage_error", "unsupported", "error",
|
|
@@ -4,8 +4,11 @@ from .foundation import *
|
|
|
4
4
|
|
|
5
5
|
EXIT_OK = 0 # includes a zero-hit search (ok:true, count:0)
|
|
6
6
|
EXIT_AMBIGUOUS = 2 # multiple plausible identities — surface, don't pick
|
|
7
|
-
EXIT_SOURCE = 3 #
|
|
8
|
-
|
|
7
|
+
EXIT_SOURCE = 3 # source could not be read OR could not be parsed — kind splits it:
|
|
8
|
+
# source_access_error = transient (rate limit / retry / 5xx), retry is right
|
|
9
|
+
# parse_error = unrecognized response shape, retry won't help
|
|
10
|
+
EXIT_NO_RESULT = 4 # a well-formed lookup found no source text — includes a bad identifier,
|
|
11
|
+
# which law.go.kr signals with an empty detail body (see unwrap_service_payload)
|
|
9
12
|
EXIT_USAGE = 5 # bad arguments, or a loader was handed a law name (search first)
|
|
10
13
|
|
|
11
14
|
# Public MolegApi methods reachable through load-followup rehydration. Guards the
|
|
@@ -54,6 +57,8 @@ SEARCH_COMMANDS = frozenset(
|
|
|
54
57
|
"search-interpretations",
|
|
55
58
|
"search-cases",
|
|
56
59
|
"search-constitutional-decisions",
|
|
60
|
+
"search-committee-decisions",
|
|
61
|
+
"search-administrative-appeals",
|
|
57
62
|
}
|
|
58
63
|
)
|
|
59
64
|
|
|
@@ -2,9 +2,23 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from .foundation import *
|
|
4
4
|
from .constants import CliError, EXIT_USAGE, FOLLOWUP_HANDOFFS, FOLLOWUP_INTERFACES
|
|
5
|
+
from .data import _statute_args
|
|
6
|
+
from .brief_mode import brief_dropped, to_brief
|
|
5
7
|
from .signals_meta import parse_as_of
|
|
6
8
|
|
|
7
9
|
def _call(api: MolegApi, args: argparse.Namespace) -> Any:
|
|
10
|
+
result = _dispatch(api, args)
|
|
11
|
+
if getattr(args, "brief", False):
|
|
12
|
+
# Record what was actually withheld *before* blanking it. Signals only
|
|
13
|
+
# sees the trimmed result, and "empty because brief" must stay
|
|
14
|
+
# distinguishable from "empty because the source had none" — otherwise a
|
|
15
|
+
# caller goes hunting for a section that never existed.
|
|
16
|
+
args.brief_dropped = brief_dropped(result)
|
|
17
|
+
return to_brief(result)
|
|
18
|
+
return result
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _dispatch(api: MolegApi, args: argparse.Namespace) -> Any:
|
|
8
22
|
c = args.command
|
|
9
23
|
# Strictly validate --as-of once, for every command that carries it, so a
|
|
10
24
|
# malformed date is a usage error rather than a silent wrong-version load.
|
|
@@ -30,11 +44,21 @@ def _call(api: MolegApi, args: argparse.Namespace) -> Any:
|
|
|
30
44
|
if c == "search-constitutional-decisions":
|
|
31
45
|
return api.search_constitutional_decisions(args.query, search_body=args.search_body,
|
|
32
46
|
decided_on=args.decided_on, case_number=args.case_number, display=args.display)
|
|
47
|
+
if c == "search-committee-decisions":
|
|
48
|
+
return api.search_committee_decisions(args.query, committee=args.committee, display=args.display)
|
|
49
|
+
if c == "get-committee-decision":
|
|
50
|
+
return api.get_committee_decision(args.identifier, committee=args.committee)
|
|
51
|
+
if c == "search-administrative-appeals":
|
|
52
|
+
return api.search_administrative_appeals(args.query, tribunal=args.tribunal, display=args.display)
|
|
53
|
+
if c == "get-administrative-appeal":
|
|
54
|
+
return api.get_administrative_appeal(args.identifier, tribunal=args.tribunal)
|
|
33
55
|
if c == "expand-legal-query":
|
|
34
56
|
return api.expand_legal_query(args.query, display=args.display, include_websearch_hint=not args.no_websearch_hint)
|
|
35
57
|
if c == "find-comparable-mechanisms":
|
|
36
58
|
return api.find_comparable_mechanisms(args.concept, display=args.display)
|
|
37
59
|
if c == "get-law":
|
|
60
|
+
if args.toc:
|
|
61
|
+
return api.get_law_toc(args.law, as_of=args.as_of, basis=args.basis)
|
|
38
62
|
return api.get_law(args.law, as_of=args.as_of, basis=args.basis,
|
|
39
63
|
articles=args.article or None, include_metadata=not args.no_metadata)
|
|
40
64
|
if c == "get-article":
|
|
@@ -59,6 +83,8 @@ def _call(api: MolegApi, args: argparse.Namespace) -> Any:
|
|
|
59
83
|
if c == "trace-law-history":
|
|
60
84
|
date_range = (args.date_from, args.date_to) if (args.date_from and args.date_to) else None
|
|
61
85
|
return api.trace_law_history(args.law, date_range=date_range, article=args.article)
|
|
86
|
+
if c == "get-revision-reason":
|
|
87
|
+
return api.get_revision_reason(args.law, mst=args.mst, as_of=args.as_of)
|
|
62
88
|
if c == "compare-law-versions":
|
|
63
89
|
return api.compare_law_versions(args.law, article=args.article)
|
|
64
90
|
if c == "find-delegated-rules":
|
|
@@ -4,7 +4,6 @@ import argparse
|
|
|
4
4
|
import json
|
|
5
5
|
import sys
|
|
6
6
|
from datetime import date, datetime
|
|
7
|
-
from importlib.metadata import PackageNotFoundError, version as _dist_version
|
|
8
7
|
from typing import Any
|
|
9
8
|
|
|
10
9
|
from ..errors import (
|
|
@@ -21,10 +20,4 @@ from ..laws import MolegApi
|
|
|
21
20
|
from ..models import DeferredLookup
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
def _pkg_version() -> str:
|
|
25
|
-
try:
|
|
26
|
-
return _dist_version("moleg-api")
|
|
27
|
-
except PackageNotFoundError:
|
|
28
|
-
return "unknown"
|
|
29
|
-
|
|
30
23
|
__all__ = [name for name in globals() if not name.startswith("__")]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from .foundation import *
|
|
4
|
+
from .._version import __version__
|
|
4
5
|
from .catalog import CATALOG
|
|
5
6
|
from .constants import (
|
|
6
7
|
CliError,
|
|
@@ -14,10 +15,22 @@ from .constants import (
|
|
|
14
15
|
from .data import _to_data
|
|
15
16
|
from .dispatch import _call
|
|
16
17
|
from .parser import build_parser
|
|
18
|
+
from .payload_size import large_payload_signals
|
|
17
19
|
from .signals import signals_for
|
|
18
20
|
|
|
19
21
|
def _emit(payload: dict[str, Any]) -> None:
|
|
20
|
-
|
|
22
|
+
# Stamped here rather than at each call site so no envelope — success, error,
|
|
23
|
+
# or catalog — can ship without it. A consumer that cannot tell which version
|
|
24
|
+
# answered cannot tell whether a missing field means "not supported yet" or
|
|
25
|
+
# "the call failed", which is how a stale install reads as a data gap.
|
|
26
|
+
stamped: dict[str, Any] = {}
|
|
27
|
+
for key, value in payload.items():
|
|
28
|
+
stamped[key] = value
|
|
29
|
+
if key == "command":
|
|
30
|
+
stamped["version"] = __version__
|
|
31
|
+
if "version" not in stamped:
|
|
32
|
+
stamped["version"] = __version__
|
|
33
|
+
print(json.dumps(stamped, ensure_ascii=False, indent=2))
|
|
21
34
|
|
|
22
35
|
|
|
23
36
|
def main(argv: list[str] | None = None, *, api: MolegApi | None = None) -> int:
|
|
@@ -66,7 +79,8 @@ def main(argv: list[str] | None = None, *, api: MolegApi | None = None) -> int:
|
|
|
66
79
|
return EXIT_NO_RESULT
|
|
67
80
|
except ParseFailureError as exc:
|
|
68
81
|
_emit({"ok": False, "command": args.command, "kind": "parse_error", "error": str(exc),
|
|
69
|
-
"discipline": ["
|
|
82
|
+
"discipline": ["소스가 인식 불가한 형태의 응답을 줌 — 같은 호출을 재시도해도 대개 그대로다(일시 장애와 다름).",
|
|
83
|
+
"식별자 오류 가능성을 먼저 배제하라(search-*로 신원 재확인). 그래도 같으면 다른 경로/커맨드로 확인하고, 부재로 단정하지 마라."]})
|
|
70
84
|
return EXIT_SOURCE
|
|
71
85
|
except NoResultError as exc:
|
|
72
86
|
msg = str(exc)
|
|
@@ -84,7 +98,8 @@ def main(argv: list[str] | None = None, *, api: MolegApi | None = None) -> int:
|
|
|
84
98
|
return EXIT_USAGE
|
|
85
99
|
else:
|
|
86
100
|
_emit({"ok": False, "command": args.command, "kind": "no_result", "error": msg,
|
|
87
|
-
"discipline": ["이 식별자·조회로 소스 본문 없음 —
|
|
101
|
+
"discipline": ["이 식별자·조회로 소스 본문 없음 — 일시 장애가 아니니 재시도는 무의미하다. 식별자가 틀렸을 수 있으니 search-* 계열로 신원을 재확인하라.",
|
|
102
|
+
"검색어·범위를 밝히고 대체 경로를 시도하기 전 부재로 단정 금지."]})
|
|
88
103
|
return EXIT_NO_RESULT
|
|
89
104
|
except UnsupportedFormatError as exc:
|
|
90
105
|
_emit({"ok": False, "command": args.command, "kind": "unsupported", "error": str(exc),
|
|
@@ -105,6 +120,16 @@ def main(argv: list[str] | None = None, *, api: MolegApi | None = None) -> int:
|
|
|
105
120
|
if "count" in sig:
|
|
106
121
|
envelope["count"] = sig["count"]
|
|
107
122
|
envelope["data"] = _to_data(result, include_raw=include_raw)
|
|
123
|
+
# Measured on the serialized data, after every narrowing option has had its
|
|
124
|
+
# effect — a guess from a row count would fire on 139 short articles and stay
|
|
125
|
+
# quiet on 20 long ones, and would need updating for every command added later.
|
|
126
|
+
size_flags, size_discipline = large_payload_signals(
|
|
127
|
+
args.command,
|
|
128
|
+
len(json.dumps(envelope["data"], ensure_ascii=False)),
|
|
129
|
+
{"articles": len(result.articles)} if isinstance(getattr(result, "articles", None), list) else None,
|
|
130
|
+
)
|
|
131
|
+
sig["flags"].update(size_flags)
|
|
132
|
+
sig["discipline"].extend(size_discipline)
|
|
108
133
|
if sig["flags"]:
|
|
109
134
|
envelope["flags"] = sig["flags"]
|
|
110
135
|
if sig["discipline"]:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from .foundation import *
|
|
4
|
+
from .._version import __version__
|
|
4
5
|
from .signals_meta import parse_as_of
|
|
5
6
|
|
|
6
7
|
def _add_law(p: argparse.ArgumentParser, required: bool = True) -> None:
|
|
@@ -19,6 +20,11 @@ def _add_articles(p: argparse.ArgumentParser, required: bool = False) -> None:
|
|
|
19
20
|
p.add_argument("--article", dest="article", action="append", default=[], required=required, help="조문(예: 제3조). 반복 지정 가능.")
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def _add_brief(p: argparse.ArgumentParser) -> None:
|
|
24
|
+
p.add_argument("--brief", action="store_true",
|
|
25
|
+
help="전문(text/full_text) 없이 요지·판시사항·참조 관계만. 인용은 전문 로드 후.")
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
def _add_budget(p: argparse.ArgumentParser) -> None:
|
|
23
29
|
p.add_argument("--budget", choices=["minimal", "standard", "broad"], default="standard")
|
|
24
30
|
|
|
@@ -32,12 +38,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
32
38
|
parser.add_argument(
|
|
33
39
|
"--version",
|
|
34
40
|
action="version",
|
|
35
|
-
version=f"moleg-api {
|
|
36
|
-
help="
|
|
41
|
+
version=f"moleg-api {__version__}",
|
|
42
|
+
help="실행 중인 moleg-api 버전을 출력하고 종료(엔벨로프의 version과 같은 값).",
|
|
37
43
|
)
|
|
38
44
|
sub = parser.add_subparsers(dest="command", metavar="<command>")
|
|
39
45
|
|
|
40
|
-
sub.add_parser("catalog", help="
|
|
46
|
+
sub.add_parser("catalog", help="전체 서브커맨드·규약·kind 목록을 한 번에.")
|
|
41
47
|
|
|
42
48
|
# ---- searches / planning ------------------------------------------- #
|
|
43
49
|
p = sub.add_parser("search-laws", help="현행/공포 법령 신원 후보 검색.")
|
|
@@ -89,6 +95,28 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
89
95
|
p.add_argument("--case-number", dest="case_number", default=None)
|
|
90
96
|
p.add_argument("--display", type=int, default=20)
|
|
91
97
|
|
|
98
|
+
p = sub.add_parser("search-committee-decisions", help="위원회 결정문 검색(개보위·공정위·금융위·인권위 등 12종).")
|
|
99
|
+
p.add_argument("query", nargs="?", default=None, help="안건명·사건명 키워드(생략 시 최신 목록).")
|
|
100
|
+
p.add_argument("--committee", required=True,
|
|
101
|
+
help="기관 코드: ppc(개인정보보호위) ftc(공정위) fsc(금융위) sfc(증선위) kcc(방통위) nhrck(인권위) acr(권익위) nlrc(노동위) eiac(고용보험심사위) iaciac(산재재심사위) oclt(중앙토지수용위) ecc(중앙환경분쟁조정위).")
|
|
102
|
+
p.add_argument("--display", type=int, default=20)
|
|
103
|
+
|
|
104
|
+
p = sub.add_parser("get-committee-decision", help="위원회 결정문 본문 로드(--brief면 요지만).")
|
|
105
|
+
p.add_argument("--id", dest="identifier", required=True, help="decision_id(검색이 준 값).")
|
|
106
|
+
p.add_argument("--committee", required=True, help="검색에 쓴 기관 코드와 동일해야 한다.")
|
|
107
|
+
_add_brief(p)
|
|
108
|
+
|
|
109
|
+
p = sub.add_parser("search-administrative-appeals", help="행정심판 재결례 검색(일반 + 특별심판 4종).")
|
|
110
|
+
p.add_argument("query", nargs="?", default=None, help="사건명 키워드(생략 시 최신 목록).")
|
|
111
|
+
p.add_argument("--tribunal", default="decc",
|
|
112
|
+
help="decc(일반 행정심판위) acr(권익위 특별) adap(소청심사위) tt(조세심판원) kmst(해양안전심판원).")
|
|
113
|
+
p.add_argument("--display", type=int, default=20)
|
|
114
|
+
|
|
115
|
+
p = sub.add_parser("get-administrative-appeal", help="행정심판 재결 본문 로드(--brief면 요지만).")
|
|
116
|
+
p.add_argument("--id", dest="identifier", required=True)
|
|
117
|
+
p.add_argument("--tribunal", default="decc", help="검색에 쓴 심판기관 코드와 동일해야 한다.")
|
|
118
|
+
_add_brief(p)
|
|
119
|
+
|
|
92
120
|
p = sub.add_parser("expand-legal-query", help="질의 확장·관련법/용어/조문 조사 계획.")
|
|
93
121
|
p.add_argument("query"); p.add_argument("--display", type=int, default=5)
|
|
94
122
|
p.add_argument("--no-websearch-hint", dest="no_websearch_hint", action="store_true")
|
|
@@ -97,9 +125,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
97
125
|
p.add_argument("concept"); p.add_argument("--display", type=int, default=5)
|
|
98
126
|
|
|
99
127
|
# ---- loaders -------------------------------------------------------- #
|
|
100
|
-
p = sub.add_parser("get-law", help="법령 본문
|
|
128
|
+
p = sub.add_parser("get-law", help="법령 본문 로드(--toc면 본문 없이 조문 목차만).")
|
|
101
129
|
_add_law(p); _add_as_of(p); _add_basis(p); _add_articles(p)
|
|
102
130
|
p.add_argument("--no-metadata", dest="no_metadata", action="store_true")
|
|
131
|
+
p.add_argument("--toc", action="store_true",
|
|
132
|
+
help="본문 대신 조문 목차(장·절 표제 + 조번호·조제목 + 삭제/이동)만. 큰 법의 지도를 수 KB로.")
|
|
103
133
|
|
|
104
134
|
p = sub.add_parser("get-article", help="조문 하나 로드.")
|
|
105
135
|
_add_law(p); p.add_argument("article"); _add_as_of(p); _add_basis(p)
|
|
@@ -124,19 +154,22 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
124
154
|
p.add_argument("--no-metadata", dest="no_metadata", action="store_true")
|
|
125
155
|
p.add_argument("--no-structuring", dest="no_structuring", action="store_true")
|
|
126
156
|
|
|
127
|
-
p = sub.add_parser("get-interpretation", help="법령해석례 본문
|
|
157
|
+
p = sub.add_parser("get-interpretation", help="법령해석례 본문 로드(--brief면 질의·회답만).")
|
|
128
158
|
p.add_argument("--id", dest="identifier", required=True)
|
|
129
159
|
p.add_argument("--source", choices=["moleg", "ministry", "all", "all_ministries"], default=None)
|
|
130
160
|
p.add_argument("--ministry", default=None)
|
|
131
161
|
p.add_argument("--no-metadata", dest="no_metadata", action="store_true")
|
|
162
|
+
_add_brief(p)
|
|
132
163
|
|
|
133
|
-
p = sub.add_parser("get-case", help="판례 본문
|
|
164
|
+
p = sub.add_parser("get-case", help="판례 본문 로드(--brief면 요지만).")
|
|
134
165
|
p.add_argument("--id", dest="identifier", required=True)
|
|
135
166
|
p.add_argument("--no-metadata", dest="no_metadata", action="store_true")
|
|
167
|
+
_add_brief(p)
|
|
136
168
|
|
|
137
|
-
p = sub.add_parser("get-constitutional-decision", help="헌재 결정 본문
|
|
169
|
+
p = sub.add_parser("get-constitutional-decision", help="헌재 결정 본문 로드(--brief면 요지만).")
|
|
138
170
|
p.add_argument("--id", dest="identifier", required=True)
|
|
139
171
|
p.add_argument("--no-metadata", dest="no_metadata", action="store_true")
|
|
172
|
+
_add_brief(p)
|
|
140
173
|
|
|
141
174
|
# ---- history / structure / delegation ------------------------------ #
|
|
142
175
|
p = sub.add_parser("trace-law-history", help="개정 연혁 이벤트.")
|
|
@@ -144,6 +177,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
144
177
|
p.add_argument("--date-from", dest="date_from", default=None)
|
|
145
178
|
p.add_argument("--date-to", dest="date_to", default=None)
|
|
146
179
|
|
|
180
|
+
p = sub.add_parser("get-revision-reason", help="특정 버전의 「개정이유 및 주요내용」·공포문 원문.")
|
|
181
|
+
_add_law(p)
|
|
182
|
+
p.add_argument("--mst", default=None, help="버전 MST(trace-law-history 이벤트의 identity.mst). 미지정이면 최신 버전.")
|
|
183
|
+
p.add_argument("--as-of", dest="as_of", default=None, help="그 시점 시행 버전의 개정이유(YYYY-MM-DD/YYYYMMDD).")
|
|
184
|
+
|
|
147
185
|
p = sub.add_parser("compare-law-versions", help="개정 전후 조문 문구 비교(소스 제공 전후 쌍).")
|
|
148
186
|
_add_law(p); p.add_argument("--article", default=None)
|
|
149
187
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .foundation import *
|
|
4
|
+
|
|
5
|
+
# Measured on 2026-07-19 against live law.go.kr (bytes of the emitted envelope):
|
|
6
|
+
#
|
|
7
|
+
# load-delegated-criteria --budget standard 513,570
|
|
8
|
+
# load-institutional-system --budget standard 396,609
|
|
9
|
+
# get-law (139 articles, no --article) 276,748
|
|
10
|
+
# get-case (one decision) 82,700
|
|
11
|
+
# load-legal-context-bundle --budget broad 37,822
|
|
12
|
+
# load-authority-context 6,889
|
|
13
|
+
# get-article 2,280
|
|
14
|
+
#
|
|
15
|
+
# The threshold is in *characters*, not bytes, because characters track token
|
|
16
|
+
# cost and Korean text is three bytes each — measuring bytes would make the same
|
|
17
|
+
# amount of reading look three times worse in Korean than in English. 20,000
|
|
18
|
+
# characters is roughly 10–13k tokens: enough that a caller who could have
|
|
19
|
+
# narrowed the request should be told they didn't.
|
|
20
|
+
LARGE_PAYLOAD_CHARS = 20_000
|
|
21
|
+
|
|
22
|
+
# Searches are exempt. They return candidate identities by design — that breadth
|
|
23
|
+
# is what resolves ambiguity, and 20 hits costs ~18,000 characters, close enough
|
|
24
|
+
# to the line that including them would make the signal fire on the one command
|
|
25
|
+
# where the size is the intended behaviour.
|
|
26
|
+
_EXEMPT_PREFIXES = ("search-", "resolve-", "expand-", "find-comparable")
|
|
27
|
+
|
|
28
|
+
_ADVICE = {
|
|
29
|
+
"get-law": "전체 로드 대신 --toc로 조문 지도를 먼저 받고, 필요한 조문만 --article로 표적 로드하라.",
|
|
30
|
+
"get-case": "--brief로 요지(판시사항·결정요지)만 먼저 받고, 전문은 인용이 필요할 때만 로드하라.",
|
|
31
|
+
"get-constitutional-decision": "--brief로 요지만 먼저 받고, 전문은 인용이 필요할 때만 로드하라.",
|
|
32
|
+
"get-interpretation": "--brief로 질의·회답만 먼저 받고, 전문은 인용이 필요할 때만 로드하라.",
|
|
33
|
+
"load-delegated-criteria": "--budget minimal로 좁히거나, 기준이 걸린 조문만 --article로 지정하라.",
|
|
34
|
+
"load-institutional-system": "--budget minimal로 좁히거나, 실제로 볼 법령만 --statute으로 지정하라.",
|
|
35
|
+
"load-legal-context-bundle": "--budget minimal로 좁히거나, 진입 조문을 --article로 지정하라.",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_DEFAULT_ADVICE = "이 응답은 컨텍스트를 크게 차지한다 — 범위를 좁힐 인자(--article·--budget·--brief 등)가 있는지 확인하라."
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def large_payload_signals(command: str, data_chars: int, extra: dict[str, Any] | None = None) -> tuple[dict[str, Any], list[str]]:
|
|
42
|
+
"""Flag a response big enough that the caller should have narrowed it.
|
|
43
|
+
|
|
44
|
+
Conditional like every other signal here: a small statute loads silently, so
|
|
45
|
+
the flag firing means something. Measured on the serialized data rather than
|
|
46
|
+
guessed from a row count, because the same number of articles can differ by
|
|
47
|
+
an order of magnitude in length — and because a size check written against
|
|
48
|
+
the actual output keeps working for commands added later.
|
|
49
|
+
"""
|
|
50
|
+
if data_chars < LARGE_PAYLOAD_CHARS or any(command.startswith(p) for p in _EXEMPT_PREFIXES):
|
|
51
|
+
return {}, []
|
|
52
|
+
flags: dict[str, Any] = {"large_payload": {"chars": data_chars}}
|
|
53
|
+
if extra:
|
|
54
|
+
flags["large_payload"].update(extra)
|
|
55
|
+
return flags, [_ADVICE.get(command, _DEFAULT_ADVICE)]
|
|
56
|
+
|
|
57
|
+
__all__ = [name for name in globals() if not name.startswith("__")]
|