See local backends and typed outcomes for executable synthetic provider wiring and the complete exception/refusal tables.
Start with the local summarizer, the clinical NLI gate, and the synthetic walkthrough and recording script.
The clinical brief threat model maps Python, CLI, REST, MCP and OpenMedKit boundaries to existing tests and records the remaining application, platform and model obligations.
openmed.clinical.build_clinical_brief() composes the existing local clinical
guards into an immutable ClinicalBrief. It never treats a generated summary as
a diagnosis or as approval to act. Successful results still need human review.
Pass a note or a DeidentificationResult, a local summarizer alias, a built-in
profile (default bhc), and an explicit BriefContext. A raw note is first
de-identified locally. Missing runtime/model assets fail closed. Without a
reviewed context the result is refused; the composer never manufactures review
history or assumes that de-identification is evidence approval.
BriefContext contains the existing EvidencePacket, its content digest, one
BriefFact per reference, a calibrated local three-class NLI callback with its
NLIThresholds, and a local privacy detector. The callback must identify the
same calibration as its thresholds. Existing, independently qualified local
checkpoints or caller-supplied calibrated providers can satisfy this contract;
training or publishing a new OpenMed model is not a feature or SDK-release
prerequisite. This API does not invent a default artifact, manufacture calibrated
scores, or fall back to a heuristic. Missing evidence or providers still produce
typed refusal rather than unsafe output.
Before reviewing references, calculate brief_policy_fingerprint(text, facts, profile). This binds the review history to the exact de-identified text,
clinical axes and profile version. Create review transitions through the existing
review-state API only for work actually reviewed. Changed content or annotations
require a new review. The existing EvidencePacket and BriefContext remain
synthetic-only.
Reviewed-local evidence uses the separate opt-in admission contract below.
Plain-string generation retains the strict extraction path: each atomic claim must exactly equal one unique reviewed source span. An opt-in structured path also accepts paraphrases through explicit bindings (below). Ambiguous spans, missing profile fields, demographic evidence, contradictions and NLI abstentions are refused. Clinical axes come from reviewed evidence, never from generation.
A trusted local backend may implement generate_brief(evidence, *, mode) instead
of summarize(text, *, mode). It receives a tuple of BriefGenerationEvidence
records containing only reviewed de-identified spans, opaque reference_ids and
original half-open Unicode-scalar source offsets. It returns
BriefGenerationResult(claims, schema_version=1) from
openmed.clinical.summarize_backends:
For example, when the supplied synthetic evidence contains only the reviewed
span Symptoms improved after fluids., a deterministic test generator can
propose the following paraphrase. A complete brief must also satisfy its profile.
from openmed.clinical.summarize_backends import (
BriefGeneratedClaim,
BriefGenerationResult,
)
class SyntheticGenerator:
def generate_brief(self, evidence, *, mode):
# Contract illustration only; not a model or a clinical verifier.
return BriefGenerationResult((
BriefGeneratedClaim(
"Symptoms improved following fluid treatment.",
(evidence[0].reference_id,),
),
))The result admits 1–64 atomic claims and at most 8,192 UTF-8 output bytes. Each claim requires exactly one reviewed reference. Empty, invented, repeated or multiple references, overlapping reviewed spans, compound claims, unknown schema versions and free-form structured results refuse. There is no similarity search or confidence-based evidence inference. Multi-reference synthesis needs a future version that defines how reviewed clinical axes combine; v1 does not guess. Direct generation-contract validation also refuses malformed Unicode with a value-free error that retains no chained decoder exception.
Claim text is joined with one space; the composer computes output offsets and
maps the selected reference to original source offsets. Explicit bindings grant
only alignment: every claim still passes the assertion, temporal, experiencer,
calibrated NLI, citation and privacy gates. No threshold changes. Successful
audits add generation_contract and claim_bindings with reference digests;
legacy exact-match packets remain unchanged. Original identifiers are checked
against generated text, and the complete response is privacy-scanned.
The bundled audit and response schemas and service response model admit this optional metadata. Both fields must occur together, and the binding sequence must match the citation count and order. Unknown metadata is refused. Legacy HTTP responses retain their original fields without added null placeholders.
OpenMedKit's ClinicalBriefGeneration and ClinicalBriefGeneratedClaim use the
same v1 wire fields (schema_version, claims, text, reference_ids). Supply
boundGeneration and caller-authorized reviewedEvidence to
ClinicalBrief.validate alongside the packet from the trusted on-device
evidence/NLI evaluator. Native checks independently bind reference digests and
source/output offsets and require entailment verdicts. The caller must validate
atomicity and all reviewed clinical axes through that evaluator; model output
cannot authorize evidence. With no explicit bindings, native validation keeps
exact extraction. Built-in model adapters continue their existing contracts;
this change adds no trained artifact, cloud fallback or clinical-validation claim.
ReviewedLocalEvidence is a separate contract (kind=reviewed_local_evidence,
schema_version=1, provenance_class=reviewed_local). Its source is the exact
de-identified artifact whose spans will enter generation, not the original
patient record. reviewed_source_digest(text) hashes those UTF-8 bytes. Offsets
are half-open Unicode-scalar coordinates (unicode_scalar_half_open); Python
code-point and Swift Unicode-scalar counts match, including supplementary
characters. The packet contains one source digest and length, a policy digest,
1–64 offset-only references, and a nullable LocalReviewReceipt. Metadata
integers are non-negative signed 64-bit values on both platforms. Malformed
Unicode source strings produce a controlled invalid-evidence refusal without
retaining their encoding error or source content.
Source/reference/receipt/authority IDs have the form source:, ref:,
receipt:, or authority: followed by 64 lowercase hexadecimal characters.
Applications must mint opaque IDs, never encode names, paths or credentials.
Digests use sha256: plus 64 lowercase hexadecimal characters. Parsers reject
unknown keys, text payloads, duplicate reference IDs and invalid span boundaries.
to_dict(), to_json() and from_json() round-trip controlled metadata only.
Decoding or changing a provenance marker never constitutes admission.
A receipt contains opaque receipt and authority IDs, the packet's
evidence_digest, and UTC Unix issued_at/expires_at seconds. The digest binds
all version, provenance, source, policy, reference and offset fields, excluding
the receipt itself. The receipt is a locator, not a bearer credential or a
self-authenticating signature. The application must supply these narrow local
protocols through ReviewedLocalBriefContext:
CurrentLocalSource.current_digest(source_id)authorizes source access and returns the current de-identified source digest, orNone.ReviewAuthorityVerifier.verify(receipt, evidence_digest=..., now=...)resolves an independently held review record, verifies the entire receipt and caller/reviewer authority, and checks current revocation. It returnsReviewAuthorityStatus.CURRENT,REVOKED, orMISMATCHED. Booleans and caller-controlled approval markers are refused. No default authority exists.
The context also supplies the ordinary content digest, reviewed BriefFacts,
calibrated NLI and privacy callbacks, plus an optional trusted clock. Recompute
brief_policy_fingerprint(text, facts, profile) and review the new packet digest
in the application's review store. Never manufacture an approval merely to make
an example pass. A minimal application composition, after actual local review,
is:
from openmed.clinical import ReviewedLocalBriefContext, build_clinical_brief
context = ReviewedLocalBriefContext(
packet=reviewed_packet, # loaded from authorized local custody
content_digest=reviewed_content_digest,
facts=reviewed_facts,
nli_predict=local_calibrated_nli,
thresholds=local_thresholds,
privacy_detector=local_privacy_detector,
source=authorized_source_store,
authority=local_review_registry,
)
brief = build_clinical_brief(deidentified_result, model="extractive", context=context)
# Protected response: brief.to_response(); value-free audit: brief.to_dict().admit_reviewed_local_evidence() checks source/policy bindings, receipt existence,
full digest binding, issuance/expiry, current source custody and review authority.
After source and review callbacks return, the trusted clock is read again:
expired receipts, invalid clocks and time rollback fail before generation.
The brief calls it at the evidence stage and again immediately before generation;
an admitted record is not a reusable authorization token. Local stores must
provide consistent reads and enforce their own concurrent-update/access policy.
Only admitted spans enter the generator; all existing NLI, citation and privacy
guards still run. Successful audit metrics.reviewed_evidence contains only the
value-free contract, and the generated output still requires human review.
Refusals are typed codes: invalid_reviewed_evidence, review_receipt_missing,
review_receipt_expired, review_receipt_mismatched, review_receipt_revoked,
review_authority_unavailable, review_source_unavailable,
review_source_changed, and review_policy_changed. A receipt with a future
issuance time is mismatched; expiry is inclusive (now >= expires_at). Store and
verifier exceptions are discarded without copying their messages or chains.
Changed axes/profile bindings require fresh review. Unavailable stores fail
closed before generation. Ordinary synthetic behavior and defaults are retained.
OpenMedKit exposes the same v1 metadata, digest and offset convention through
ReviewedLocalEvidence.fromJSON, toJSON, and admit. The
ClinicalBrief.reviewedLocal adapter admits and rechecks before calling a supplied
on-device generator over reviewed spans, then validates the supplied complete
local evidence/NLI evaluation through the existing native guards. Its current
policy digest must be recomputed by the trusted local evaluator; the SDK does
not mint that policy or review. This adds no inference backend, reviewer UI,
training prerequisite, cloud fallback, or autonomous clinical action. Both
platforms' fixtures are wholly synthetic even though they exercise the
reviewed_local provenance class; they do not qualify real clinical use.
ReviewedLocalEvidence is a separate contract (kind=reviewed_local_evidence,
schema_version=1, provenance_class=reviewed_local). Its source is the exact
de-identified artifact whose spans will enter generation, not the original
patient record. reviewed_source_digest(text) hashes those UTF-8 bytes. Offsets
are half-open Unicode-scalar coordinates (unicode_scalar_half_open); Python
code-point and Swift Unicode-scalar counts match, including supplementary
characters. The packet contains one source digest and length, a policy digest,
1–64 offset-only references, and a nullable LocalReviewReceipt. Metadata
integers are non-negative signed 64-bit values on both platforms. Malformed
Unicode source strings produce a controlled invalid-evidence refusal without
retaining their encoding error or source content.
Source/reference/receipt/authority IDs have the form source:, ref:,
receipt:, or authority: followed by 64 lowercase hexadecimal characters.
Applications must mint opaque IDs, never encode names, paths or credentials.
Digests use sha256: plus 64 lowercase hexadecimal characters. Parsers reject
unknown keys, text payloads, duplicate reference IDs and invalid span boundaries.
to_dict(), to_json() and from_json() round-trip controlled metadata only.
Decoding or changing a provenance marker never constitutes admission.
A receipt contains opaque receipt and authority IDs, the packet's
evidence_digest, and UTC Unix issued_at/expires_at seconds. The digest binds
all version, provenance, source, policy, reference and offset fields, excluding
the receipt itself. The receipt is a locator, not a bearer credential or a
self-authenticating signature. The application must supply these narrow local
protocols through ReviewedLocalBriefContext:
CurrentLocalSource.current_digest(source_id)authorizes source access and returns the current de-identified source digest, orNone.ReviewAuthorityVerifier.verify(receipt, evidence_digest=..., now=...)resolves an independently held review record, verifies the entire receipt and caller/reviewer authority, and checks current revocation. It returnsReviewAuthorityStatus.CURRENT,REVOKED, orMISMATCHED. Booleans and caller-controlled approval markers are refused. No default authority exists.
The context also supplies the ordinary content digest, reviewed BriefFacts,
calibrated NLI and privacy callbacks, plus an optional trusted clock. Recompute
brief_policy_fingerprint(text, facts, profile) and review the new packet digest
in the application's review store. Never manufacture an approval merely to make
an example pass. A minimal application composition, after actual local review,
is:
from openmed.clinical import ReviewedLocalBriefContext, build_clinical_brief
context = ReviewedLocalBriefContext(
packet=reviewed_packet, # loaded from authorized local custody
content_digest=reviewed_content_digest,
facts=reviewed_facts,
nli_predict=local_calibrated_nli,
thresholds=local_thresholds,
privacy_detector=local_privacy_detector,
source=authorized_source_store,
authority=local_review_registry,
)
brief = build_clinical_brief(deidentified_result, model="extractive", context=context)
# Protected response: brief.to_response(); value-free audit: brief.to_dict().admit_reviewed_local_evidence() checks source/policy bindings, receipt existence,
full digest binding, issuance/expiry, current source custody and review authority.
After source and review callbacks return, the trusted clock is read again:
expired receipts, invalid clocks and time rollback fail before generation.
The brief calls it at the evidence stage and again immediately before generation;
an admitted record is not a reusable authorization token. Local stores must
provide consistent reads and enforce their own concurrent-update/access policy.
Only admitted spans enter the generator; all existing NLI, citation and privacy
guards still run. Successful audit metrics.reviewed_evidence contains only the
value-free contract, and the generated output still requires human review.
Refusals are typed codes: invalid_reviewed_evidence, review_receipt_missing,
review_receipt_expired, review_receipt_mismatched, review_receipt_revoked,
review_authority_unavailable, review_source_unavailable,
review_source_changed, and review_policy_changed. A receipt with a future
issuance time is mismatched; expiry is inclusive (now >= expires_at). Store and
verifier exceptions are discarded without copying their messages or chains.
Changed axes/profile bindings require fresh review. Unavailable stores fail
closed before generation. Ordinary synthetic behavior and defaults are retained.
OpenMedKit exposes the same v1 metadata, digest and offset convention through
ReviewedLocalEvidence.fromJSON, toJSON, and admit. The
ClinicalBrief.reviewedLocal adapter admits and rechecks before calling a supplied
on-device generator over reviewed spans, then validates the supplied complete
local evidence/NLI evaluation through the existing native guards. Its current
policy digest must be recomputed by the trusted local evaluator; the SDK does
not mint that policy or review. This adds no inference backend, reviewer UI,
training prerequisite, cloud fallback, or autonomous clinical action. Both
platforms' fixtures are wholly synthetic even though they exercise the
reviewed_local provenance class; they do not qualify real clinical use.
The stage order is not configurable: de-identification, sections, evidence, summary-input contract, section plan, length budget, profile, generation, safety envelope, demographic minimization, atomic claims, assertion/temporal/experiencer pairs, calibrated NLI, citation consistency/boundaries/minimality, temporal order, unsupported claims, coverage, citation support, empty-evidence guard, privacy, review packet and provenance. Empty evidence is also rejected before generation.
Only reviewed spans are sent to the generator. Temporal uncertainty is retained for human review; document order is not a claim that events occurred in that order. Deterministic citation-span checks do not imply clinician adjudication. The summary and the assembled protected response both pass the configured privacy detector. Python outbound sockets are blocked; trusted callbacks are not isolated plugins and must not invoke external processes/services themselves.
The shared source-surface leakage guard runs before the
caller-supplied detector. It checks supported Unicode variants, unspaced script
surfaces and recognized Hangul suffixes. A match refuses with privacy, without
returning a partial summary. Inflected names outside those rules still require
the separate detector and human review.
refusal_reason is a BriefRefusal enum. Refusals contain no partial summary.
Exceptions from backends are not copied into reports or chained into public
errors. The stages trace records entered stages; on refusal the last stage
identifies the failure boundary, not a claim that the stage passed.
The complete refusal vocabulary, including reviewed-local authority and
interruption codes, is listed in Local backends.
Missing local runtime packages, unknown local aliases and
uncached artifacts use model_unavailable; unexpected execution failures remain
stage_failed. Remote providers remain prohibited. CLI, REST, MCP and clients
preserve these codes without exception messages, package details or private paths.
OpenMedKit reports model_unavailable for an unloaded or unavailable model.
The immutable brief-profile-evidence-classes-v1 mapping assigns reviewed
profile fields to the existing clinical_summary_v1 policy classes. Admission,
diagnoses, active problems and assessment share active_problems; background,
current situation, discharge condition, course and key findings share
key_findings. Discharge medications, procedures, pending items, follow-up/plan,
and safety concerns map to their matching classes. Unknown fields are refused.
utf8-byte-upper-bound-v1 estimates demand as UTF-8 bytes plus one joining
separator before every span except the first, charged to the following span's
class. It is deliberately conservative, especially for
non-ASCII text; it is not a measured backend token count. Class caps are 192 for
active problems, 160 for findings, 128 each for medications and safety, and 96
each for procedures, pending items and follow-up, under the global 2,048 budget.
Deferred classes yield length_budget_exceeded at the length-budget stage before
generation. No policy cap is silently relaxed. The mapping, estimator identity
and counts-only budget are recorded in metrics.length_budget, bound into the
brief audit digest as budget provenance; source spans and tokenizer output are
never recorded there.
brief.summaryandbrief.to_response()are protected application output. Do not log or persist them without an explicit application privacy boundary.brief.to_dict()omits all source and summary text and returns aggregate metrics, offsets, digests, fixed reasons, review packet and provenance.- The deterministic
digestbinds the safe record, including the summary digest. - The result never marks clinician review approved, even when all checks pass.
The synthetic unit fixtures use explicitly labelled test-only NLI scores and privacy detectors. They verify orchestration and refusal behavior, not model quality, clinical validation or a release benchmark.
The bundled brief audit and response schemas admit the optional, closed
metrics.reviewed_evidence v1 metadata. Runtime record validation also checks its
bounded offsets, unique references and receipt time ordering. This describes an
audit record; it does not replace current source custody or independent review
authority. The original synthetic evidence-packet schema remains unchanged.
The bundled Draft 2020-12 files in openmed/core/schemas/json/ describe the
brief audit and protected response, evidence packet, NLI verification list and
SDOH evidence report. openmed.clinical.record_schemas exports and fingerprints
these records without loading a runtime or contacting a service. Optional
validate_clinical_record(name, record) validation requires jsonschema and
reports controlled schema names and field locations without submitted values.
The audit schemas reject nested source and summary text; the response schema
retains its explicitly protected summary field. Schema validation does not
prove that a supplied record came from the producer, that its digests are
correct, or that clinical review has occurred. Record versions and Python
producer behavior remain unchanged.
openmed brief note.txt --model extractive --profile bhc --review-id <digest> --context-factory my_application.review:provider --summary-output summary.txt --review-output review.json writes separate, newly created mode-0600 files. It
never overwrites existing destinations. Exit 1 means refusal; stdout contains
only counts, status and a digest. The installed factory is trusted local Python
configuration, not an upload or a server request parameter. It returns a callable
(original_text, review_id) -> (DeidentificationResult, context) where context
is a BriefContext or opt-in ReviewedLocalBriefContext backed by
the application's existing review and access-control store. Do not use this
mechanism to invent review histories or accept untrusted executable modules.
POST /brief, Python OpenMedClient.brief(text, model="extractive", review_id=...), TypeScript client.brief({text, model: "extractive", review_id}), and the read-only MCP openmed_brief tool share the protected
response. Models are restricted to local aliases; remote URLs, approval records
and NLI scores are not accepted in requests. The opaque review reference is 64
lowercase hexadecimal characters. Configure REST with
app.state.brief_context_provider or MCP with the local runtime's
brief_context_provider; that provider must authorize access for the caller.
Missing review configuration returns a typed refusal, not an unguarded summary.
REST access logs add only bounded outcome vocabulary and counts to the normal
request duration. Never log the response: summary is protected content.
build_clinical_brief(..., cancellation=...) accepts an optional caller-owned
BriefCancellation, exported from openmed.clinical. Existing calls without a
context preserve their result and digest. For a whole-request deadline, start the
existing extraction budget clock before review lookup:
from openmed.clinical import BriefCancellation, build_clinical_brief
from openmed.core.budget import RequestBudget
cancellation = BriefCancellation(RequestBudget(max_wall_time=10).start())
# An application thread may call cancellation.cancel() at any time.
brief = build_clinical_brief(
deidentified, model="extractive", context=reviewed_context,
cancellation=cancellation,
)All fixed pipeline stages check the context before entering. Generation, each NLI
call, both privacy scans and the final publication boundary also check after work.
A trusted local summarizer, NLI callback, privacy detector or review lookup can
explicitly accept the keyword cancellation and call cancellation.check() inside
its own work. Legacy signatures receive no extra keyword. No stage can be removed
or reordered by the context. The clock uses the existing BudgetClock wall-time
semantics (elapsed time greater than the allowance); no second extraction budget
or wire-supplied evidence policy is introduced.
Caller cancellation and KeyboardInterrupt/asyncio.CancelledError produce
refusal_reason="cancelled". Expiry produces "deadline_exceeded"; ordinary
backend failures retain "stage_failed". Explicit cancellation wins when both
are observed at the same checkpoint. Observed expiry stays terminal. Interrupted
results are refused with an empty summary, citations, verdicts, metrics and
provenance. Only entered stage names and fixed diagnostics survive; no partial
protected output is returned as success.
brief_response(..., cancellation=...) covers application-owned review lookup
as well as composition. The REST route cancels its context when its awaiting task
is cancelled. A disconnected client may be unable to receive the refusal. This
is not a general job scheduler or HTTP disconnect monitor. The CLI accepts
--timeout-seconds, maps interruption to the same empty response in its JSON
data envelope and exits 1. It creates no output files for an interrupted request
and removes files it reserved if interrupted during output writing.
A synchronous provider, including the current Python MLX generate call, may not
cooperate while blocked or generating. Cancellation cannot kill that call or
promise immediate memory reclamation. Its returned output or exception is checked
before any next stage or publication; late output is discarded. The REST worker
may finish later but cannot publish its result through the cancelled request.
The Python MLX adapter drops the model and tokenizer references it owns on every
terminal path after loading. Caller-owned providers, native loaded model actors
and runtime-wide caches remain application-owned; no global cache flush is used.
Providers must release their own transient resources with finally/defer. They
must not persist, log or externally publish intermediate output themselves.
OpenMedKit uses ClinicalBriefCancellation: native task cancellation maps to
ClinicalBriefError.cancelled; an application-supplied, thread-safe
deadlineExpired closure (using its existing local clock) maps to
.deadlineExceeded. ClinicalBrief.compose and the Maple brief adapter guard
generation, local evaluation, rendered-packet verification and publication.
Evaluators may capture the context for checks inside their own evidence/NLI
stages. No mobile lifecycle policy or Apple Foundation Models cloud fallback is
added. All successful briefs still require qualified human review.
Structured generate_brief backends receive the same request context when their
signature explicitly declares cancellation. Late structured results are
discarded too. Native ClinicalBrief.reviewedLocal accepts the same deadline
context and checks admission callbacks, generation, evaluation and publication;
cancelled generation cannot reach the evaluator. Both bundled brief record
schemas admit the controlled cancelled and deadline_exceeded refusal codes.
LocalBriefContextProvider in openmed.clinical.brief_context adapts existing
Python Journey ClinicalFact and EvidenceLocator records into BriefContext.
It implements the callable used by all four Python/CLI/REST/MCP seams above.
This issue's adapter targets those Python extraction and transport contracts;
the existing native brief packet contract is unchanged.
Two narrow application-owned protocols keep extraction, access control and review authority separate from context construction:
BriefExtractionProvider.resolve(text, review_id)authorizes the caller and resolves the current protectedBriefExtraction, or returnsNone. Include the complete selected facts, locators, mappings and known conflicts.BriefReviewVerifier.verify(review_id, plan)checks an existing explicit receipt, including current authorization, validity and revocation. Return(stored_binding_digest, approved_evidence_packet)orNone. Never construct approval transitions merely because the provider requested verification.
BriefExtraction contains a DeidentificationResult, artifact/source identities,
facts and locators, and one BriefFactMapping(fact_id, profile_field, temporality)
per selected fact. Set synthetic=True only for synthetic test evidence. The
adapter preserves the existing synthetic-only packet boundary; reviewed-local
admission is separate work in #3651. It does not certify that caller-labelled
data is synthetic and is not a production evidence/review store.
Assertion, certainty and experiencer are read from the existing normalized fact
attributes (assertion, certainty, experiencer). Temporality is explicitly
declared in the mapping and reviewed; lifecycle status and dates do not imply a
temporal axis. Missing/unknown axes or field states refuse instead of becoming
negative facts. Unsupported axes, profile fields and demographic facts refuse.
There is no inferred fact type-to-profile table or heuristic axis fallback.
Each fact must have exactly one untransformed text_span locator in the selected
de-identified artifact. Offsets are half-open Python character positions. The
span must fit within one detected section. Missing locators, mixed subjects or
encounters, duplicate/shared/overlapping spans, duplicate source claims and
non-text/transformed locators fail closed. Original-source offsets require an
upstream mapping and renewed review; the adapter never guesses a redaction shift.
An open conflict involving selected facts blocks construction.
Before review, use plan_brief_context(extraction, profile="bhc", thresholds=thresholds) to get the unapproved plan. Its binding.digest binds
the original and de-identified sources, artifact/source identities,
de-identification method/mapping/entities, complete fact and locator records,
explicit field/temporal mappings, conflicts, versioned profile and full calibrated
threshold policy. Source/reference identifiers in the plan are synthetic-prefixed
digests rather than copied record identifiers. The existing packet policy binds
content and mapped axes; the separate receipt binding also covers full fact and
calibration identities. Changed evidence requires a new explicit receipt. The
adapter compares the stored binding and exact packet spans, validates approval
history and rechecks the plan after verification. It creates no approval itself.
from openmed.clinical.brief_context import LocalBriefContextProvider
# All dependencies below belong to the trusted local application.
provider = LocalBriefContextProvider(
extraction_provider=authorized_extraction_store,
review_verifier=independent_receipt_store,
thresholds=reviewed_calibration,
nli_predict=calibrated_local_predictor,
privacy_detector=local_privacy_detector,
profile="bhc",
)
app.state.brief_context_provider = provider # REST
# Also return provider from a CLI context factory, or inject it into MCP runtime.
outcome = provider.build(original_text, opaque_review_id)
safe_diagnostics = outcome.to_dict() # controlled code and binding digest onlybuild returns BriefContextOutcome with a BriefContextCode: ready,
missing_facts, missing_mapping, conflicted_facts, unsupported_mapping,
unknown_fact, missing_source, ambiguous_source, invalid_offsets,
review_required, evidence_changed, invalid_evidence or
provider_unavailable. Its artifact/context are protected application values;
only to_dict() is a diagnostic serialization. The callable raises a value-free
BriefContextError on refusal; existing transports convert failed lookups into
their existing invalid_evidence refusal, with no summary or generation.
No model is downloaded or initialized to construct a context. Python outbound
sockets are blocked during provider calls, as during composition. Trusted
callbacks must not launch external network clients or log source text. A context
with code="ready" admits evidence to the guarded composer; a successful brief
still has status="needs_review". Synthetic callbacks and fixtures establish
contract behavior only, not clinical validation or model qualification.
openmed.clinical.exporters.fhir.export_brief_document() projects a successful
brief into the closed openmed.clinical.brief.fhir-r4.v1 subset. It runs offline
without a transport, SMART credentials, models or EHR writes. Refused results,
missing or changed provenance, and any requested finalization are rejected.
The current ClinicalBrief result always requires review: approved source
evidence does not approve the generated document.
from datetime import datetime, timezone
from openmed.clinical.exporters.fhir import (
export_brief_document,
import_brief_document,
)
# brief is an existing successful ClinicalBrief. local_detector must be a
# configured local leakage detector; an empty test detector is not a PHI guard.
document = export_brief_document(
brief,
recorded_at=datetime.now(timezone.utc),
privacy_detector=local_detector,
original_identifiers=original_identifier_tokens,
)
audit = document.to_dict() # codes, counts, offsets/digests; no narrative
protected = document.to_response() # protected narrative; do not log
restored = import_brief_document(
protected,
privacy_detector=local_detector,
original_identifiers=original_identifier_tokens,
)
assert restored.to_response() == protectedThe response has bundle, document_reference and document_url:
- The document Bundle includes a preliminary Composition first, its local Device author, and one evidence DocumentReference per cited claim. Every Composition reference resolves inside the Bundle. The Bundle has a deterministic identifier bound to the projected metadata and explicit UTC export time.
- Composition narrative preserves the summary. Claim sections follow citation order and retain Unicode-scalar output/source offsets in the controlled metadata extension. A final Limitations section preserves the human-review disclaimer.
- Evidence attachments contain only
urn:sha256commitments, fixed titles and media types. They contain no source payloads, private paths, original identifiers or remote locations. Commitments cannot retrieve source text; applications must resolve evidence through their own authorized local evidence store. - The separate document DocumentReference is
currentwithdocStatus=preliminary. Its attachment URL equalsdocument_url, an opaque local identifier for the Bundle in this response. No bytes are uploaded or external URLs dereferenced. - The controlled extension preserves the brief, summary, source, provenance and policy digests, evidence identifiers/hashes, citations, queued review status and conversion-loss codes. It cannot attest approval or reconstruct a ClinicalBrief.
Conversion losses explicitly report unavailable profile section names and omitted source payloads, evaluation metrics, detailed review packets and model details. The brief contract does not expose profile section boundaries, so the exporter preserves claim order without inventing profile headings or clinical context. Dates are explicit export times, never inferred patient dates. No patient identity or clinical attester is synthesized.
This subset follows the R4 Composition,
DocumentReference and
document Bundle structures; it does not
claim full R4, IPS, US Core or national implementation-guide conformance.
import_brief_document() is the subset validator: it rejects unknown fields,
altered narrative, unsafe status, attachment changes, external/dangling references,
write requests and changed section order. Imported commitments are structural
evidence links, not authentication of an external author or validation against
an unavailable source store.
Both directions require the configured detector on decoded string surfaces and
the assembled document, including extensions and attachment metadata. All
findings block output. Original identifier tokens are also prohibited across
those surfaces. BriefDocumentError contains only a controlled code and never
chains callback exceptions. repr(document) and to_dict() omit narrative;
only explicit to_response() returns it. These gates supplement upstream
de-identification and do not establish model quality or authorize clinical use.
OpenMedKit mirrors this subset with ClinicalBriefFHIRDocument.export(brief, recordedAt:date, originalIdentifiers:tokens, privacyCheck:localCheck) and
ClinicalBriefFHIRDocument.validate(documentJSON:data, originalIdentifiers:tokens, privacyCheck:localCheck). responseJSON() is
protected output; auditJSON() and the description are value-free. A shared
synthetic fixture checks Python/Swift byte-identical export and round trips.
Native callbacks are trusted on-device application code; no remote fallback or
EHR transport is provided.
The version-1 synthetic corpus in
tests/fixtures/eval/summaries/multilingual_briefs.json covers English and French
Latin text, Arabic RTL text, Hindi combining marks, and English/Hindi code
switching. It reuses the existing per-language identifier traps and language-pack
registry. Fixed local generation, NLI and privacy providers exercise the complete
composer and CLI, REST, Python client and MCP adapters offline. Shared packets
are recomputed by the public Python composer and validated by OpenMedKit; native
citation offsets use Unicode scalars, matching Python code points, rather than
Swift Characters or UTF-16 units.
The fixture preparation explicitly normalizes with normalize_for_detection()
(NFKC and digit folding), retains its original offset map, and gold-masks the
synthetic identifier. Citation tests project each post-mask source span through
the de-identification map and then the normalization map back to raw source;
normalizing that raw slice reproduces the exact cited claim. Every interior
replacement boundary is rejected. Normalization is an upstream preparation step,
not an implicit composer transformation. Gold masking does not measure identifier
detection recall. Populated replacement aliases must agree; an unset optional
PIIEntity.surrogate does not conflict with its redacted_text.
Preserved negated, family and historical facts retain their reviewed axes and
require human review. Generated negation, family-to-patient or temporal changes
are refused by exact-source alignment as unsupported_claim; the suite does not
claim semantic conflict detection for paraphrases. Fixed NLI contradictions yield
nli_rejected, and a provider without declared fixture-pair support yields
nli_unavailable. Refusals contain no partial summary or citations. Changed
source digests or annotations invalidate the evidence identity and review.
The regression report separates contract_parity from model_quality, which is
always not_evaluated. It contains only counts, digests and controlled codes.
clinical_language_support remains not_established, even if a packet passes.
A language pack or surrogate locale does not establish summarizer, NLI or
atomicity support. The composer currently has no language argument and uses the
English claim policy; explicit non-English calls to segment_summary_claims()
remain unsupported_language. The report records that unsupported atomicity
separately from synthetic transport parity. No model weights, clinical benchmark,
cloud fallback or release qualification are added.
Run the focused offline slice with:
.venv/bin/python -m pytest tests/unit/clinical/test_multilingual_brief.py tests/integration/test_multilingual_clinical_brief.py tests/unit/clinical/test_citation_boundaries.py -q
cd swift/OpenMedKit && swift test