{
  "openapi": "3.1.0",
  "info": {
    "title": "Hoopcare Scoring API — proposed contract",
    "version": "0.2.0",
    "summary": "Illustrative, sanitized contract for the locally deployed Hoopcare scoring engine.",
    "description": "PREVIEW / PROPOSED CONTRACT — v0.2.0. Describes the intended shape of the Hoopcare scoring API for evaluation purposes. Not a production interface: paths, schemas, and semantics may change until a release compatibility matrix pins them. All examples are synthetic; no PHI. Errors follow RFC 9457 (application/problem+json) and must never contain PHI, echoed input values, or stack traces — at any nesting level. Authorization: OAuth2 client credentials with per-operation, resource-specific scopes (see each operation's security requirement); mTLS may be required in addition per deployment policy (see the mutualTLS scheme). Object authorization is tenant-scoped: a token valid for jobs.read cannot read another tenant's jobId. Idempotency-Key values are retained for 24 hours; replay after expiry re-executes the request. Batch work runs under per-tenant concurrency and daily-volume quotas. Immutable copy of this version: /contracts/scoring-api/0.2.0/openapi.json (sha256 alongside). Contact: contact@hoopcare.com.",
    "contact": { "name": "Hoopcare", "email": "contact@hoopcare.com", "url": "https://ai.hoopcare.com/documentation" },
    "x-status": "proposed",
    "x-evidence": "Model performance figures referenced by this API are preliminary internal development estimates from an unlocked analysis, pending locked-cohort analysis and partner authorization."
  },
  "servers": [
    { "url": "https://hoopcare.internal.example-hospital.org", "description": "Customer-deployed instance (closed topology) — hostname is site-specific; there is no Hoopcare-hosted production endpoint in this preview." }
  ],
  "tags": [
    { "name": "scoring", "description": "Score preoperative case records" },
    { "name": "jobs", "description": "Batch job lifecycle" },
    { "name": "models", "description": "Licensed model registry and cards" },
    { "name": "ops", "description": "Health and metrics (network-restricted; authentication per deployment policy)" }
  ],
  "paths": {
    "/v1/score": {
      "post": {
        "tags": ["scoring"],
        "operationId": "scoreCase",
        "summary": "Score one CaseRecord against licensed models",
        "security": [ { "oauth2ClientCredentials": ["hoopcare/score.write"] } ],
        "parameters": [
          { "$ref": "#/components/parameters/IdempotencyKey" },
          { "$ref": "#/components/parameters/CorrelationId" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScoreRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Scored (individual results may still be degraded/abstained — inspect per-result status and status_reasons)",
            "headers": { "X-Correlation-Id": { "$ref": "#/components/headers/XCorrelationId" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScoreResponse" } } }
          },
          "400": { "$ref": "#/components/responses/Problem" },
          "401": { "$ref": "#/components/responses/Problem" },
          "403": { "$ref": "#/components/responses/Problem" },
          "409": { "description": "Idempotency-Key replay with a different payload", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "413": { "description": "Payload over limits (request bytes, note count, per-note or total note characters) — see CaseRecord limits; the problem document names the exceeded limit", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "415": { "description": "Unsupported media type — application/json required", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "422": { "description": "Schema-valid JSON that fails clinical validation (units, ranges, temporal sanity, source timestamps after the feature cutoff)", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "429": { "description": "Rate limited", "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "500": { "$ref": "#/components/responses/Problem" },
          "503": { "description": "Engine not ready (bundle sealed, license invalid, or QC gate failed) — safe to retry after Retry-After", "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }
        }
      }
    },
    "/v1/batch": {
      "post": {
        "tags": ["jobs"],
        "operationId": "submitBatch",
        "summary": "Submit a batch scoring job (preconfigured storage reference)",
        "description": "Batch jobs run under per-tenant concurrency and daily-volume quotas (contract-configurable). Manifests are referenced through preconfigured, allowlisted storage connections — arbitrary URLs and filesystem paths are rejected. Partial failures are reported per row in the job result.",
        "security": [ { "oauth2ClientCredentials": ["hoopcare/jobs.write"] } ],
        "parameters": [ { "$ref": "#/components/parameters/IdempotencyKey" }, { "$ref": "#/components/parameters/CorrelationId" } ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchRequest" } } }
        },
        "responses": {
          "202": { "description": "Accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatus" } } } },
          "400": { "$ref": "#/components/responses/Problem" },
          "401": { "$ref": "#/components/responses/Problem" },
          "403": { "$ref": "#/components/responses/Problem" },
          "409": { "$ref": "#/components/responses/Problem" },
          "422": { "$ref": "#/components/responses/Problem" },
          "429": { "description": "Quota exceeded", "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "500": { "$ref": "#/components/responses/Problem" },
          "503": { "description": "Not ready", "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }
        }
      }
    },
    "/v1/jobs/{jobId}": {
      "get": {
        "tags": ["jobs"],
        "operationId": "getJob",
        "summary": "Job status, throughput, QC summary, output location",
        "description": "Object authorization is tenant-scoped: requesting another tenant's jobId returns 404 (not 403) to avoid identifier disclosure.",
        "security": [ { "oauth2ClientCredentials": ["hoopcare/jobs.read"] } ],
        "parameters": [ { "name": "jobId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^job_[a-z0-9]{12}$" } }, { "$ref": "#/components/parameters/CorrelationId" } ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatus" } } } },
          "401": { "$ref": "#/components/responses/Problem" },
          "403": { "$ref": "#/components/responses/Problem" },
          "404": { "$ref": "#/components/responses/Problem" },
          "429": { "description": "Rate limited", "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "500": { "$ref": "#/components/responses/Problem" },
          "503": { "$ref": "#/components/responses/Problem" }
        }
      },
      "delete": {
        "tags": ["jobs"],
        "operationId": "cancelJob",
        "summary": "Cancel a queued or running job",
        "security": [ { "oauth2ClientCredentials": ["hoopcare/jobs.write"] } ],
        "parameters": [ { "name": "jobId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^job_[a-z0-9]{12}$" } } ],
        "responses": {
          "200": { "description": "Cancellation accepted; terminal state reported by getJob", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatus" } } } },
          "404": { "$ref": "#/components/responses/Problem" },
          "409": { "description": "Job already terminal", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }
        }
      }
    },
    "/v1/models": {
      "get": {
        "tags": ["models"],
        "operationId": "listModels",
        "summary": "Licensed models, versions, calibration cohort tags, evidence status",
        "security": [ { "oauth2ClientCredentials": ["hoopcare/models.read"] } ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "required": ["models"], "additionalProperties": false, "properties": { "models": { "type": "array", "items": { "$ref": "#/components/schemas/ModelInfo" } } } } } } },
          "401": { "$ref": "#/components/responses/Problem" },
          "403": { "$ref": "#/components/responses/Problem" },
          "429": { "description": "Rate limited", "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "500": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/v1/models/{modelId}/card": {
      "get": {
        "tags": ["models"],
        "operationId": "getModelCard",
        "summary": "Model card mapped to TRIPOD+AI and ONC predictive-DSI source attributes",
        "description": "PLANNED — returns 501 in preview builds until signed cards exist after the locked analysis.",
        "security": [ { "oauth2ClientCredentials": ["hoopcare/models.read"] } ],
        "parameters": [ { "name": "modelId", "in": "path", "required": true, "schema": { "type": "string", "examples": ["mortality-30d"] } } ],
        "responses": {
          "200": { "description": "OK — machine-readable source attributes plus a rendered PDF link", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "404": { "$ref": "#/components/responses/Problem" },
          "501": { "description": "Card not yet published for this model version (preview)", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }
        }
      }
    },
    "/v1/connect/run": {
      "post": {
        "tags": ["jobs"],
        "operationId": "triggerExtract",
        "summary": "Trigger an extract/refresh cycle (retro backfill or incremental)",
        "security": [ { "oauth2ClientCredentials": ["hoopcare/connect.run"] } ],
        "parameters": [ { "$ref": "#/components/parameters/IdempotencyKey" } ],
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false, "properties": { "mode": { "type": "string", "enum": ["incremental", "backfill"] }, "since": { "type": "string", "format": "date" } } } } } },
        "responses": {
          "202": { "description": "Accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatus" } } } },
          "401": { "$ref": "#/components/responses/Problem" },
          "403": { "$ref": "#/components/responses/Problem" },
          "409": { "$ref": "#/components/responses/Problem" },
          "429": { "description": "A cycle is already running", "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }
        }
      }
    },
    "/healthz": { "get": { "tags": ["ops"], "operationId": "health", "summary": "Liveness (network-restricted; auth per deployment policy)", "security": [], "responses": { "200": { "description": "Alive" } } } },
    "/readyz": { "get": { "tags": ["ops"], "operationId": "ready", "summary": "Readiness — fails closed while license/bundle/QC gates are unsatisfied", "security": [], "responses": { "200": { "description": "Ready" }, "503": { "description": "Not ready" } } } },
    "/metrics": { "get": { "tags": ["ops"], "operationId": "metrics", "summary": "Prometheus metrics (network-restricted; must not contain PHI)", "security": [], "responses": { "200": { "description": "Prometheus text exposition format", "content": { "text/plain": { "schema": { "type": "string" } } } } } } }
  },
  "components": {
    "securitySchemes": {
      "oauth2ClientCredentials": {
        "type": "oauth2",
        "description": "OAuth2 client credentials against the customer IdP or the bundled issuer. Scopes are least-privilege and required per operation. Tokens are audience-bound; TTL and rotation are deployment policy.",
        "flows": { "clientCredentials": { "tokenUrl": "https://idp.example-hospital.org/oauth2/token", "scopes": { "hoopcare/score.write": "Submit case records for scoring", "hoopcare/jobs.read": "Read job status", "hoopcare/jobs.write": "Submit and cancel batch jobs", "hoopcare/models.read": "Read model registry and cards", "hoopcare/connect.run": "Trigger connector extract cycles" } } }
      },
      "mutualTLS": {
        "type": "mutualTLS",
        "description": "Client-certificate authentication. Deployment policy selects one of: OAuth2 only, mTLS only (network-internal integrations), or both (mTLS at the transport layer AND a scoped OAuth2 token). Certificate lifecycle and revocation are site-operated."
      }
    },
    "headers": {
      "RetryAfter": { "description": "Seconds to wait before retrying", "schema": { "type": "integer", "minimum": 1 } },
      "XCorrelationId": { "description": "Echo of the request correlation id (generated when absent)", "schema": { "type": "string", "maxLength": 128 } },
      "XRateLimitRemaining": { "description": "Requests remaining in the current window for this client", "schema": { "type": "integer", "minimum": 0 } }
    },
    "parameters": {
      "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "maxLength": 128 }, "description": "Replays with the same key and payload return the original result; a different payload under the same key returns 409. Keys are retained 24 hours; after expiry the request re-executes." },
      "CorrelationId": { "name": "X-Correlation-Id", "in": "header", "required": false, "schema": { "type": "string", "maxLength": 128 }, "description": "Echoed in responses and problem documents; generated when absent." }
    },
    "responses": {
      "Problem": { "description": "Error (RFC 9457). No PHI, echoed input values, or stack traces at any level.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. detail and errors[].message must not contain PHI or echoed input values.",
        "additionalProperties": false,
        "properties": {
          "type": { "type": "string", "format": "uri", "examples": ["https://ai.hoopcare.com/problems/validation"] },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "detail": { "type": "string" },
          "instance": { "type": "string" },
          "correlationId": { "type": "string" },
          "errors": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "pointer": { "type": "string", "examples": ["/labs/0/value"] }, "severity": { "type": "string", "enum": ["error", "warning"] }, "code": { "type": "string", "examples": ["UNIT_UNSUPPORTED"] }, "message": { "type": "string", "description": "Human-readable; no PHI or echoed values" } } } }
        }
      },
      "ScoreRequest": {
        "type": "object",
        "required": ["record"],
        "additionalProperties": false,
        "properties": {
          "record": { "$ref": "#/components/schemas/CaseRecord" },
          "models": { "type": "array", "maxItems": 32, "description": "Subset of licensed model IDs; omit for all.", "items": { "type": "string", "maxLength": 64 } },
          "prediction_index_time": { "type": "string", "format": "date-time", "description": "The clinical moment the prediction represents. Omit to apply the site's index policy (default: time of scoring, bounded by scheduled_start). Echoed in the response with the policy id." },
          "feature_cutoff_time": { "type": "string", "format": "date-time", "description": "Latest permissible source timestamp. Omit to derive from prediction_index_time per the site policy. Source data timestamped after the cutoff is rejected (422) or excluded-and-flagged per policy." },
          "index_policy": { "type": "string", "maxLength": 64, "description": "Named site policy for index/cutoff derivation (e.g. at-booking, t-minus-7d, midnight-before). Mutually exclusive with explicit times.", "examples": ["midnight-before"] }
        }
      },
      "CaseRecord": {
        "type": "object",
        "description": "Canonical preoperative case record (schema hoopcare.case-record/1.0). One per scheduled procedure per schedule version: a reschedule produces a new case_id linked via supersedes. case_id is tenant-scoped and treated as PHI-adjacent (an indirect identifier). Missing optional sections are informative and must not be imputed upstream. Unknown properties are rejected (strict schemas); site-specific extensions go under extensions with governance review. Code system versions (CPT, ICD-10-CM, LOINC, RxNorm, UCUM) are pinned per release. All timestamps are ISO 8601 with offset; timezone-less values are rejected. Size limits: request ≤ 2 MB; notes ≤ 20 items, ≤ 25,000 characters each, ≤ 150,000 characters total (413 on breach; truncation is never silent — over-limit inputs are rejected, not clipped).",
        "required": ["schema", "case_id", "scheduled_start", "patient", "procedure"],
        "additionalProperties": false,
        "properties": {
          "schema": { "type": "string", "const": "hoopcare.case-record/1.0" },
          "case_id": { "type": "string", "maxLength": 64 },
          "supersedes": { "type": "string", "maxLength": 64, "description": "case_id this record replaces after a reschedule/correction" },
          "scheduled_start": { "type": "string", "format": "date-time" },
          "case_record_assembled_at": { "type": "string", "format": "date-time", "description": "When the connector assembled this record" },
          "source_extract_watermark_time": { "type": "string", "format": "date-time", "description": "Freshness watermark of the source extract this record was built from" },
          "patient": {
            "type": "object", "required": ["age", "sex"], "additionalProperties": false,
            "properties": {
              "age": { "type": "number", "minimum": 18, "maximum": 110, "description": "Years at surgery. Under-18 is out of scope and returns out_of_scope." },
              "sex": { "type": "string", "enum": ["F", "M", "other", "unknown"], "description": "Sex at birth where available; other/unknown are modeled as informative categories" },
              "bmi": { "type": ["number", "null"], "minimum": 8, "maximum": 100 }
            }
          },
          "procedure": {
            "type": "object", "required": ["code", "system"], "additionalProperties": false,
            "properties": {
              "code": { "type": "string", "maxLength": 16 },
              "system": { "type": "string", "enum": ["CPT", "HCPCS", "SNOMED"] },
              "urgency": { "type": "string", "enum": ["elective", "urgent", "emergent"] },
              "service": { "type": "string", "maxLength": 64 },
              "setting": { "type": "string", "enum": ["ambulatory", "inpatient"] }
            }
          },
          "additional_procedures": { "type": "array", "maxItems": 10, "items": { "type": "object", "additionalProperties": false, "required": ["code", "system"], "properties": { "code": { "type": "string", "maxLength": 16 }, "system": { "type": "string", "enum": ["CPT", "HCPCS", "SNOMED"] } } } },
          "anesthesia": { "type": "object", "additionalProperties": false, "properties": { "planned_type": { "type": "string", "enum": ["general", "mac", "regional", "neuraxial", "general_block", "other"] } } },
          "labs": { "type": "array", "maxItems": 500, "items": { "type": "object", "required": ["loinc", "value", "unit", "observed"], "additionalProperties": false, "properties": { "loinc": { "type": "string", "maxLength": 16 }, "value": { "type": "number" }, "unit": { "type": "string", "maxLength": 32, "description": "UCUM" }, "observed": { "type": "string", "format": "date-time", "description": "Result instant; governs cutoff eligibility. Corrections replace by loinc+observed." }, "corrected": { "type": "boolean" } } } },
          "vitals": {
            "type": "object", "additionalProperties": false,
            "description": "Allowlisted vital signs; unknown keys rejected.",
            "properties": {
              "hr": { "type": "number" }, "sbp": { "type": "number" }, "dbp": { "type": "number" }, "map": { "type": "number" },
              "rr": { "type": "number" }, "spo2": { "type": "number" }, "temp_c": { "type": "number" },
              "observed": { "type": "string", "format": "date-time" }
            }
          },
          "medications": { "type": "array", "maxItems": 300, "items": { "type": "object", "additionalProperties": false, "properties": { "rxnorm": { "type": "string", "maxLength": 16 }, "class": { "type": "string", "maxLength": 64 }, "active": { "type": "boolean" }, "recorded": { "type": "string", "format": "date-time" } } } },
          "diagnoses": { "type": "array", "maxItems": 1000, "items": { "type": "object", "required": ["icd10"], "additionalProperties": false, "properties": { "icd10": { "type": "string", "maxLength": 10 }, "recorded": { "type": "string", "format": "date-time" } } } },
          "ecg": { "type": "object", "additionalProperties": false, "properties": { "qtc": { "type": "number" }, "qrs": { "type": "number" }, "pr": { "type": "number" }, "rate": { "type": "number" }, "observed": { "type": "string", "format": "date-time" } } },
          "prior_utilization": {
            "type": "object", "additionalProperties": false,
            "description": "Allowlisted utilization counts; unknown keys rejected.",
            "properties": {
              "surgeries_5y": { "type": "integer", "minimum": 0 }, "admissions_1y": { "type": "integer", "minimum": 0 },
              "icu_1y": { "type": "integer", "minimum": 0 }, "ed_visits_1y": { "type": "integer", "minimum": 0 },
              "days_since_last_surgery": { "type": "integer", "minimum": 0 }
            }
          },
          "notes": { "type": "array", "description": "Hybrid tier only. Availability is governed by authored_at (authoring/signing instant) relative to feature_cutoff_time; date-only timestamps are rejected.", "maxItems": 20, "items": { "type": "object", "required": ["type", "authored_at", "text"], "additionalProperties": false, "properties": { "type": { "type": "string", "maxLength": 16, "description": "LOINC document type preferred (34117-2 H&P, 11488-4 consult)" }, "authored_at": { "type": "string", "format": "date-time" }, "text": { "type": "string", "maxLength": 25000 } } } },
          "extensions": { "type": "object", "maxProperties": 10, "description": "Governed site extensions only — namespaced keys agreed in the site data-governance review; each value ≤ 1,024 characters; contents must be non-PHI configuration/context, not clinical free text.", "additionalProperties": { "type": "string", "maxLength": 1024 } }
        }
      },
      "ScoreResponse": {
        "type": "object",
        "required": ["case_id", "record_version", "scored_at", "prediction_index_time", "feature_cutoff_time", "index_policy", "results"],
        "additionalProperties": false,
        "properties": {
          "case_id": { "type": "string" },
          "record_version": { "type": "string", "description": "sha256 of the canonicalized input bundle" },
          "scored_at": { "type": "string", "format": "date-time" },
          "prediction_index_time": { "type": "string", "format": "date-time", "description": "Accepted/derived index — echo of request or policy result" },
          "feature_cutoff_time": { "type": "string", "format": "date-time" },
          "index_policy": { "type": "string", "description": "Policy id + version that derived the index/cutoff", "examples": ["midnight-before@2"] },
          "source_extract_watermark_time": { "type": ["string", "null"], "format": "date-time" },
          "correlationId": { "type": "string" },
          "expires_at": { "type": "string", "format": "date-time", "description": "Scores older than this are marked stale by consumers" },
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/ScoreResult" } },
          "warnings": { "type": "array", "items": { "$ref": "#/components/schemas/Warning" } }
        }
      },
      "ScoreResult": {
        "type": "object",
        "required": ["model", "version", "status"],
        "additionalProperties": false,
        "description": "Result variants are constrained: a valid result carries exactly one value shape (probability for classifiers, estimate for regressors, score for the composite index); non-valid statuses carry no value fields and must carry status_reasons.",
        "properties": {
          "model": { "type": "string", "examples": ["mortality-30d"] },
          "version": { "type": "string", "examples": ["0.9.0+cal.2026Q3"], "description": "model+calibration; feature-manifest and threshold-policy versions are in provenance" },
          "status": { "type": "string", "enum": ["valid", "degraded", "stale", "out_of_scope", "abstained", "failed"], "description": "degraded = scored with completeness below the expected band; abstained = inputs outside safe operating range; out_of_scope = population exclusion" },
          "status_reasons": { "type": "array", "items": { "type": "string", "enum": ["MISSING_REQUIRED_DOMAIN", "COMPLETENESS_BELOW_THRESHOLD", "FEATURE_AFTER_CUTOFF", "POPULATION_EXCLUDED", "PROCEDURE_UNSUPPORTED", "INPUT_OUT_OF_DISTRIBUTION", "MODEL_NOT_LICENSED", "MODEL_NOT_READY", "SCORE_EXPIRED", "INTERNAL_ERROR"] }, "description": "Stable machine-readable reasons; required whenever status is not valid. Do not parse warning text for workflow logic." },
          "probability": { "type": ["number", "null"], "minimum": 0, "maximum": 1, "description": "Isotonic-calibrated probability (classifiers)" },
          "estimate": { "type": ["number", "null"], "description": "Point estimate (regressors)" },
          "interval80": { "type": ["array", "null"], "items": { "type": "number" }, "minItems": 2, "maxItems": 2 },
          "score": { "type": ["integer", "null"], "minimum": 0, "maximum": 100, "description": "risk-score-global only: composite index, not a probability" },
          "tier": { "type": ["string", "null"], "enum": ["very_low", "low", "intermediate", "high", "very_high", null] },
          "flag": { "type": ["boolean", "null"], "description": "At the site-configured operating point" },
          "operating_point": { "type": ["string", "null"], "description": "Site threshold-policy reference; example values are not clinical policy" },
          "top_drivers": { "type": "array", "maxItems": 10, "items": { "type": "string", "maxLength": 64 }, "description": "Codes from the published driver vocabulary (feature-group identifiers) — never free text derived from source notes; UI rendering/localization is the display layer's responsibility. Contribution is not causality." },
          "provenance": { "type": "object", "additionalProperties": false, "properties": { "feature_manifest": { "type": "string" }, "threshold_policy": { "type": "string" }, "explanation_method": { "type": "string", "examples": ["grouped-shap-topk"] } } }
        },
        "allOf": [
          { "if": { "properties": { "status": { "const": "valid" } }, "required": ["status"] },
            "then": { "anyOf": [ { "required": ["probability"], "properties": { "probability": { "type": "number" } } }, { "required": ["estimate"], "properties": { "estimate": { "type": "number" } } }, { "required": ["score"], "properties": { "score": { "type": "integer" } } } ] } },
          { "if": { "properties": { "status": { "enum": ["out_of_scope", "abstained", "failed", "stale"] } }, "required": ["status"] },
            "then": { "required": ["status_reasons"], "properties": { "probability": { "const": null }, "estimate": { "const": null }, "score": { "const": null } } } },
          { "if": { "properties": { "status": { "const": "degraded" } }, "required": ["status"] },
            "then": { "required": ["status_reasons"] } }
        ]
      },
      "Warning": {
        "type": "object",
        "additionalProperties": false,
        "properties": { "pointer": { "type": "string", "examples": ["/labs/bnp"] }, "severity": { "type": "string", "enum": ["info", "warning"] }, "code": { "type": "string", "examples": ["INFORMATIVE_MISSINGNESS"] }, "message": { "type": "string", "description": "Human-readable; no PHI. Use codes, not this text, for workflow logic." } }
      },
      "BatchRequest": {
        "type": "object",
        "required": ["format", "storage_connection", "object_key"],
        "additionalProperties": false,
        "description": "Manifests are read through preconfigured storage connections (allowlisted connector + bucket/container + prefix, configured at deployment). Arbitrary URLs and filesystem paths are rejected to prevent SSRF/path traversal.",
        "properties": {
          "format": { "type": "string", "enum": ["ndjson", "parquet"] },
          "storage_connection": { "type": "string", "maxLength": 64, "description": "Name of a preconfigured, allowlisted storage connection", "examples": ["retro-input-approved"] },
          "object_key": { "type": "string", "maxLength": 512, "pattern": "^[A-Za-z0-9!_.*'()/-]+$", "examples": ["2026-08/cohort-01/manifest.parquet"] },
          "checksum_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
          "models": { "type": "array", "maxItems": 32, "items": { "type": "string", "maxLength": 64 } },
          "index_policy": { "type": "string", "maxLength": 64 }
        }
      },
      "JobStatus": {
        "type": "object",
        "required": ["job_id", "state"],
        "additionalProperties": false,
        "properties": {
          "job_id": { "type": "string" },
          "state": { "type": "string", "enum": ["queued", "running", "succeeded", "failed", "cancelling", "cancelled", "expired"] },
          "progress": { "type": "object", "additionalProperties": false, "properties": { "total": { "type": "integer" }, "scored": { "type": "integer" }, "failed_rows": { "type": "integer" } } },
          "qc_summary": { "type": "object", "additionalProperties": true },
          "output_location": { "type": ["string", "null"], "description": "Object key under the job's storage connection" },
          "expires_at": { "type": "string", "format": "date-time", "description": "Outputs are deleted after expiry per the site retention policy" },
          "problem": { "$ref": "#/components/schemas/Problem" }
        }
      },
      "ModelInfo": {
        "type": "object",
        "required": ["model_id", "version", "status"],
        "additionalProperties": false,
        "properties": {
          "model_id": { "type": "string" },
          "outcome_definition": { "type": "string" },
          "horizon": { "type": "string", "examples": ["30d"] },
          "population": { "type": "string" },
          "tiers": { "type": "array", "items": { "type": "string", "enum": ["super_light", "ehr_standard", "ehr_advanced", "max"] } },
          "llm_layer_available": { "type": "boolean" },
          "directive_output": { "type": "boolean", "description": "True for endpoints providing a specific care recommendation (e.g. asc-eligibility, prophylaxis flag) — separate intended-use/regulatory track; evaluation only" },
          "version": { "type": "string" },
          "calibration_tag": { "type": "string" },
          "status": { "type": "string", "enum": ["proposed", "prototype", "internally_verified", "partner_evaluated", "production_supported"] },
          "evidence_status": { "type": "string", "description": "Link/state of the signed model card; 'pending final locked-cohort analysis' in this preview" }
        }
      }
    },
    "examples": {
      "MinimumValidCase": {
        "summary": "Minimum valid request",
        "value": { "record": { "schema": "hoopcare.case-record/1.0", "case_id": "SC-SYN-000001", "scheduled_start": "2026-09-14T07:30:00-05:00", "patient": { "age": 60, "sex": "F" }, "procedure": { "code": "47562", "system": "CPT" } } }
      },
      "AbstainedResult": {
        "summary": "Non-valid result with machine-readable reasons",
        "value": { "model": "aki-kdigo2-30d", "version": "0.9.0+cal.2026Q3", "status": "abstained", "status_reasons": ["INPUT_OUT_OF_DISTRIBUTION"], "probability": null, "estimate": null, "score": null }
      }
    }
  }
}
