isa-sdk 1.0.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.
Files changed (160) hide show
  1. isa_sdk-1.0.0/.gitignore +9 -0
  2. isa_sdk-1.0.0/CHANGELOG.md +86 -0
  3. isa_sdk-1.0.0/MIGRATION.md +140 -0
  4. isa_sdk-1.0.0/PKG-INFO +328 -0
  5. isa_sdk-1.0.0/PUBLIC_API.txt +1141 -0
  6. isa_sdk-1.0.0/README.md +294 -0
  7. isa_sdk-1.0.0/pyproject.toml +86 -0
  8. isa_sdk-1.0.0/scripts/gen_catalog.py +882 -0
  9. isa_sdk-1.0.0/src/isa_sdk/__init__.py +60 -0
  10. isa_sdk-1.0.0/src/isa_sdk/account.py +5 -0
  11. isa_sdk-1.0.0/src/isa_sdk/catalog.py +5 -0
  12. isa_sdk-1.0.0/src/isa_sdk/core.py +5 -0
  13. isa_sdk-1.0.0/src/isa_sdk/proxy.py +5 -0
  14. isa_sdk-1.0.0/src/isa_sdk/py.typed +0 -0
  15. isa_sdk-1.0.0/src/isa_sdk/rapidsign.py +5 -0
  16. isa_sdk-1.0.0/src/isa_sdk/webhooks.py +5 -0
  17. isa_sdk-1.0.0/src/isa_sdk/zyins.py +50 -0
  18. isa_sdk-1.0.0/src/sah_sdk/__contract__.py +257 -0
  19. isa_sdk-1.0.0/src/sah_sdk/__init__.py +133 -0
  20. isa_sdk-1.0.0/src/sah_sdk/_codemod/__init__.py +25 -0
  21. isa_sdk-1.0.0/src/sah_sdk/_codemod/__main__.py +45 -0
  22. isa_sdk-1.0.0/src/sah_sdk/_codemod/rewriter.py +179 -0
  23. isa_sdk-1.0.0/src/sah_sdk/account/__init__.py +142 -0
  24. isa_sdk-1.0.0/src/sah_sdk/account/_op.py +86 -0
  25. isa_sdk-1.0.0/src/sah_sdk/account/branding.py +107 -0
  26. isa_sdk-1.0.0/src/sah_sdk/account/cases.py +173 -0
  27. isa_sdk-1.0.0/src/sah_sdk/account/email.py +92 -0
  28. isa_sdk-1.0.0/src/sah_sdk/account/preferences.py +102 -0
  29. isa_sdk-1.0.0/src/sah_sdk/account/reference_data.py +111 -0
  30. isa_sdk-1.0.0/src/sah_sdk/catalog/__init__.py +36 -0
  31. isa_sdk-1.0.0/src/sah_sdk/catalog/carriers.py +107 -0
  32. isa_sdk-1.0.0/src/sah_sdk/catalog/conditions.py +44 -0
  33. isa_sdk-1.0.0/src/sah_sdk/catalog/errors.py +89 -0
  34. isa_sdk-1.0.0/src/sah_sdk/catalog/medications.py +1909 -0
  35. isa_sdk-1.0.0/src/sah_sdk/catalog/products.py +312 -0
  36. isa_sdk-1.0.0/src/sah_sdk/catalog/scopes.py +35 -0
  37. isa_sdk-1.0.0/src/sah_sdk/catalog/sign_events.py +24 -0
  38. isa_sdk-1.0.0/src/sah_sdk/catalog/states.py +236 -0
  39. isa_sdk-1.0.0/src/sah_sdk/core/__init__.py +56 -0
  40. isa_sdk-1.0.0/src/sah_sdk/core/auth.py +95 -0
  41. isa_sdk-1.0.0/src/sah_sdk/core/bootstrap.py +135 -0
  42. isa_sdk-1.0.0/src/sah_sdk/core/constants.py +117 -0
  43. isa_sdk-1.0.0/src/sah_sdk/core/credential_store.py +96 -0
  44. isa_sdk-1.0.0/src/sah_sdk/core/debug.py +157 -0
  45. isa_sdk-1.0.0/src/sah_sdk/core/env.py +73 -0
  46. isa_sdk-1.0.0/src/sah_sdk/core/envelope.py +93 -0
  47. isa_sdk-1.0.0/src/sah_sdk/core/errors.py +388 -0
  48. isa_sdk-1.0.0/src/sah_sdk/core/idempotency.py +16 -0
  49. isa_sdk-1.0.0/src/sah_sdk/core/json_response.py +40 -0
  50. isa_sdk-1.0.0/src/sah_sdk/core/license_hmac.py +134 -0
  51. isa_sdk-1.0.0/src/sah_sdk/core/session.py +336 -0
  52. isa_sdk-1.0.0/src/sah_sdk/core/sign_request.py +133 -0
  53. isa_sdk-1.0.0/src/sah_sdk/core/transport.py +102 -0
  54. isa_sdk-1.0.0/src/sah_sdk/core/value_types.py +96 -0
  55. isa_sdk-1.0.0/src/sah_sdk/core/wire.py +70 -0
  56. isa_sdk-1.0.0/src/sah_sdk/isa.py +1635 -0
  57. isa_sdk-1.0.0/src/sah_sdk/proxy/__init__.py +88 -0
  58. isa_sdk-1.0.0/src/sah_sdk/proxy/call.py +302 -0
  59. isa_sdk-1.0.0/src/sah_sdk/py.typed +0 -0
  60. isa_sdk-1.0.0/src/sah_sdk/rapidsign/__init__.py +30 -0
  61. isa_sdk-1.0.0/src/sah_sdk/webhooks.py +29 -0
  62. isa_sdk-1.0.0/src/sah_sdk/zyins/__init__.py +210 -0
  63. isa_sdk-1.0.0/src/sah_sdk/zyins/account_namespaces.py +162 -0
  64. isa_sdk-1.0.0/src/sah_sdk/zyins/applicant.py +180 -0
  65. isa_sdk-1.0.0/src/sah_sdk/zyins/branding.py +75 -0
  66. isa_sdk-1.0.0/src/sah_sdk/zyins/bundled_api_versions.py +72 -0
  67. isa_sdk-1.0.0/src/sah_sdk/zyins/cases/__init__.py +254 -0
  68. isa_sdk-1.0.0/src/sah_sdk/zyins/cases/storage.py +104 -0
  69. isa_sdk-1.0.0/src/sah_sdk/zyins/cases/zero_knowledge.py +269 -0
  70. isa_sdk-1.0.0/src/sah_sdk/zyins/cases.py +141 -0
  71. isa_sdk-1.0.0/src/sah_sdk/zyins/cases_storage.py +19 -0
  72. isa_sdk-1.0.0/src/sah_sdk/zyins/client.py +431 -0
  73. isa_sdk-1.0.0/src/sah_sdk/zyins/coverage.py +111 -0
  74. isa_sdk-1.0.0/src/sah_sdk/zyins/credential_state.py +151 -0
  75. isa_sdk-1.0.0/src/sah_sdk/zyins/datasets.py +35 -0
  76. isa_sdk-1.0.0/src/sah_sdk/zyins/datasets_v3.py +764 -0
  77. isa_sdk-1.0.0/src/sah_sdk/zyins/health.py +47 -0
  78. isa_sdk-1.0.0/src/sah_sdk/zyins/isa_options.py +335 -0
  79. isa_sdk-1.0.0/src/sah_sdk/zyins/license.py +46 -0
  80. isa_sdk-1.0.0/src/sah_sdk/zyins/licenses.py +122 -0
  81. isa_sdk-1.0.0/src/sah_sdk/zyins/licenses_facade.py +227 -0
  82. isa_sdk-1.0.0/src/sah_sdk/zyins/logos.py +147 -0
  83. isa_sdk-1.0.0/src/sah_sdk/zyins/measurements.py +279 -0
  84. isa_sdk-1.0.0/src/sah_sdk/zyins/plan_info_label.py +128 -0
  85. isa_sdk-1.0.0/src/sah_sdk/zyins/preferences.py +74 -0
  86. isa_sdk-1.0.0/src/sah_sdk/zyins/prequalify.py +282 -0
  87. isa_sdk-1.0.0/src/sah_sdk/zyins/prequalify_legacy_blob.py +44 -0
  88. isa_sdk-1.0.0/src/sah_sdk/zyins/prequalify_v3.py +904 -0
  89. isa_sdk-1.0.0/src/sah_sdk/zyins/product.py +216 -0
  90. isa_sdk-1.0.0/src/sah_sdk/zyins/products.py +54 -0
  91. isa_sdk-1.0.0/src/sah_sdk/zyins/quote.py +80 -0
  92. isa_sdk-1.0.0/src/sah_sdk/zyins/quote_v3.py +153 -0
  93. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/__init__.py +435 -0
  94. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/_make_key.py +39 -0
  95. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/autocomplete_algorithm.py +406 -0
  96. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/autocorrector.py +325 -0
  97. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/concept.py +173 -0
  98. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/index.py +260 -0
  99. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/match_algorithm.py +111 -0
  100. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/reference_index.py +215 -0
  101. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/sort.py +31 -0
  102. isa_sdk-1.0.0/src/sah_sdk/zyins/reference/suggestion.py +77 -0
  103. isa_sdk-1.0.0/src/sah_sdk/zyins/reference_data.py +42 -0
  104. isa_sdk-1.0.0/src/sah_sdk/zyins/reference_v3.py +394 -0
  105. isa_sdk-1.0.0/src/sah_sdk/zyins/usage.py +32 -0
  106. isa_sdk-1.0.0/tests/__init__.py +0 -0
  107. isa_sdk-1.0.0/tests/account/__init__.py +0 -0
  108. isa_sdk-1.0.0/tests/account/helpers.py +63 -0
  109. isa_sdk-1.0.0/tests/account/test_branding.py +65 -0
  110. isa_sdk-1.0.0/tests/account/test_cases.py +170 -0
  111. isa_sdk-1.0.0/tests/account/test_email.py +67 -0
  112. isa_sdk-1.0.0/tests/account/test_preferences.py +48 -0
  113. isa_sdk-1.0.0/tests/account/test_reference_data.py +57 -0
  114. isa_sdk-1.0.0/tests/catalog/__init__.py +0 -0
  115. isa_sdk-1.0.0/tests/catalog/test_catalog.py +96 -0
  116. isa_sdk-1.0.0/tests/core/__init__.py +0 -0
  117. isa_sdk-1.0.0/tests/core/test_bootstrap_conformance.py +135 -0
  118. isa_sdk-1.0.0/tests/core/test_sign_request.py +212 -0
  119. isa_sdk-1.0.0/tests/proxy/__init__.py +0 -0
  120. isa_sdk-1.0.0/tests/proxy/test_call.py +218 -0
  121. isa_sdk-1.0.0/tests/test_canonical_aliases.py +101 -0
  122. isa_sdk-1.0.0/tests/test_conformance_scenarios.py +108 -0
  123. isa_sdk-1.0.0/tests/test_session_interceptor.py +380 -0
  124. isa_sdk-1.0.0/tests/test_top_level_exports.py +89 -0
  125. isa_sdk-1.0.0/tests/zyins/test_account_namespaces.py +255 -0
  126. isa_sdk-1.0.0/tests/zyins/test_api_version_map_and_case_storage.py +215 -0
  127. isa_sdk-1.0.0/tests/zyins/test_bundleless_match_and_cases_save_recall.py +366 -0
  128. isa_sdk-1.0.0/tests/zyins/test_client.py +200 -0
  129. isa_sdk-1.0.0/tests/zyins/test_concurrency.py +113 -0
  130. isa_sdk-1.0.0/tests/zyins/test_datasets.py +145 -0
  131. isa_sdk-1.0.0/tests/zyins/test_datasets_v3.py +359 -0
  132. isa_sdk-1.0.0/tests/zyins/test_datasets_v3_parse_conformance.py +71 -0
  133. isa_sdk-1.0.0/tests/zyins/test_debug_logging.py +164 -0
  134. isa_sdk-1.0.0/tests/zyins/test_envelope_and_raw_response.py +168 -0
  135. isa_sdk-1.0.0/tests/zyins/test_errors.py +142 -0
  136. isa_sdk-1.0.0/tests/zyins/test_health.py +114 -0
  137. isa_sdk-1.0.0/tests/zyins/test_idempotency_conflict.py +91 -0
  138. isa_sdk-1.0.0/tests/zyins/test_isa_factories.py +125 -0
  139. isa_sdk-1.0.0/tests/zyins/test_isa_options.py +338 -0
  140. isa_sdk-1.0.0/tests/zyins/test_isa_reference_wiring.py +137 -0
  141. isa_sdk-1.0.0/tests/zyins/test_licenses.py +120 -0
  142. isa_sdk-1.0.0/tests/zyins/test_licenses_facade.py +307 -0
  143. isa_sdk-1.0.0/tests/zyins/test_logos.py +89 -0
  144. isa_sdk-1.0.0/tests/zyins/test_measurements.py +170 -0
  145. isa_sdk-1.0.0/tests/zyins/test_plan_info_label.py +165 -0
  146. isa_sdk-1.0.0/tests/zyins/test_prequalify.py +448 -0
  147. isa_sdk-1.0.0/tests/zyins/test_prequalify_kwargs.py +217 -0
  148. isa_sdk-1.0.0/tests/zyins/test_prequalify_legacy_blob.py +93 -0
  149. isa_sdk-1.0.0/tests/zyins/test_prequalify_v2_fields.py +201 -0
  150. isa_sdk-1.0.0/tests/zyins/test_prequalify_v3.py +291 -0
  151. isa_sdk-1.0.0/tests/zyins/test_prequalify_v3_multi_amount.py +202 -0
  152. isa_sdk-1.0.0/tests/zyins/test_prequalify_v3_wire_shape.py +368 -0
  153. isa_sdk-1.0.0/tests/zyins/test_quote.py +64 -0
  154. isa_sdk-1.0.0/tests/zyins/test_quote_v3.py +240 -0
  155. isa_sdk-1.0.0/tests/zyins/test_reference_adapters.py +830 -0
  156. isa_sdk-1.0.0/tests/zyins/test_reference_namespace.py +473 -0
  157. isa_sdk-1.0.0/tests/zyins/test_reference_v3_conformance.py +184 -0
  158. isa_sdk-1.0.0/tests/zyins/test_v3_facade_routing.py +211 -0
  159. isa_sdk-1.0.0/tests/zyins/test_zero_knowledge_get_not_found.py +54 -0
  160. isa_sdk-1.0.0/uv.lock +868 -0
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ build/
9
+ *.egg-info/
@@ -0,0 +1,86 @@
1
+ # Changelog — sah-sdk
2
+
3
+ All notable changes to the unified Python SDK. Until v1.0.0 ships to
4
+ public PyPI, every release is a pre-release and the API may change in
5
+ incompatible ways.
6
+
7
+ ## 1.0.0rc2 — 2026-05-30
8
+
9
+ Reference-facade consumer-gap fixes so bpp2.0 can drop its bypasses. The
10
+ underlying release tag is `sdk/v1.0.0-rc.2`.
11
+
12
+ ### Fixed
13
+
14
+ - **`products_by_family` skips empty-id rows.** A row whose `id` is an
15
+ empty string is now dropped (was kept), matching the Go/PHP/C# mirrors.
16
+ - **`discontinued_products` accepts integer-valued float epochs.** The
17
+ strict `isinstance(value, int)` check is replaced by an integer-epoch
18
+ coercion that keeps integer-valued numbers in any JSON notation
19
+ (`1700000000.0`) and drops genuine fractionals (`1700000000.5`).
20
+
21
+ ## 1.0.0rc1 — 2026-05-29
22
+
23
+ First release candidate for the public 1.0 SDK. **Internal channel only**
24
+ (GitHub Packages / Test PyPI). The committed metadata uses PEP 440
25
+ normalized form `1.0.0rc1`; the underlying release tag is
26
+ `sdk/v1.0.0-rc.1`.
27
+
28
+ ### Highlights
29
+
30
+ - **Per-surface `api_version` map (no global default).**
31
+ `Isa.with_keycode(api_version={"prequalify": "2026-05-14", ...})`.
32
+ Surfaces missing from the map fall back to `BUNDLED_API_VERSIONS`.
33
+ - **`BUNDLED_API_VERSIONS` exported.** The full per-surface default
34
+ table is a public symbol; downstream tooling and conformance
35
+ scenarios pin against it byte-identically across all 5 languages.
36
+ - **Bundleless top-level `isa.zyins.reference.match(text)`.** Fetches
37
+ and caches the v3 datasets index transparently. No prior
38
+ `datasets.get_v3()` required. `refresh_reference_index()`
39
+ invalidates.
40
+ - **`cases.save` / `cases.recall` via injectable `CaseStorage`.**
41
+ Default `ZeroKnowledgeCaseStorage` envelopes the payload before
42
+ writing through `/v1/case`. Carrier adapters implement the same
43
+ protocol to redirect persistence.
44
+ - **v3 wire-shape end-to-end.** `prequalify` / `quote` accept the
45
+ nested `applicant` + `coverage` + `products` envelope; `pricing[]`
46
+ table iteration on offers; ULID-aware conditions/medications;
47
+ `face_amount_cents` singular.
48
+ - **Idempotency: strict UUID v4** on `/v3/*` mutations. SDK auto-mints
49
+ per call; consumer override via `idempotency_key=` validated at
50
+ the transport layer.
51
+
52
+ ### Migration
53
+
54
+ See [`MIGRATION.md`](./MIGRATION.md) for the 0.x → 1.0 cut and
55
+ [../../MIGRATION.md](../../MIGRATION.md) for the cross-language guide.
56
+
57
+ ### Known drift (conformance gate YELLOW — non-blocking)
58
+
59
+ The Python conformance runner is PENDING; nine known drift items from
60
+ the TypeScript canonical runner are tracked in PR #387:
61
+
62
+ 1. Bearer auth doesn't reach product methods on the locked surface;
63
+ `isa.zyins.prequalify` requires `Isa.with_keycode()`.
64
+ 2. `applicant.height_inches` (number) vs `applicant.height` (Height
65
+ object): v3 serializer crashes on scenarios 01,02,03,04,05,12.
66
+ 3. Scenario 12 routes through v2, not v3 (no `api_version` map).
67
+ 4. `reference.concepts.match` returns `is_known=False` when the v3
68
+ datasets bundle hasn't been primed (scenarios 03, 07, 09).
69
+ 5. `cases.save({applicant, state, note})` rejected; locked
70
+ `ZeroKnowledgeCaseStorage` requires `{product, payload}`.
71
+ 6. `cases.recall(id)` without `recall_token` throws by design;
72
+ scenario 10 assumes token-optional recall.
73
+ 7. `prequalify(req, idempotency_key=...)` not supported on locked
74
+ signature (scenario 12).
75
+ 8. `reference.match` (scenario) vs `reference.concepts.match` (locked).
76
+ 9. `reference.conditions_for(id=, sort=)` does not exist; traversal
77
+ is on the concept handle.
78
+
79
+ Each is either an SDK fix, a scenario rewrite, or "by design" — the
80
+ gate is YELLOW pending triage.
81
+
82
+ ### Links
83
+
84
+ - Docs: <https://docs.isaapi.com>
85
+ - Guides: [`api/guides/`](../../api/guides/)
86
+ - Migration: [`MIGRATION.md`](./MIGRATION.md)
@@ -0,0 +1,140 @@
1
+ # Migration — 0.x → 1.0.0rc1 (Python)
2
+
3
+ The cross-language guide at [`../../MIGRATION.md`](../../MIGRATION.md)
4
+ covers the full cut (constructor rename, per-surface `api_version`,
5
+ v3 wire-shape, CaseStorage adapter, bundleless `reference.match`).
6
+ Python-specific notes:
7
+
8
+ - **Install (rc.1, internal channel):**
9
+
10
+ ```bash
11
+ # Test PyPI:
12
+ pip install --index-url https://test.pypi.org/simple/ \
13
+ --extra-index-url https://pypi.org/simple/ \
14
+ sah-sdk==1.0.0rc1
15
+
16
+ # OR GitHub Packages for Python (when configured):
17
+ pip install --index-url https://<token>@pypi.pkg.github.com/Software-Automation-Holdings-LLC/ \
18
+ sah-sdk==1.0.0rc1
19
+ ```
20
+
21
+ PEP 440 normalization: the release tag is `sdk/v1.0.0-rc.1`; the
22
+ installable package version is `1.0.0rc1`.
23
+
24
+ - **Constructor:** `Isa.create(...)` → `Isa.with_keycode(...)`. The
25
+ `device_id` parameter is removed (internal SDK detail).
26
+ - **api_version:** `str` → `dict[str, str]` (per-surface map). Use
27
+ `BUNDLED_API_VERSIONS` for defaults.
28
+ - **Cases:** `isa.case.save(...)` → `isa.zyins.cases.save(product=...,
29
+ payload=...)`. Default storage is `ZeroKnowledgeCaseStorage`.
30
+ - **Reference:** `datasets.get_v3().match(...)` →
31
+ `isa.zyins.reference.match(text)`. Cache primes on first call;
32
+ `isa.zyins.reference.refresh()` invalidates.
33
+ - **Type checking:** mypy strict against the SDK now enforces the
34
+ locked surface contract.
35
+
36
+ ---
37
+
38
+ # Historical: `isa-sdk-zyins` 0.2.x → `sah-sdk` 0.3.0
39
+
40
+ ## What changed
41
+
42
+ The Python SDK has consolidated from a per-product package into a single
43
+ unified package. Per `SDK_DESIGN.md` §0 (2026-05-18), there is now **one
44
+ package per language**, with each product mounted under a sub-namespace:
45
+
46
+ | Before (0.2.x) | After (0.3.0) |
47
+ |-------------------------------|----------------------------------------|
48
+ | `isa-sdk-zyins` (PyPI name) | `sah-sdk` |
49
+ | `isa_sdk.zyins` (import root) | `sah_sdk.zyins` |
50
+ | `isa_sdk.zyins.transport` | `sah_sdk.core.transport` |
51
+ | `isa_sdk.zyins.errors` | `sah_sdk.core.errors` |
52
+ | `isa_sdk.zyins.envelope` | `sah_sdk.core.envelope` |
53
+ | `isa_sdk.zyins.debug` | `sah_sdk.core.debug` |
54
+ | `isa_sdk.zyins.auth` | `sah_sdk.core.auth` |
55
+ | (n/a — was zyins-only) | `sah_sdk.rapidsign` (scaffold) |
56
+ | (n/a — was zyins-only) | `sah_sdk.proxy` (scaffold) |
57
+
58
+ All public re-exports remain available at the namespace root —
59
+ `from sah_sdk.zyins import Isa, QuoteInput, PrequalifyResult` still
60
+ works, and `Isa` is additionally exported from the top-level
61
+ (`from sah_sdk import Isa`). Domain types (`Applicant`, `Coverage`,
62
+ `Product`, `Medication`, `Condition`, …) are unchanged.
63
+
64
+ ## Why
65
+
66
+ - Per-product splits added 20 publish targets (4 products × 5 languages)
67
+ without realized benefit — bundle size, licensing separation, and
68
+ release cadence are all addressable at other layers.
69
+ - One mental model: consumers learn `Isa.with_bearer()` once and use the
70
+ same client for `isa.zyins.*`, `isa.rapidsign.*`, `isa.proxy.*`.
71
+ - Cross-product primitives (auth strategies, error hierarchy, envelope,
72
+ transport) live in one place and stay in sync.
73
+
74
+ ## Mechanical migration
75
+
76
+ For most projects, the migration is two find-and-replace operations:
77
+
78
+ ```bash
79
+ # In your project root:
80
+ grep -rl 'from isa_sdk.zyins' src/ | xargs sed -i '' \
81
+ 's/from isa_sdk\.zyins/from sah_sdk.zyins/g'
82
+
83
+ grep -rl 'isa_sdk\.zyins\.\(transport\|errors\|envelope\|debug\|auth\)' src/ | xargs sed -i '' \
84
+ -e 's/isa_sdk\.zyins\.transport/sah_sdk.core.transport/g' \
85
+ -e 's/isa_sdk\.zyins\.errors/sah_sdk.core.errors/g' \
86
+ -e 's/isa_sdk\.zyins\.envelope/sah_sdk.core.envelope/g' \
87
+ -e 's/isa_sdk\.zyins\.debug/sah_sdk.core.debug/g' \
88
+ -e 's/isa_sdk\.zyins\.auth/sah_sdk.core.auth/g'
89
+ ```
90
+
91
+ For projects with non-trivial imports (e.g. that re-export SDK symbols
92
+ from internal facades, or that use the SDK across many packages), use
93
+ the bundled `libcst`-based codemod:
94
+
95
+ ```bash
96
+ pip install 'sah-sdk[dev]'
97
+ python -m sah_sdk._codemod path/to/your/src
98
+ ```
99
+
100
+ The codemod is idempotent — running it twice on already-migrated code
101
+ is a no-op. It only rewrites known import patterns; ambiguous cases are
102
+ left alone with a diagnostic for human review.
103
+
104
+ ## Public surface (0.3.0)
105
+
106
+ ```python
107
+ from sah_sdk import (
108
+ Isa,
109
+ # Auth strategies (carry credentials; transport wires them later)
110
+ BearerAuth, LicenseAuth, SessionAuth,
111
+ # Errors
112
+ IsaApiError, IsaIdempotencyConflictError, IsaPermissionError,
113
+ IsaTransportError, IsaConfigError,
114
+ # Envelope and value types
115
+ Envelope, RawResponse, Money, Email, Url,
116
+ # Constants
117
+ Product, ProductLabels, UsState, ErrorCode,
118
+ )
119
+ ```
120
+
121
+ ## Pinning during transition
122
+
123
+ If you must support both during transition, pin to the legacy package
124
+ in one place and the new package in another, then migrate file by file:
125
+
126
+ ```toml
127
+ # pyproject.toml
128
+ dependencies = [
129
+ "isa-sdk-zyins==0.2.*; python_version < '3.10'", # legacy callers
130
+ "sah-sdk>=0.3.0", # everything else
131
+ ]
132
+ ```
133
+
134
+ The legacy package will receive security backports for six months past
135
+ the 0.3.0 GA date, then be archived.
136
+
137
+ ## Questions / issues
138
+
139
+ File an issue at <https://github.com/Software-Automation-Holdings-LLC/isa-platform/issues>
140
+ with the `sdk-python` label.
isa_sdk-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,328 @@
1
+ Metadata-Version: 2.4
2
+ Name: isa-sdk
3
+ Version: 1.0.0
4
+ Summary: Unified Python SDK for ISA APIs (zyins, rapidsign, proxy).
5
+ Project-URL: Homepage, https://github.com/Software-Automation-Holdings-LLC/isa-platform
6
+ Project-URL: Source, https://github.com/Software-Automation-Holdings-LLC/isa-platform
7
+ Author: Software Automation Holdings, LLC
8
+ License: Apache-2.0
9
+ Keywords: insurance,isa,rapidsign,sdk,zyins
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: cryptography>=42
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: libcst>=1.4
24
+ Requires-Dist: pydantic>=2.6
25
+ Provides-Extra: dev
26
+ Requires-Dist: build>=1.2; extra == 'dev'
27
+ Requires-Dist: libcst>=1.4; extra == 'dev'
28
+ Requires-Dist: mypy>=1.10; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Requires-Dist: respx>=0.21; extra == 'dev'
32
+ Requires-Dist: ruff>=0.5; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # sah-sdk
36
+
37
+ Python SDK for the [Best Plan Pro API](https://docs.isaapi.com) — powered by the ZyINS engine. Mirrors the
38
+ canonical TypeScript SDK at `packages/zyins/js/` with Python-idiomatic
39
+ naming (`snake_case`) and pydantic v2 models.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install sah-sdk
45
+ ```
46
+
47
+ > **GitHub Packages fallback.** Until `sah-sdk` is fully published on PyPI, install directly from the source repository:
48
+ >
49
+ > ```bash
50
+ > pip install "git+https://github.com/Software-Automation-Holdings-LLC/sdk.git@sdk/v0.5.0#subdirectory=packages/python"
51
+ > ```
52
+ >
53
+ > The wire surface is identical either way; only the install command changes.
54
+
55
+ ## Quick start
56
+
57
+ ```python
58
+ import os
59
+ from sah_sdk.zyins import Isa, Applicant, Coverage, PrequalifyInput, Sex
60
+
61
+ # Reads ISA_TOKEN from the environment — no explicit token needed.
62
+ isa = Isa.with_bearer()
63
+
64
+ result = isa.zyins.prequalify(PrequalifyInput(
65
+ applicant=Applicant(
66
+ dob="1962-04-18",
67
+ sex=Sex.MALE,
68
+ height_inches=70,
69
+ weight_pounds=195,
70
+ state="NC",
71
+ nicotine_use="none",
72
+ ),
73
+ coverage=Coverage.face_value(100_000),
74
+ products="colonial-penn.final-expense",
75
+ ))
76
+
77
+ for plan in result.data.plans:
78
+ print(plan.brand, plan.tier, plan.monthly_premium)
79
+ ```
80
+
81
+ `Isa.with_bearer()` reads `ISA_TOKEN` from the environment. `Authorization: Bearer <token>`,
82
+ `Idempotency-Key`, and the date-pinned `Version` header are set automatically.
83
+
84
+ ## First call in <15 lines
85
+
86
+ ```python
87
+ from sah_sdk.zyins import Isa, Coverage
88
+
89
+ isa = Isa.with_keycode(
90
+ keycode="SDV-HWH-WDD",
91
+ email="john.doe@acme-agency.com",
92
+ )
93
+ result = isa.zyins.prequalify_v2(
94
+ applicant={"dob": "1962-04-18", "sex": "male", "state": "NC"},
95
+ coverage=Coverage.face_value(25_000),
96
+ products="colonial-penn.final-expense",
97
+ )
98
+ print(result.data.plans[0].monthly_premium)
99
+ ```
100
+
101
+ ## Per-surface API versions
102
+
103
+ The ISA API is a federation of independently versioned surfaces. Every SDK
104
+ release exports a frozen `BUNDLED_API_VERSIONS` mapping recording which
105
+ `/vN` each surface targets:
106
+
107
+ ```python
108
+ from sah_sdk.zyins import BUNDLED_API_VERSIONS
109
+
110
+ print(BUNDLED_API_VERSIONS)
111
+ # {
112
+ # "prequalify": "v2",
113
+ # "quote": "v2",
114
+ # "datasets": "v2",
115
+ # "reference": "v2",
116
+ # "sessions": "v1",
117
+ # "branding": "v1",
118
+ # "cases": "v1",
119
+ # }
120
+ ```
121
+
122
+ Pin individual surfaces with a per-surface `api_version` map. There is **no**
123
+ `default` key and **no** string shorthand — resolution is
124
+ `api_version.get(surface, BUNDLED_API_VERSIONS[surface])`:
125
+
126
+ ```python
127
+ isa = await Isa.with_keycode(
128
+ keycode="SDV-HWH-WDD",
129
+ email="john.doe@acme-agency.com",
130
+ api_version={"quote": "v2"}, # pin only quote; everything else bundled
131
+ )
132
+ ```
133
+
134
+ The release that retargets `prequalify` / `quote` / `datasets` / `reference`
135
+ to `v3` will bump those entries. See [SDK syntax proposal §2.7][syntax-27].
136
+
137
+ [syntax-27]: ../../docs/sdk-syntax-proposal.md#27-versioning--per-surface-not-global
138
+
139
+ ## Reference data — `.match()`
140
+
141
+ The unversioned `isa.zyins.reference` namespace canonicalizes free-text
142
+ medication and condition input. Unknown text never rejects — it returns a
143
+ structured envelope so the final canonicalization fires server-side at
144
+ `/vN/prequalify`:
145
+
146
+ ```python
147
+ ds = await isa.zyins.datasets.get(include=["conditions", "medications"])
148
+
149
+ insulin = isa.zyins.medications.match("insulin")
150
+ print(insulin.id, insulin.name, insulin.is_known)
151
+ # med_01KSR2WVAGC05ZGR6FA4QYEB12 INSULIN True
152
+
153
+ # Symmetric traversal — what conditions is insulin used for?
154
+ used_for = insulin.conditions(isa.zyins.reference.Sort.MOST_COMMON_FIRST)
155
+ # frequency-ordered list; cond_01KSR2WVAGC05ZGR6FA4QYEA8X first
156
+
157
+ novel = isa.zyins.medications.match("NewExperimental XR 2026")
158
+ # → {"is_known": False, "input_text": "NewExperimental XR 2026", ...}
159
+ ```
160
+
161
+ `Sort.MOST_COMMON_FIRST` and `Sort.ALPHABETICAL` are the two supported
162
+ orderings.
163
+
164
+ ## Case storage — bring your own
165
+
166
+ `isa.zyins.cases.*` routes through a `CaseStorage` adapter. The default is
167
+ the zero-knowledge store — ISA's servers only hold ciphertext and an opaque
168
+ ID. To plug a carrier-controlled store, pass your adapter at construction:
169
+
170
+ ```python
171
+ isa = await Isa.with_keycode(
172
+ keycode=..., email=...,
173
+ case_storage=CarrierCaseStorage(), # optional; default = ZeroKnowledgeCaseStorage
174
+ )
175
+ ```
176
+
177
+ See [cases guide](https://docs.isaapi.com/docs/cases) for the full
178
+ bring-your-own pattern.
179
+
180
+ ## Auth deviation from the TS SDK
181
+
182
+ The TypeScript SDK in this monorepo still carries the pre-#286 HMAC device
183
+ signing surface (`AuthContext` with `licenseKey + orderId + email + deviceId`).
184
+ The Python SDK is built against the post-#286 wire contract: a single
185
+ bearer token (`isa_live_*` / `isa_test_*`) is the entire auth surface.
186
+ This is the intentional simplification called out in the platform's
187
+ `platform_v1_architecture` notes.
188
+
189
+ ## Surface
190
+
191
+ | TypeScript | Python |
192
+ | ----------------------------------------- | ----------------------------------- |
193
+ | `client.prequalify(req)` | `client.prequalify.run(input)` |
194
+ | `client.license.activate/deactivate/check`| `client.license.*` (mirrored) |
195
+ | `client.case.email(req)` | `client.case.email(input)` |
196
+ | (new) | `client.quote.run(input)` |
197
+ | (new) | `client.datasets.list/get` |
198
+ | (new) | `client.reference_data.get(kind)` |
199
+ | (new) | `client.usage.summary(period)` |
200
+
201
+ Errors mirror the TS hierarchy: `ISAError` (alias `ZyInsError`, also
202
+ exported as `IsaApiError`) → `LicenseError`, `PrequalifyError`,
203
+ `ValidationError`, `RateLimitError`, `AuthError`,
204
+ `IsaIdempotencyConflictError`.
205
+
206
+ ## `Isa` factory client
207
+
208
+ Per [SDK_DESIGN.md §3](https://github.com/Software-Automation-Holdings-LLC/isa-platform/blob/main/docs/SDK_DESIGN.md),
209
+ the recommended entry point is the `Isa` class with three named factories:
210
+
211
+ ```python
212
+ from sah_sdk.zyins import Isa
213
+
214
+ # Reads ISA_TOKEN from the environment.
215
+ isa = Isa.with_bearer()
216
+ env = isa.zyins.prequalify(req)
217
+ print(env.data, env.request_id, env.idempotency_key, env.retry_attempts)
218
+
219
+ # Or pass the token explicitly.
220
+ isa = Isa.with_bearer("isa_live_…")
221
+
222
+ # License factory — reads ISA_LICENSE_KEYCODE / ISA_LICENSE_EMAIL.
223
+ isa = Isa.with_license()
224
+
225
+ # Session factory — reads ISA_SESSION_ID / ISA_SESSION_SECRET.
226
+ isa = Isa.with_session()
227
+ ```
228
+
229
+ Each factory raises `IsaConfigError` with a clear, actionable message if
230
+ the required env vars are unset and no explicit arguments are supplied.
231
+
232
+ ### Raw HTTP access
233
+
234
+ Every method has a `.with_raw_response()` variant returning both the
235
+ parsed envelope and the underlying HTTP metadata:
236
+
237
+ ```python
238
+ env, raw = isa.zyins.prequalify.with_raw_response(req)
239
+ raw.status # int
240
+ raw.url # str
241
+ raw.headers # read-only mapping
242
+ ```
243
+
244
+ ### Debug logging
245
+
246
+ Set `ISA_LOG=debug` to dump every request and response to **stderr** —
247
+ never stdout, so parent processes piping the consumer's JSON output stay
248
+ clean. Credential headers (`Authorization`, `X-Device-Signature`,
249
+ `X-Session-Signature`) and PII body fields (`email`, `dob`, `ssn`,
250
+ `phone`) are redacted automatically.
251
+
252
+ ### Idempotency conflicts
253
+
254
+ When the same `Idempotency-Key` is replayed with a different body the
255
+ server returns 409 `idempotency_conflict`. The SDK raises
256
+ `IsaIdempotencyConflictError` with `.key` and `.first_seen_at` so the
257
+ caller can audit the queued-write bug class:
258
+
259
+ ```python
260
+ from sah_sdk.zyins import IsaIdempotencyConflictError
261
+
262
+ try:
263
+ isa.zyins.prequalify(req, idempotency_key="case-42")
264
+ except IsaIdempotencyConflictError as e:
265
+ log.error("key %s first seen at %s", e.key, e.first_seen_at)
266
+ ```
267
+
268
+ ## Concurrency
269
+
270
+ The `Isa` client is safe for use with `asyncio.gather` and
271
+ `concurrent.futures` — every request mints a fresh request-id and
272
+ idempotency key, and shared client state (auth, base URL, debug logger)
273
+ is read-only after construction. Reuse a single `Isa` instance across
274
+ all concurrent requests; the underlying HTTP transport pools connections
275
+ for you.
276
+
277
+ ```python
278
+ import asyncio
279
+ from sah_sdk.zyins import Isa
280
+
281
+ isa = Isa.with_bearer()
282
+
283
+ async def one(req):
284
+ return isa.zyins.prequalify(req)
285
+
286
+ results = await asyncio.gather(*(one(r) for r in batch))
287
+ # Each result.request_id is distinct.
288
+ ```
289
+
290
+ ## Development
291
+
292
+ ```bash
293
+ hatch run test # pytest
294
+ hatch run lint # ruff + mypy --strict
295
+ hatch build # wheel + sdist
296
+ ```
297
+
298
+ Live-integration tests run only when `ZYINS_TEST_TOKEN` is set:
299
+
300
+ ```bash
301
+ ZYINS_TEST_TOKEN=isa_test_... hatch run test -- -m integration
302
+ ```
303
+
304
+ ## Licenses and Ready
305
+
306
+ The Python SDK exposes the public BPP license-lifecycle surface and the
307
+ platform readiness probe on every `ZyInsClient`:
308
+
309
+ ```python
310
+ from sah_sdk.zyins import LicenseCheckInput, ZyInsClient
311
+
312
+ client = ZyInsClient("isa_live_...")
313
+
314
+ result = client.license.check(
315
+ LicenseCheckInput(
316
+ email="john.doe@acme-agency.com",
317
+ keycode="ABC-123-XYZ",
318
+ device_id="dev_01HZK2N5GQR9T8X4B6FJW3Y1AS",
319
+ )
320
+ )
321
+ # result.status: "valid" | "invalid" | "inactive"
322
+
323
+ ready = client.health.get_readiness()
324
+ # ready.ready: True on every required probe = "serving"
325
+ ```
326
+
327
+ The pre-existing `client.license` (singular) sub-client targets the
328
+ authenticated `/v1/license/*` self-status endpoints and is untouched.