sourcelock 0.1.0__tar.gz → 0.2.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.
- {sourcelock-0.1.0 → sourcelock-0.2.0}/ADAPTER_GUIDE.md +11 -2
- {sourcelock-0.1.0 → sourcelock-0.2.0}/PKG-INFO +66 -9
- {sourcelock-0.1.0 → sourcelock-0.2.0}/README.md +65 -8
- {sourcelock-0.1.0 → sourcelock-0.2.0}/RELEASING.md +10 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/action/action.yml +2 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/action/run_doctor.py +4 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/docs/ci.md +7 -1
- {sourcelock-0.1.0 → sourcelock-0.2.0}/examples/sourcelock-example-adapter/README.md +4 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/examples/sourcelock-example-adapter/sourcelock_example_adapter.py +3 -3
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/__init__.py +1 -1
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/_demo.py +2 -2
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/codes.py +127 -53
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/coverage.py +93 -10
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/hcc.py +117 -8
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/leie.py +21 -6
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/provider.py +15 -9
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/cli.py +183 -35
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2023q1.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2023q2.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2023q3.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2023q4.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2024q1.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2024q2.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2024q3.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2024q4.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2025q1.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2025q2.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2025q3.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2025q4.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2026q1.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/hcpcs_2026q2.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/icd10cm_fy2024.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/icd10cm_fy2025.csv.gz +0 -0
- sourcelock-0.2.0/hc_source/data/codes/manifest.json +327 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/data/codes/regenerate.py +161 -9
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/interfaces.py +40 -2
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/mcp_server.py +46 -16
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/receipts.py +5 -1
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/schemas.py +21 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/pyproject.toml +1 -1
- sourcelock-0.2.0/qa/FIX-WAVE-10-BRIEF.md +26 -0
- sourcelock-0.2.0/qa/FIX-WAVE-7-BRIEF.md +45 -0
- sourcelock-0.2.0/qa/FIX-WAVE-8-BRIEF.md +45 -0
- sourcelock-0.2.0/qa/FIX-WAVE-9-BRIEF.md +21 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/adapters/test_codes.py +181 -9
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/adapters/test_coverage.py +120 -3
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/adapters/test_hcc.py +71 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/adapters/test_leie.py +13 -6
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/adapters/test_provider.py +8 -6
- sourcelock-0.2.0/tests/test_answer_contract.py +239 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_cli.py +78 -1
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_codes_cpt_claim.py +11 -11
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_demo_adapter.py +1 -1
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_plugin_discovery.py +1 -1
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_schemas.py +29 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_shortcuts.py +29 -1
- sourcelock-0.1.0/hc_source/data/codes/manifest.json +0 -75
- {sourcelock-0.1.0 → sourcelock-0.2.0}/.github/workflows/ci.yml +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/.github/workflows/release.yml +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/.gitignore +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/LICENSE +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/MORNING-REPORT.md +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/action/dry-run.sh +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/examples/sourcelock-example-adapter/pyproject.toml +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/__init__.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/_demo_fixture.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/adapters/_leie_sample.csv +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/cache.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/cli_manifest.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/data/codes/hcpcs_2026q3.csv.gz +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/data/codes/icd10cm_fy2026.csv.gz +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/data/codes/icd10cm_fy2027.csv.gz +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/data/hcc/hcc_data.json.zlib +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/doctor.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/guard.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/http.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/lockfile.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/manifest.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/hc_source/npi.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/qa/FIX-WAVE-2-BRIEF.md +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/qa/breaker-round1.md +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/scripts/check_wheel.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/scripts/wheel_check.sh +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/source-lock.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/adapters/README.md +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/conftest.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/README.md +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/codes/cdc_root_listing.html +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/codes/cms_landing_trimmed.html +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/README.md +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/articles_report_trimmed.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/contract_types.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/error_400_invalid_ncdid.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/error_401_lcd_no_token.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/final_lcds_report_trimmed.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/lcd_related_documents_33818.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/lcd_related_documents_head.csv +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/local_data_schedule.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/ncd_detail_11.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/ncd_report_trimmed.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/ncd_zip_range_headers.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/openapi_spec_trimmed.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/states.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/whats_new_local_trimmed.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/coverage/whats_new_national.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/VERIFIED_ADDENDUM.md +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/derive_report.txt +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/head_C2419P1M.csv +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/head_C2824T2N.csv +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/head_F2425P1M.txt +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/head_F2826T1N.txt +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/mirror_py2026.bin +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/mirror_sw2025.bin +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/mirror_sw2026.bin +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/mirror_sw2026_republished.bin +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/hcc/regenerate_vendored_data.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/leie/provenance.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/leie/updated_drift.csv +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/leie/updated_ok.csv +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/leie/updated_schema_changed.csv +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/leie/updated_unquoted.csv +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/leie/waivers_page.html +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/PROVENANCE.md +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/lookup_npi_missing.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/lookup_npi_ok.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/npi_api_error_noversion.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/npi_files.html +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/npi_files_v1_only.html +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/pecos_dataset_resources.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/pecos_filter_miss.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/pecos_filter_ok.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/pecos_size1.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/fixtures/provider/pecos_stats.json +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_action.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_adapter_contract.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_cache.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_canary_severity.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_cli_failure_modes.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_doctor.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_doctor_classification.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_doctor_honesty.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_guard.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_guard_bypass.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_guard_must_pass.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_http.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_http_limits.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_lock_init_safety.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_manifest.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_mcp_server.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_no_echo_residue.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_no_input_echo.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_npi.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_offline_doctor.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_packaging.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_receipt_provenance.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_release_workflow.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_stale_canaries.py +0 -0
- {sourcelock-0.1.0 → sourcelock-0.2.0}/tests/test_strict_discovery.py +0 -0
|
@@ -161,7 +161,14 @@ Rules:
|
|
|
161
161
|
* Handlers are synchronous and receive the **validated** model.
|
|
162
162
|
* Never call `handler` directly. `ToolSpec.invoke()` is where the zero-PHI guard
|
|
163
163
|
runs; the CLI and the MCP server both go through it.
|
|
164
|
-
* A miss is not an error. Return
|
|
164
|
+
* A miss is an answer, not an error. Return structured non-null data such as
|
|
165
|
+
`{"found": false}` or `{"matches": [], "match_count": 0}` with an
|
|
166
|
+
`answered` receipt and a warning. The framework rejects an answered
|
|
167
|
+
`ToolResult` whose data is null.
|
|
168
|
+
* A deliberate fail-closed refusal is different: return `data=None` and build
|
|
169
|
+
the receipt with `status=ReceiptStatus.REFUSED` plus a stable uppercase
|
|
170
|
+
`refusal_code`. CLI route calls exit 3 and MCP marks the structured result as
|
|
171
|
+
an error. The framework rejects a refused result whose data is non-null.
|
|
165
172
|
Raise only when the source itself failed.
|
|
166
173
|
|
|
167
174
|
### Receipts
|
|
@@ -395,7 +402,9 @@ to duplicate it. Write tests for the things only you know:
|
|
|
395
402
|
|
|
396
403
|
* a successful call, asserting on `data` **and** on the receipt's
|
|
397
404
|
`source_version` and `effective_from`;
|
|
398
|
-
* a miss, asserting
|
|
405
|
+
* a miss, asserting structured negative data and a warning rather than an exception;
|
|
406
|
+
* a deliberate refusal, asserting `data is None`, `receipt.status == "refused"`,
|
|
407
|
+
and the exact stable `receipt.refusal_code`;
|
|
399
408
|
* fallback: authority down, mirror up, `receipt.fallback_used is True`;
|
|
400
409
|
* both sources down: `SourceUnreachable`;
|
|
401
410
|
* each canary's happy path, and its drift path from a mutated fixture;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcelock
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Zero-PHI source-assurance CLI for healthcare's public data inputs
|
|
5
5
|
Author: SourceLock
|
|
6
6
|
License: MIT License
|
|
@@ -53,14 +53,31 @@ underneath you.
|
|
|
53
53
|
## Install
|
|
54
54
|
|
|
55
55
|
```
|
|
56
|
-
|
|
57
|
-
cd sourcelock
|
|
58
|
-
pipx install .
|
|
56
|
+
pipx install sourcelock
|
|
59
57
|
```
|
|
60
58
|
|
|
61
|
-
Python 3.11 or newer.
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
Python 3.11 or newer. `pip install sourcelock` works too, inside a virtualenv.
|
|
60
|
+
Either way you get one executable: `hc-source`.
|
|
61
|
+
|
|
62
|
+
## Vendored date-of-service coverage
|
|
63
|
+
|
|
64
|
+
- **ICD-10-CM: 2023-10-01 through 2027-03-31.** Vendored releases are
|
|
65
|
+
`fy2024`, `fy2024-april`, `fy2025`, `fy2025-april`, `fy2026`,
|
|
66
|
+
`fy2026-april`, and `fy2027`. FY2027 is supported only through March 31,
|
|
67
|
+
2027 because the April 2027 publication status is not yet settled.
|
|
68
|
+
- **HCPCS Level II: 2023-01-01 through 2026-09-30.** Every quarterly public-use
|
|
69
|
+
file from `2023q1` through `2026q3` is vendored.
|
|
70
|
+
|
|
71
|
+
CMS's [ICD-10 publication record](https://www.cms.gov/medicare/coding-billing/icd-10-codes)
|
|
72
|
+
defines a distinct FY2025 base window (October 1, 2024 through March 31, 2025)
|
|
73
|
+
and April-update window (April 1 through September 30, 2025). The CDC archive
|
|
74
|
+
retains both upstream ZIPs. Their code-description members are byte-identical,
|
|
75
|
+
so the manifest keeps two release/provenance entries while deduplicating the
|
|
76
|
+
derived table; FY2024's published April update is handled the same way.
|
|
77
|
+
|
|
78
|
+
Dates outside the windows above fail closed at exit `3` with
|
|
79
|
+
`RELEASE_NOT_VENDORED` (or `AMBIGUOUS_WINDOW` when an April update is not yet
|
|
80
|
+
settled). SourceLock never substitutes a different release's snapshot.
|
|
64
81
|
|
|
65
82
|
## See it work, offline, right now
|
|
66
83
|
|
|
@@ -157,6 +174,44 @@ hc-source mcp # serve it all to an age
|
|
|
157
174
|
`call` reaches every route, including ones provided by adapters you installed;
|
|
158
175
|
the named commands are shorthand for the four questions people arrive with.
|
|
159
176
|
|
|
177
|
+
## Machine contract for tool calls
|
|
178
|
+
|
|
179
|
+
This is a breaking CLI contract change for the next release. `hc-source call`
|
|
180
|
+
and the four route shortcuts use these exit codes:
|
|
181
|
+
|
|
182
|
+
| exit | meaning | trigger |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| `0` | answer | A positive or negative answer was produced. Negative answers are structured (`found: false`, an empty result list, or LEIE `screen_result: "clear"`); `data` is never null. |
|
|
185
|
+
| `1` | invalid input | The tool name, parameter syntax, PHI guard, or typed parameter validation rejected the call. |
|
|
186
|
+
| `2` | source or adapter failure | The authority could not be read, or the adapter failed unexpectedly or returned an invalid response before it could produce evidenced data. |
|
|
187
|
+
| `3` | deliberate refusal | The route failed closed because the requested release/window/model year cannot be answered from the vendored evidence, or because every HCC diagnosis failed the validity gate without an explicit bypass. `data` is null; `receipt.status` is `"refused"`; `receipt.refusal_code` is stable (for example `AMBIGUOUS_WINDOW`, `RELEASE_NOT_VENDORED`, `REFUSED_MODEL_YEAR`, or `NO_VALID_DIAGNOSES`). |
|
|
188
|
+
|
|
189
|
+
With `--json`, every one of those paths emits the same top-level shape:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"data": {},
|
|
194
|
+
"receipt": {"status": "answered", "refusal_code": null},
|
|
195
|
+
"error": null
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Exactly one outcome channel is populated: answers carry non-null `data` and an
|
|
200
|
+
`answered` receipt; refusals carry `data: null` and a `refused` receipt;
|
|
201
|
+
validation/source/adapter failures carry `data: null`, `receipt: null`, and a
|
|
202
|
+
structured `error` (`INVALID_INPUT`, `SOURCE_UNREACHABLE`, or
|
|
203
|
+
`ADAPTER_FAILURE`). MCP returns that same envelope as `structuredContent` and
|
|
204
|
+
marks failures and deliberate refusals as MCP errors.
|
|
205
|
+
|
|
206
|
+
`hcc.score` has no diagnosis/date-of-service parameter, so it uses one explicit
|
|
207
|
+
validity anchor for every payment year: the vendored ICD-10-CM `fy2026-april`
|
|
208
|
+
release, effective 2026-04-01 through 2026-09-30. The answer payload and receipt
|
|
209
|
+
name that release and its hash. A mixed list still scores from its valid codes;
|
|
210
|
+
a valid-but-unmapped code keeps the existing named-unmapped behavior. If every
|
|
211
|
+
code is invalid, the route refuses with `NO_VALID_DIAGNOSES`. Use
|
|
212
|
+
`--allow-demographic-only` (or MCP/call parameter
|
|
213
|
+
`allow_demographic_only=true`) only to opt into a demographic-only partial RAF.
|
|
214
|
+
|
|
160
215
|
## Pin your sources and fail the build when they move
|
|
161
216
|
|
|
162
217
|
```
|
|
@@ -172,7 +227,7 @@ run ends with:
|
|
|
172
227
|
23 ok, 0 drift, 0 unreachable, 0 schema_changed, 0 unpinned, 0 stale, 0 error
|
|
173
228
|
```
|
|
174
229
|
|
|
175
|
-
|
|
230
|
+
Doctor has a separate, unchanged 0–4 verdict contract: `0` everything matched, `1` drift or schema change,
|
|
176
231
|
`2` a source was unreachable or an adapter broke, `3` a canary was observed but
|
|
177
232
|
nothing pinned it, `4` everything matched but something reported that what it
|
|
178
233
|
read is out of date. `3` and `4` exist because a missing `source-lock.json` used
|
|
@@ -216,7 +271,9 @@ a stack trace — the moment upstream moves.
|
|
|
216
271
|
`hc-source mcp` serves every discovered tool over MCP stdio: one MCP tool per
|
|
217
272
|
route, the same typed parameters, the same receipts, the same PHI refusals as
|
|
218
273
|
the CLI. It refuses to start if any adapter failed to import, because an agent
|
|
219
|
-
that sees a short `tools/list` reads it as the whole product.
|
|
274
|
+
that sees a short `tools/list` reads it as the whole product. Every MCP result,
|
|
275
|
+
including validation failures, source failures, and deliberate refusals, carries
|
|
276
|
+
the machine envelope documented above in `structuredContent`.
|
|
220
277
|
|
|
221
278
|
Responses are cached on disk and revalidated conditionally — an entry is stored
|
|
222
279
|
only if the response carried an `ETag` or a `Last-Modified`, and a hit is a
|
|
@@ -13,14 +13,31 @@ underneath you.
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
|
|
17
|
-
cd sourcelock
|
|
18
|
-
pipx install .
|
|
16
|
+
pipx install sourcelock
|
|
19
17
|
```
|
|
20
18
|
|
|
21
|
-
Python 3.11 or newer.
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
Python 3.11 or newer. `pip install sourcelock` works too, inside a virtualenv.
|
|
20
|
+
Either way you get one executable: `hc-source`.
|
|
21
|
+
|
|
22
|
+
## Vendored date-of-service coverage
|
|
23
|
+
|
|
24
|
+
- **ICD-10-CM: 2023-10-01 through 2027-03-31.** Vendored releases are
|
|
25
|
+
`fy2024`, `fy2024-april`, `fy2025`, `fy2025-april`, `fy2026`,
|
|
26
|
+
`fy2026-april`, and `fy2027`. FY2027 is supported only through March 31,
|
|
27
|
+
2027 because the April 2027 publication status is not yet settled.
|
|
28
|
+
- **HCPCS Level II: 2023-01-01 through 2026-09-30.** Every quarterly public-use
|
|
29
|
+
file from `2023q1` through `2026q3` is vendored.
|
|
30
|
+
|
|
31
|
+
CMS's [ICD-10 publication record](https://www.cms.gov/medicare/coding-billing/icd-10-codes)
|
|
32
|
+
defines a distinct FY2025 base window (October 1, 2024 through March 31, 2025)
|
|
33
|
+
and April-update window (April 1 through September 30, 2025). The CDC archive
|
|
34
|
+
retains both upstream ZIPs. Their code-description members are byte-identical,
|
|
35
|
+
so the manifest keeps two release/provenance entries while deduplicating the
|
|
36
|
+
derived table; FY2024's published April update is handled the same way.
|
|
37
|
+
|
|
38
|
+
Dates outside the windows above fail closed at exit `3` with
|
|
39
|
+
`RELEASE_NOT_VENDORED` (or `AMBIGUOUS_WINDOW` when an April update is not yet
|
|
40
|
+
settled). SourceLock never substitutes a different release's snapshot.
|
|
24
41
|
|
|
25
42
|
## See it work, offline, right now
|
|
26
43
|
|
|
@@ -117,6 +134,44 @@ hc-source mcp # serve it all to an age
|
|
|
117
134
|
`call` reaches every route, including ones provided by adapters you installed;
|
|
118
135
|
the named commands are shorthand for the four questions people arrive with.
|
|
119
136
|
|
|
137
|
+
## Machine contract for tool calls
|
|
138
|
+
|
|
139
|
+
This is a breaking CLI contract change for the next release. `hc-source call`
|
|
140
|
+
and the four route shortcuts use these exit codes:
|
|
141
|
+
|
|
142
|
+
| exit | meaning | trigger |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| `0` | answer | A positive or negative answer was produced. Negative answers are structured (`found: false`, an empty result list, or LEIE `screen_result: "clear"`); `data` is never null. |
|
|
145
|
+
| `1` | invalid input | The tool name, parameter syntax, PHI guard, or typed parameter validation rejected the call. |
|
|
146
|
+
| `2` | source or adapter failure | The authority could not be read, or the adapter failed unexpectedly or returned an invalid response before it could produce evidenced data. |
|
|
147
|
+
| `3` | deliberate refusal | The route failed closed because the requested release/window/model year cannot be answered from the vendored evidence, or because every HCC diagnosis failed the validity gate without an explicit bypass. `data` is null; `receipt.status` is `"refused"`; `receipt.refusal_code` is stable (for example `AMBIGUOUS_WINDOW`, `RELEASE_NOT_VENDORED`, `REFUSED_MODEL_YEAR`, or `NO_VALID_DIAGNOSES`). |
|
|
148
|
+
|
|
149
|
+
With `--json`, every one of those paths emits the same top-level shape:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"data": {},
|
|
154
|
+
"receipt": {"status": "answered", "refusal_code": null},
|
|
155
|
+
"error": null
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Exactly one outcome channel is populated: answers carry non-null `data` and an
|
|
160
|
+
`answered` receipt; refusals carry `data: null` and a `refused` receipt;
|
|
161
|
+
validation/source/adapter failures carry `data: null`, `receipt: null`, and a
|
|
162
|
+
structured `error` (`INVALID_INPUT`, `SOURCE_UNREACHABLE`, or
|
|
163
|
+
`ADAPTER_FAILURE`). MCP returns that same envelope as `structuredContent` and
|
|
164
|
+
marks failures and deliberate refusals as MCP errors.
|
|
165
|
+
|
|
166
|
+
`hcc.score` has no diagnosis/date-of-service parameter, so it uses one explicit
|
|
167
|
+
validity anchor for every payment year: the vendored ICD-10-CM `fy2026-april`
|
|
168
|
+
release, effective 2026-04-01 through 2026-09-30. The answer payload and receipt
|
|
169
|
+
name that release and its hash. A mixed list still scores from its valid codes;
|
|
170
|
+
a valid-but-unmapped code keeps the existing named-unmapped behavior. If every
|
|
171
|
+
code is invalid, the route refuses with `NO_VALID_DIAGNOSES`. Use
|
|
172
|
+
`--allow-demographic-only` (or MCP/call parameter
|
|
173
|
+
`allow_demographic_only=true`) only to opt into a demographic-only partial RAF.
|
|
174
|
+
|
|
120
175
|
## Pin your sources and fail the build when they move
|
|
121
176
|
|
|
122
177
|
```
|
|
@@ -132,7 +187,7 @@ run ends with:
|
|
|
132
187
|
23 ok, 0 drift, 0 unreachable, 0 schema_changed, 0 unpinned, 0 stale, 0 error
|
|
133
188
|
```
|
|
134
189
|
|
|
135
|
-
|
|
190
|
+
Doctor has a separate, unchanged 0–4 verdict contract: `0` everything matched, `1` drift or schema change,
|
|
136
191
|
`2` a source was unreachable or an adapter broke, `3` a canary was observed but
|
|
137
192
|
nothing pinned it, `4` everything matched but something reported that what it
|
|
138
193
|
read is out of date. `3` and `4` exist because a missing `source-lock.json` used
|
|
@@ -176,7 +231,9 @@ a stack trace — the moment upstream moves.
|
|
|
176
231
|
`hc-source mcp` serves every discovered tool over MCP stdio: one MCP tool per
|
|
177
232
|
route, the same typed parameters, the same receipts, the same PHI refusals as
|
|
178
233
|
the CLI. It refuses to start if any adapter failed to import, because an agent
|
|
179
|
-
that sees a short `tools/list` reads it as the whole product.
|
|
234
|
+
that sees a short `tools/list` reads it as the whole product. Every MCP result,
|
|
235
|
+
including validation failures, source failures, and deliberate refusals, carries
|
|
236
|
+
the machine envelope documented above in `structuredContent`.
|
|
180
237
|
|
|
181
238
|
Responses are cached on disk and revalidated conditionally — an entry is stored
|
|
182
239
|
only if the response carried an `ETag` or a `Last-Modified`, and a hit is a
|
|
@@ -99,6 +99,16 @@ The third command must print an answer and a receipt without touching the
|
|
|
99
99
|
network. If it does not, the wheel is missing `hc_source/data/` and the release
|
|
100
100
|
should be yanked rather than patched over.
|
|
101
101
|
|
|
102
|
+
## Next-version breaking contract note
|
|
103
|
+
|
|
104
|
+
The next release after `0.1.1` must call out the tool-call contract break:
|
|
105
|
+
deliberate fail-closed outcomes move from exit 0 to exit 3 and carry
|
|
106
|
+
`receipt.status="refused"` plus `receipt.refusal_code`; negative answers carry
|
|
107
|
+
structured non-null data; and `--json`/MCP use one `{data, receipt, error}`
|
|
108
|
+
envelope for answers, refusals, invalid input, and source failures. Doctor's
|
|
109
|
+
existing 0–4 verdict meanings do not change. Do not publish the next version
|
|
110
|
+
without putting that migration note in its release notes.
|
|
111
|
+
|
|
102
112
|
For the GitHub Release artifacts instead of PyPI:
|
|
103
113
|
|
|
104
114
|
```bash
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
# The job fails (with error annotations carrying each canary's remediation
|
|
10
10
|
# verbatim) whenever doctor reports drift, a schema change, or an unreachable
|
|
11
11
|
# source. See docs/ci.md for the full workflow example.
|
|
12
|
+
# This action consumes doctor's 0-4 verdict contract only. Tool-call exit 3 is
|
|
13
|
+
# a deliberate refusal; doctor exit 3 remains observed-but-unpinned.
|
|
12
14
|
name: "SourceLock Doctor"
|
|
13
15
|
description: >-
|
|
14
16
|
Fail the build when a pinned public healthcare data source drifts, changes
|
|
@@ -22,6 +22,10 @@ Behaviour contract:
|
|
|
22
22
|
* refuses to report green on a report that contradicts itself -- see
|
|
23
23
|
:func:`reconcile_exit_code`.
|
|
24
24
|
|
|
25
|
+
These are doctor-specific meanings. Route calls have a separate 0-3 contract
|
|
26
|
+
where exit 3 is a deliberate refusal; this script never invokes a route call and
|
|
27
|
+
must not reinterpret doctor's exit 3 as one.
|
|
28
|
+
|
|
25
29
|
Local dry-run (no GitHub, no install step)::
|
|
26
30
|
|
|
27
31
|
python action/run_doctor.py --from-json report.json
|
|
@@ -7,6 +7,12 @@ source. Every failing canary becomes a GitHub error annotation carrying that
|
|
|
7
7
|
canary's remediation text verbatim, so the person reading the red build sees
|
|
8
8
|
the fix, not a stack trace.
|
|
9
9
|
|
|
10
|
+
The action runs **doctor only**, so its exit `3` remains doctor's
|
|
11
|
+
"observed but unpinned" verdict. It does not consume route-call exit codes;
|
|
12
|
+
`hc-source call` and the route shortcuts now use exit `3` for a deliberate
|
|
13
|
+
fail-closed refusal. The two contracts are intentionally namespaced by command
|
|
14
|
+
and are both tabulated in the README.
|
|
15
|
+
|
|
10
16
|
## Scaffold it
|
|
11
17
|
|
|
12
18
|
```
|
|
@@ -117,7 +123,7 @@ action version you pin is exactly the scanner version you run.
|
|
|
117
123
|
|
|
118
124
|
## What failure looks like
|
|
119
125
|
|
|
120
|
-
Exit codes are doctor's contract: `0` everything matches the lockfile, `1`
|
|
126
|
+
Exit codes here are doctor's unchanged contract: `0` everything matches the lockfile, `1`
|
|
121
127
|
drift or schema change, `2` unreachable source, adapter load failure, or an
|
|
122
128
|
internal adapter error, `3` a canary was observed but nothing pinned it, `4`
|
|
123
129
|
every canary matched but at least one reported that what it read is out of
|
|
@@ -50,6 +50,10 @@ The same things it enforces on itself. There is no privileged path.
|
|
|
50
50
|
* **Receipts.** `build_receipt` requires at least one domain non-claim and takes
|
|
51
51
|
provenance from the fetch, so you cannot stamp a retrieval time you did not
|
|
52
52
|
perform.
|
|
53
|
+
* **Answer nullability.** An ordinary miss is structured data such as
|
|
54
|
+
`{"found": false}`. Only a deliberate fail-closed refusal may use
|
|
55
|
+
`data=None`, and its receipt must say `status="refused"` with a stable
|
|
56
|
+
`refusal_code`.
|
|
53
57
|
|
|
54
58
|
## What a broken plugin looks like
|
|
55
59
|
|
|
@@ -45,7 +45,7 @@ from hc_source.schemas import SourceContract
|
|
|
45
45
|
# name, and every tool you declare must be prefixed with this id.
|
|
46
46
|
SOURCE_ID = "example_formulary"
|
|
47
47
|
|
|
48
|
-
TRANSFORM_VERSION = "example-formulary-transform/
|
|
48
|
+
TRANSFORM_VERSION = "example-formulary-transform/2"
|
|
49
49
|
|
|
50
50
|
RELEASE_ID = "2026-07"
|
|
51
51
|
_RELEASE_FROM = date(2026, 7, 1)
|
|
@@ -136,7 +136,7 @@ def _lookup(params: LookupParams) -> ToolResult:
|
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
return ToolResult(
|
|
139
|
-
data=
|
|
139
|
+
data={"found": False} if entry is None else {"code": params.code, **entry},
|
|
140
140
|
receipt=build_receipt(
|
|
141
141
|
contract=CONTRACT,
|
|
142
142
|
route=f"{SOURCE_ID}.lookup",
|
|
@@ -200,4 +200,4 @@ class ExampleFormularyAdapter(SourceAdapter):
|
|
|
200
200
|
|
|
201
201
|
|
|
202
202
|
#: The module-level name discovery looks for. Everything above is yours.
|
|
203
|
-
ADAPTER = ExampleFormularyAdapter()
|
|
203
|
+
ADAPTER = ExampleFormularyAdapter()
|
|
@@ -28,7 +28,7 @@ from ..receipts import build_receipt
|
|
|
28
28
|
from ..schemas import SourceContract
|
|
29
29
|
|
|
30
30
|
SOURCE_ID = "demo"
|
|
31
|
-
TRANSFORM_VERSION = "
|
|
31
|
+
TRANSFORM_VERSION = "2"
|
|
32
32
|
|
|
33
33
|
#: Environment override, used by the test suite to simulate upstream drift.
|
|
34
34
|
FIXTURE_ENV_VAR = "HC_SOURCE_DEMO_FIXTURE"
|
|
@@ -145,7 +145,7 @@ def _lookup_code(params: LookupCodeParams) -> ToolResult:
|
|
|
145
145
|
)
|
|
146
146
|
|
|
147
147
|
return ToolResult(
|
|
148
|
-
data=match,
|
|
148
|
+
data=match if match is not None else {"found": False},
|
|
149
149
|
receipt=build_receipt(
|
|
150
150
|
contract=CONTRACT,
|
|
151
151
|
route="demo.lookup_code",
|