feat(sp36): extract eligibility router
Move POST /api/eligibility/request and POST /api/eligibility/parse-271 from api.py to api_routers/eligibility.py, plus the _validate_eligibility_request single-router helper. Behaviour-preserving — endpoint URLs, params, status codes, response shapes, and 400/422/500 error envelopes are unchanged. The auth gate moves from per-route to router-level (semantically equivalent).
This commit is contained in:
@@ -65,25 +65,14 @@ def _actor_user_id(request: Request) -> int | None:
|
|||||||
return getattr(user, "id", None)
|
return getattr(user, "id", None)
|
||||||
from cyclone.parsers.exceptions import CycloneParseError
|
from cyclone.parsers.exceptions import CycloneParseError
|
||||||
from cyclone.parsers.models import BatchSummary, ClaimOutput, Envelope, ParseResult
|
from cyclone.parsers.models import BatchSummary, ClaimOutput, Envelope, ParseResult
|
||||||
from cyclone.parsers.models_270 import (
|
|
||||||
EligibilityBenefitInquiry,
|
|
||||||
InformationReceiver270,
|
|
||||||
InformationSource270,
|
|
||||||
ParseResult270,
|
|
||||||
Subscriber270,
|
|
||||||
)
|
|
||||||
from cyclone.parsers.models_271 import ParseResult271
|
|
||||||
from cyclone.parsers.models_835 import ParseResult835
|
from cyclone.parsers.models_835 import ParseResult835
|
||||||
from cyclone.parsers.payer import PayerConfig, PayerConfig835
|
from cyclone.parsers.payer import PayerConfig, PayerConfig835
|
||||||
from cyclone.parsers.parse_270 import parse as parse_270_text
|
|
||||||
from cyclone.parsers.parse_271 import parse as parse_271_text
|
|
||||||
from cyclone.parsers.parse_277ca import parse_277ca_text
|
from cyclone.parsers.parse_277ca import parse_277ca_text
|
||||||
from cyclone.parsers.parse_837 import parse
|
from cyclone.parsers.parse_837 import parse
|
||||||
from cyclone.parsers.parse_835 import parse as parse_835
|
from cyclone.parsers.parse_835 import parse as parse_835
|
||||||
from cyclone.parsers.parse_999 import parse_999_text
|
from cyclone.parsers.parse_999 import parse_999_text
|
||||||
from cyclone.parsers.parse_ta1 import parse_ta1_text
|
from cyclone.parsers.parse_ta1 import parse_ta1_text
|
||||||
from cyclone.parsers.segments import tokenize as _tokenize_segments
|
from cyclone.parsers.segments import tokenize as _tokenize_segments
|
||||||
from cyclone.parsers.serialize_270 import serialize_270
|
|
||||||
from cyclone.parsers.serialize_999 import serialize_999
|
from cyclone.parsers.serialize_999 import serialize_999
|
||||||
from cyclone.parsers.serialize_837 import SerializeError as SerializeError837, serialize_837, serialize_837_for_resubmit
|
from cyclone.parsers.serialize_837 import SerializeError as SerializeError837, serialize_837, serialize_837_for_resubmit
|
||||||
from cyclone.parsers.batch_ack_builder import build_ack_for_batch
|
from cyclone.parsers.batch_ack_builder import build_ack_for_batch
|
||||||
@@ -2827,193 +2816,6 @@ async def activity_stream(
|
|||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# 270 / 271 eligibility (SP3 P4 T23–T24) — API-only, no DB persistence
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_eligibility_request(body: dict) -> tuple[ParseResult270, str]:
|
|
||||||
"""Build a :class:`ParseResult270` from a request body dict.
|
|
||||||
|
|
||||||
The body shape is the minimum surface needed to build a valid 270
|
|
||||||
inquiry (per spec section 3.4 — operator-driven, ephemeral):
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
{
|
|
||||||
"subscriber": {first_name, last_name, member_id, dob},
|
|
||||||
"provider": {npi, name},
|
|
||||||
"payer": {id, name},
|
|
||||||
"service_type_code": "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
Returns ``(ParseResult270, service_type_code)``. Raises
|
|
||||||
:class:`HTTPException` (400) when the body is missing required
|
|
||||||
fields.
|
|
||||||
"""
|
|
||||||
subscriber_in = body.get("subscriber") or {}
|
|
||||||
provider_in = body.get("provider") or {}
|
|
||||||
payer_in = body.get("payer") or {}
|
|
||||||
service_type_code = (body.get("service_type_code") or "").strip()
|
|
||||||
|
|
||||||
# Required-field checks. We surface a single 400 with the first
|
|
||||||
# missing field name to match the rest of the API's error contract.
|
|
||||||
if not service_type_code:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail={"error": "Bad request", "detail": "service_type_code is required"},
|
|
||||||
)
|
|
||||||
if not subscriber_in.get("member_id"):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail={"error": "Bad request", "detail": "subscriber.member_id is required"},
|
|
||||||
)
|
|
||||||
if not provider_in.get("npi"):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail={"error": "Bad request", "detail": "provider.npi is required"},
|
|
||||||
)
|
|
||||||
if not payer_in.get("name"):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail={"error": "Bad request", "detail": "payer.name is required"},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Build the Pydantic models. The serializer handles all envelope
|
|
||||||
# generation (sender_id/receiver_id/control_number/transaction_date
|
|
||||||
# are filled in by the serializer with sensible defaults).
|
|
||||||
from datetime import date as _date
|
|
||||||
|
|
||||||
subscriber_dob_raw = subscriber_in.get("dob")
|
|
||||||
subscriber_dob: _date | None = None
|
|
||||||
if subscriber_dob_raw:
|
|
||||||
try:
|
|
||||||
subscriber_dob = _date.fromisoformat(subscriber_dob_raw)
|
|
||||||
except (TypeError, ValueError) as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail={
|
|
||||||
"error": "Bad request",
|
|
||||||
"detail": f"subscriber.dob must be YYYY-MM-DD: {exc}",
|
|
||||||
},
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
result = ParseResult270(
|
|
||||||
envelope=Envelope(
|
|
||||||
sender_id="SUBMITTERID",
|
|
||||||
receiver_id=str(payer_in.get("id") or "RECEIVERID"),
|
|
||||||
control_number="000000001",
|
|
||||||
transaction_date=_date.today(),
|
|
||||||
implementation_guide="005010X279A1",
|
|
||||||
),
|
|
||||||
information_source=InformationSource270(
|
|
||||||
name=str(payer_in["name"]),
|
|
||||||
id=str(payer_in.get("id") or "") or None,
|
|
||||||
),
|
|
||||||
information_receiver=InformationReceiver270(
|
|
||||||
name=str(provider_in.get("name") or ""),
|
|
||||||
npi=str(provider_in["npi"]),
|
|
||||||
),
|
|
||||||
subscriber=Subscriber270(
|
|
||||||
member_id=str(subscriber_in["member_id"]),
|
|
||||||
first_name=str(subscriber_in.get("first_name") or "") or None,
|
|
||||||
last_name=str(subscriber_in.get("last_name") or "") or None,
|
|
||||||
dob=subscriber_dob,
|
|
||||||
),
|
|
||||||
inquiries=[EligibilityBenefitInquiry(service_type_code=service_type_code)],
|
|
||||||
summary=BatchSummary(
|
|
||||||
input_file="eligibility_request",
|
|
||||||
control_number="000000001",
|
|
||||||
transaction_date=_date.today(),
|
|
||||||
total_claims=1,
|
|
||||||
passed=1,
|
|
||||||
failed=0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return result, service_type_code
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/eligibility/request", dependencies=[Depends(matrix_gate)])
|
|
||||||
def post_eligibility_request(body: dict) -> Any:
|
|
||||||
"""Build a 270 eligibility inquiry from a small JSON body.
|
|
||||||
|
|
||||||
Returns ``{"raw_270_text": <X12>, "parsed": <ParseResult270>}``
|
|
||||||
so the operator can either download the raw text (paste into a
|
|
||||||
payer portal) or render the parsed fields directly. Per spec
|
|
||||||
section 3.4, nothing is persisted to the DB.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
result, _ = _validate_eligibility_request(body)
|
|
||||||
except HTTPException:
|
|
||||||
raise
|
|
||||||
except (KeyError, TypeError, ValueError) as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail={"error": "Bad request", "detail": f"Malformed body: {exc}"},
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
raw_270_text = serialize_270(result)
|
|
||||||
return {
|
|
||||||
"raw_270_text": raw_270_text,
|
|
||||||
"parsed": json.loads(result.model_dump_json()),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/eligibility/parse-271", dependencies=[Depends(matrix_gate)])
|
|
||||||
async def post_eligibility_parse_271(
|
|
||||||
file: UploadFile = File(...),
|
|
||||||
) -> Any:
|
|
||||||
"""Parse a 271 eligibility response and return the structured summary.
|
|
||||||
|
|
||||||
Accepts the raw 271 text as a file upload (multipart/form-data),
|
|
||||||
mirrors the ``/api/parse-999`` contract. Per spec section 3.4 the
|
|
||||||
result is NOT persisted — the operator re-pastes the 271 each
|
|
||||||
time they need a fresh read.
|
|
||||||
|
|
||||||
The response body is a JSON object with three top-level keys:
|
|
||||||
``coverage_benefits``, ``subscriber``, and ``summary``. 400 is
|
|
||||||
returned on empty / undecodable / malformed EDI; 200 on success.
|
|
||||||
"""
|
|
||||||
raw = await file.read()
|
|
||||||
if not raw:
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=400,
|
|
||||||
content={"error": "Empty file", "detail": "Uploaded file contained no bytes."},
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
text = raw.decode("utf-8")
|
|
||||||
except UnicodeDecodeError as exc:
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=400,
|
|
||||||
content={"error": "Encoding error", "detail": str(exc)},
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = parse_271_text(text, input_file=file.filename or "")
|
|
||||||
except CycloneParseError as exc:
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=400,
|
|
||||||
content={"error": "Parse error", "detail": str(exc)},
|
|
||||||
)
|
|
||||||
except Exception as exc: # pragma: no cover - safety net
|
|
||||||
log.exception("Unexpected parser failure on 271")
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=500,
|
|
||||||
content={"error": "Internal server error", "detail": str(exc)},
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"coverage_benefits": [
|
|
||||||
json.loads(cb.model_dump_json()) for cb in result.coverage_benefits
|
|
||||||
],
|
|
||||||
"subscriber": json.loads(result.subscriber.model_dump_json()),
|
|
||||||
"summary": json.loads(result.summary.model_dump_json()),
|
|
||||||
"envelope": json.loads(result.envelope.model_dump_json()),
|
|
||||||
"information_source": json.loads(result.information_source.model_dump_json()),
|
|
||||||
"information_receiver": json.loads(result.information_receiver.model_dump_json()),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# SP9: providers / payers / clearhouse endpoints
|
# SP9: providers / payers / clearhouse endpoints
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from cyclone.api_routers import (
|
|||||||
admin,
|
admin,
|
||||||
claim_acks,
|
claim_acks,
|
||||||
dashboard,
|
dashboard,
|
||||||
|
eligibility,
|
||||||
health,
|
health,
|
||||||
ta1_acks,
|
ta1_acks,
|
||||||
)
|
)
|
||||||
@@ -24,6 +25,7 @@ routers: list[APIRouter] = [
|
|||||||
admin.router, # gated
|
admin.router, # gated
|
||||||
claim_acks.router, # gated
|
claim_acks.router, # gated
|
||||||
dashboard.router, # gated
|
dashboard.router, # gated
|
||||||
|
eligibility.router, # gated
|
||||||
health.router, # public — health probes must work pre-auth
|
health.router, # public — health probes must work pre-auth
|
||||||
ta1_acks.router, # gated
|
ta1_acks.router, # gated
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
"""``/api/eligibility/request`` and ``/api/eligibility/parse-271`` — API-only eligibility pair.
|
||||||
|
|
||||||
|
Builds a 270 inquiry from a small JSON body and parses a 271 response.
|
||||||
|
Nothing is persisted to the DB — these are operator-driven, ephemeral
|
||||||
|
operations per SP3 (P4 T23–T24). The 270 serializer pulls X12 from a
|
||||||
|
``ParseResult270`` Pydantic; the 271 parser builds the same structure
|
||||||
|
in reverse from the wire format.
|
||||||
|
|
||||||
|
Why these are not ``GET /api/eligibility/...``: the 270 build is
|
||||||
|
operator-initiated (pay-portal paste-back), so the inbound surface is
|
||||||
|
a JSON ``POST``. The 271 inbound is a multipart file upload — same
|
||||||
|
shape as ``/api/parse-999`` — so the file can be the actual 271 text
|
||||||
|
saved from the payer portal.
|
||||||
|
|
||||||
|
SP36 Task 5: this block moved here from ``api.py:2832`` (``270 / 271
|
||||||
|
eligibility`` divider).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import date as _date
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
from cyclone.auth.deps import matrix_gate
|
||||||
|
from cyclone.parsers.exceptions import CycloneParseError
|
||||||
|
from cyclone.parsers.models import BatchSummary, Envelope
|
||||||
|
from cyclone.parsers.models_270 import (
|
||||||
|
EligibilityBenefitInquiry,
|
||||||
|
InformationReceiver270,
|
||||||
|
InformationSource270,
|
||||||
|
ParseResult270,
|
||||||
|
Subscriber270,
|
||||||
|
)
|
||||||
|
from cyclone.parsers.parse_271 import parse as parse_271_text
|
||||||
|
from cyclone.parsers.serialize_270 import serialize_270
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
router = APIRouter(dependencies=[Depends(matrix_gate)])
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_eligibility_request(body: dict) -> tuple[ParseResult270, str]:
|
||||||
|
"""Build a :class:`ParseResult270` from a request body dict.
|
||||||
|
|
||||||
|
The body shape is the minimum surface needed to build a valid 270
|
||||||
|
inquiry (per spec section 3.4 — operator-driven, ephemeral):
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
"subscriber": {first_name, last_name, member_id, dob},
|
||||||
|
"provider": {npi, name},
|
||||||
|
"payer": {id, name},
|
||||||
|
"service_type_code": "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns ``(ParseResult270, service_type_code)``. Raises
|
||||||
|
:class:`HTTPException` (400) when the body is missing required
|
||||||
|
fields.
|
||||||
|
"""
|
||||||
|
subscriber_in = body.get("subscriber") or {}
|
||||||
|
provider_in = body.get("provider") or {}
|
||||||
|
payer_in = body.get("payer") or {}
|
||||||
|
service_type_code = (body.get("service_type_code") or "").strip()
|
||||||
|
|
||||||
|
# Required-field checks. We surface a single 400 with the first
|
||||||
|
# missing field name to match the rest of the API's error contract.
|
||||||
|
if not service_type_code:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail={"error": "Bad request", "detail": "service_type_code is required"},
|
||||||
|
)
|
||||||
|
if not subscriber_in.get("member_id"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail={"error": "Bad request", "detail": "subscriber.member_id is required"},
|
||||||
|
)
|
||||||
|
if not provider_in.get("npi"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail={"error": "Bad request", "detail": "provider.npi is required"},
|
||||||
|
)
|
||||||
|
if not payer_in.get("name"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail={"error": "Bad request", "detail": "payer.name is required"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build the Pydantic models. The serializer handles all envelope
|
||||||
|
# generation (sender_id/receiver_id/control_number/transaction_date
|
||||||
|
# are filled in by the serializer with sensible defaults).
|
||||||
|
subscriber_dob_raw = subscriber_in.get("dob")
|
||||||
|
subscriber_dob: _date | None = None
|
||||||
|
if subscriber_dob_raw:
|
||||||
|
try:
|
||||||
|
subscriber_dob = _date.fromisoformat(subscriber_dob_raw)
|
||||||
|
except (TypeError, ValueError) as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail={
|
||||||
|
"error": "Bad request",
|
||||||
|
"detail": f"subscriber.dob must be YYYY-MM-DD: {exc}",
|
||||||
|
},
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
result = ParseResult270(
|
||||||
|
envelope=Envelope(
|
||||||
|
sender_id="SUBMITTERID",
|
||||||
|
receiver_id=str(payer_in.get("id") or "RECEIVERID"),
|
||||||
|
control_number="000000001",
|
||||||
|
transaction_date=_date.today(),
|
||||||
|
implementation_guide="005010X279A1",
|
||||||
|
),
|
||||||
|
information_source=InformationSource270(
|
||||||
|
name=str(payer_in["name"]),
|
||||||
|
id=str(payer_in.get("id") or "") or None,
|
||||||
|
),
|
||||||
|
information_receiver=InformationReceiver270(
|
||||||
|
name=str(provider_in.get("name") or ""),
|
||||||
|
npi=str(provider_in["npi"]),
|
||||||
|
),
|
||||||
|
subscriber=Subscriber270(
|
||||||
|
member_id=str(subscriber_in["member_id"]),
|
||||||
|
first_name=str(subscriber_in.get("first_name") or "") or None,
|
||||||
|
last_name=str(subscriber_in.get("last_name") or "") or None,
|
||||||
|
dob=subscriber_dob,
|
||||||
|
),
|
||||||
|
inquiries=[EligibilityBenefitInquiry(service_type_code=service_type_code)],
|
||||||
|
summary=BatchSummary(
|
||||||
|
input_file="eligibility_request",
|
||||||
|
control_number="000000001",
|
||||||
|
transaction_date=_date.today(),
|
||||||
|
total_claims=1,
|
||||||
|
passed=1,
|
||||||
|
failed=0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return result, service_type_code
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/eligibility/request")
|
||||||
|
def post_eligibility_request(body: dict) -> Any:
|
||||||
|
"""Build a 270 eligibility inquiry from a small JSON body.
|
||||||
|
|
||||||
|
Returns ``{"raw_270_text": <X12>, "parsed": <ParseResult270>}``
|
||||||
|
so the operator can either download the raw text (paste into a
|
||||||
|
payer portal) or render the parsed fields directly. Per spec
|
||||||
|
section 3.4, nothing is persisted to the DB.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result, _ = _validate_eligibility_request(body)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except (KeyError, TypeError, ValueError) as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail={"error": "Bad request", "detail": f"Malformed body: {exc}"},
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
raw_270_text = serialize_270(result)
|
||||||
|
return {
|
||||||
|
"raw_270_text": raw_270_text,
|
||||||
|
"parsed": json.loads(result.model_dump_json()),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/eligibility/parse-271")
|
||||||
|
async def post_eligibility_parse_271(
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
) -> Any:
|
||||||
|
"""Parse a 271 eligibility response and return the structured summary.
|
||||||
|
|
||||||
|
Accepts the raw 271 text as a file upload (multipart/form-data),
|
||||||
|
mirrors the ``/api/parse-999`` contract. Per spec section 3.4 the
|
||||||
|
result is NOT persisted — the operator re-pastes the 271 each
|
||||||
|
time they need a fresh read.
|
||||||
|
|
||||||
|
The response body is a JSON object with three top-level keys:
|
||||||
|
``coverage_benefits``, ``subscriber``, and ``summary``. 400 is
|
||||||
|
returned on empty / undecodable / malformed EDI; 200 on success.
|
||||||
|
"""
|
||||||
|
raw = await file.read()
|
||||||
|
if not raw:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=400,
|
||||||
|
content={"error": "Empty file", "detail": "Uploaded file contained no bytes."},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
text = raw.decode("utf-8")
|
||||||
|
except UnicodeDecodeError as exc:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=400,
|
||||||
|
content={"error": "Encoding error", "detail": str(exc)},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = parse_271_text(text, input_file=file.filename or "")
|
||||||
|
except CycloneParseError as exc:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=400,
|
||||||
|
content={"error": "Parse error", "detail": str(exc)},
|
||||||
|
)
|
||||||
|
except Exception as exc: # pragma: no cover - safety net
|
||||||
|
log.exception("Unexpected parser failure on 271")
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=500,
|
||||||
|
content={"error": "Internal server error", "detail": str(exc)},
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"coverage_benefits": [
|
||||||
|
json.loads(cb.model_dump_json()) for cb in result.coverage_benefits
|
||||||
|
],
|
||||||
|
"subscriber": json.loads(result.subscriber.model_dump_json()),
|
||||||
|
"summary": json.loads(result.summary.model_dump_json()),
|
||||||
|
"envelope": json.loads(result.envelope.model_dump_json()),
|
||||||
|
"information_source": json.loads(result.information_source.model_dump_json()),
|
||||||
|
"information_receiver": json.loads(result.information_receiver.model_dump_json()),
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user