mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
Merge branch 'feat/vault-w4-qa' into release/v1.27.17
This commit is contained in:
commit
03058b0210
81 files changed
+10544
-3246
No files matched your search
+350
-24
@@ -8669,20 +8669,39 @@ paths:
|
||||
maxLength: 100
|
||||
description: Case-insensitive search over title + filename.
|
||||
- name: kind
|
||||
in: query
|
||||
required: false
|
||||
style: form
|
||||
explode: true
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- DOCTOR_REPORT
|
||||
- DISCHARGE_LETTER
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
description: Category filter — OR inside the facet. Repeat the parameter (or send a comma-separated value).
|
||||
- name: episodeId
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- DOCTOR_REPORT
|
||||
- DISCHARGE_LETTER
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
description: Category filter.
|
||||
description: Only documents linked to this illness/condition episode.
|
||||
- name: year
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1900
|
||||
maximum: 9999
|
||||
description: Only documents whose filing date falls in this calendar year (UTC). Takes precedence over from/to.
|
||||
- name: from
|
||||
in: query
|
||||
required: false
|
||||
@@ -8746,9 +8765,16 @@ paths:
|
||||
summary: Store a document (no extraction)
|
||||
description: "STORE-ONLY upload. Stores the raw document ENCRYPTED at rest with `status: STORED` and runs NO extraction
|
||||
— provider-free, no AI consent / budget / egress. A file is always filable, even with no document-scan provider
|
||||
configured. `multipart/form-data`: a `file` (JPEG/PNG/WebP/PDF, validated by magic-byte MIME sniff, ≤ 12 MiB)
|
||||
plus optional `title`, `kind`, and `documentDate` (YYYY-MM-DD) form fields. AI extraction is a separate opt-in
|
||||
action — see `POST /api/documents/inbound/{id}/extract`."
|
||||
configured. `multipart/form-data`: a `file` plus optional `title`, `kind`, `documentDate` (YYYY-MM-DD), and
|
||||
repeated `episodeIds` form fields (pre-link to the caller's illness/condition episodes). Accepted types
|
||||
(magic-byte sniffed, never the wire Content-Type): PDF/JPEG/PNG/WebP/GIF render inline; Office
|
||||
(docx/xlsx/pptx/doc/xls/ppt), text/CSV/Markdown/RTF, TIFF, HEIC/HEIF, XML/JSON are stored verbatim and served
|
||||
download-only. HEIC is stored as-is but attachment-only — prefer transcoding to JPEG client-side for inline
|
||||
preview parity. Error contract: `413` with `meta.reason = \"fileTooLarge\"` (+ `maxFileBytes`) or
|
||||
`\"quotaExceeded\"` (+ `quotaBytes`, `usedBytes`); `415` with `meta.reason = \"unsupportedType\"`. A same-user
|
||||
duplicate (same bytes) returns 200 + `meta.duplicate: true` with the existing row — not an error.
|
||||
`Idempotency-Key` honoured. Read `GET /api/documents/inbound/usage` for the effective limits before offering an
|
||||
upload. AI extraction is a separate opt-in action — see `POST /api/documents/inbound/{id}/extract`."
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -8759,7 +8785,7 @@ paths:
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
description: The document image or PDF. Validated by magic-byte MIME sniff, not the wire Content-Type.
|
||||
description: The document. Validated by magic-byte MIME sniff, not the wire Content-Type.
|
||||
title:
|
||||
description: Optional user label.
|
||||
type: string
|
||||
@@ -8772,6 +8798,7 @@ paths:
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
@@ -8779,9 +8806,21 @@ paths:
|
||||
description: Optional user filing date (YYYY-MM-DD). Defaults to the upload day when omitted so the library
|
||||
sort/filter/display stay aligned.
|
||||
type: string
|
||||
episodeIds:
|
||||
description: Optional condition pre-links (repeat the field per id). Every id must be a live episode of the caller.
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- file
|
||||
responses:
|
||||
"200":
|
||||
description: "Duplicate — the same bytes are already stored: the existing row, with `meta.duplicate: true` at the
|
||||
envelope level."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/InboundDocumentEnvelope"
|
||||
"201":
|
||||
description: The stored document.
|
||||
content:
|
||||
@@ -8789,6 +8828,99 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/InboundDocumentEnvelope"
|
||||
"401": *a1
|
||||
"413":
|
||||
description: "Too large: `meta.reason` is `fileTooLarge` (with `maxFileBytes`) or `quotaExceeded` (with `quotaBytes` +
|
||||
`usedBytes`)."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"415":
|
||||
description: Unsupported or unidentifiable type (`meta.reason = "unsupportedType"`). Executables, HTML/SVG, and generic
|
||||
archives are always refused.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/usage:
|
||||
get:
|
||||
tags:
|
||||
- Documents
|
||||
summary: Storage usage + effective limits
|
||||
description: "The caller's vault usage and effective limits: `usedBytes` (every non-purged row — tombstones inside the
|
||||
30-day undo grace still count), `quotaBytes` (per-user override ?? instance default), `maxFileBytes`
|
||||
(admin-tunable per-file cap), `acceptedExtensions` for picker `accept` lists, and `linkedEpisodes` (episodes
|
||||
carrying at least one live document link — the condition-filter chips). Read this before offering an upload."
|
||||
responses:
|
||||
"200":
|
||||
description: Usage + limits.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DocumentUsageEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/bulk:
|
||||
post:
|
||||
tags:
|
||||
- Documents
|
||||
summary: Bulk action over documents
|
||||
description: Applies one action to up to 100 owner-scoped documents and returns a per-id result array — a partial
|
||||
failure never aborts the batch. `error` is `notFound` (unknown / foreign / tombstoned where liveness is
|
||||
required) or `conflict` (restore blocked by a live duplicate of the same bytes).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ids:
|
||||
minItems: 1
|
||||
maxItems: 100
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 40
|
||||
action:
|
||||
type: string
|
||||
enum:
|
||||
- setKind
|
||||
- linkEpisode
|
||||
- unlinkEpisode
|
||||
- delete
|
||||
- restore
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- DOCTOR_REPORT
|
||||
- DISCHARGE_LETTER
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
episodeId:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 40
|
||||
required:
|
||||
- ids
|
||||
- action
|
||||
responses:
|
||||
"200":
|
||||
description: Per-id outcomes.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DocumentBulkEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/{id}:
|
||||
@@ -8845,6 +8977,7 @@ paths:
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
@@ -8853,6 +8986,13 @@ paths:
|
||||
- type: string
|
||||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||||
- type: "null"
|
||||
episodeIds:
|
||||
maxItems: 20
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 40
|
||||
responses:
|
||||
"200":
|
||||
description: The updated document.
|
||||
@@ -8885,16 +9025,14 @@ paths:
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/{id}/original:
|
||||
get:
|
||||
/api/documents/inbound/{id}/restore:
|
||||
post:
|
||||
tags:
|
||||
- Documents
|
||||
summary: View / download the original document
|
||||
description: "Decrypts and serves the raw uploaded document (the bytes stored encrypted at rest under
|
||||
`InboundDocument.contentEncrypted`). Owner-scoped + gated on the opt-in `inboundDocuments` module. Uploads are
|
||||
constrained at the store path to the inline-safe set (JPEG / PNG / WebP / PDF), so the document is always served
|
||||
`Content-Disposition: inline` for in-tab rendering; a non-ASCII filename is carried via RFC 5987 `filename*`.
|
||||
Fails closed (500) on a decrypt error — it never returns the ciphertext."
|
||||
summary: Restore a discarded document
|
||||
description: Clears the soft-delete tombstone within the 30-day undo grace. 409 when the tombstone is already purged
|
||||
(the undo window is over) or when an identical live document blocks the restore (`meta.reason =
|
||||
"duplicateExists"`). Restoring an already-live document is a no-op success.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
@@ -8903,7 +9041,42 @@ paths:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The decrypted original document bytes with the stored MIME type.
|
||||
description: The restored document + its staged facts.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/InboundRestoreEnvelope"
|
||||
"401": *a1
|
||||
"409":
|
||||
description: "Not restorable: purged past the grace window, or an identical live document exists."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/{id}/original:
|
||||
get:
|
||||
tags:
|
||||
- Documents
|
||||
summary: View / download the original document
|
||||
description: "Decrypts and serves the raw uploaded document (the bytes stored encrypted at rest under
|
||||
`InboundDocument.contentEncrypted`). Owner-scoped + gated on the opt-in `inboundDocuments` module. Serving
|
||||
posture follows the document's `servingClass`: inline types (PDF/JPEG/PNG/WebP/GIF) come with their true
|
||||
Content-Type, `Content-Disposition: inline`, `nosniff`, and a `Content-Security-Policy: sandbox` header;
|
||||
attachment types (Office/text/TIFF/HEIC/XML/JSON) ALWAYS come as `application/octet-stream` +
|
||||
`Content-Disposition: attachment` + `nosniff` — never inline. A non-ASCII filename is carried via RFC 5987
|
||||
`filename*`. Fails closed (500) on a decrypt error — it never returns the ciphertext."
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The decrypted original document bytes. Inline types carry the stored MIME type; attachment types are served
|
||||
as application/octet-stream.
|
||||
content:
|
||||
application/pdf:
|
||||
schema:
|
||||
@@ -8913,6 +9086,10 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
@@ -10698,6 +10875,8 @@ components:
|
||||
type: boolean
|
||||
mcp:
|
||||
type: boolean
|
||||
inboundDocuments:
|
||||
type: boolean
|
||||
mentalHealth:
|
||||
type: boolean
|
||||
additionalProperties: false
|
||||
@@ -11256,6 +11435,7 @@ components:
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
@@ -25699,6 +25879,7 @@ components:
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
@@ -25743,6 +25924,15 @@ components:
|
||||
type: number
|
||||
pendingCount:
|
||||
type: number
|
||||
conditionLinks:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/DocumentConditionLink"
|
||||
servingClass:
|
||||
type: string
|
||||
enum:
|
||||
- inline
|
||||
- attachment
|
||||
createdAt:
|
||||
type: string
|
||||
updatedAt:
|
||||
@@ -25761,13 +25951,29 @@ components:
|
||||
- errorReason
|
||||
- factCount
|
||||
- pendingCount
|
||||
- conditionLinks
|
||||
- servingClass
|
||||
- createdAt
|
||||
- updatedAt
|
||||
additionalProperties: false
|
||||
description: A stored document. The raw bytes are stored encrypted at rest and never returned; this is the metadata +
|
||||
staging summary. `status` is STORED for a freshly uploaded file (no extraction run). `title` is the user label
|
||||
(plaintext); `documentDate` is the user filing date; `reportDate` is the model-transcribed date (null until
|
||||
extraction runs).
|
||||
extraction runs). `servingClass` says how `/original` delivers the file — render inline (`inline`) or
|
||||
download-only (`attachment`); render/download by it, never by MIME guess. Treat an unknown `kind` value as OTHER
|
||||
when decoding.
|
||||
DocumentConditionLink:
|
||||
type: object
|
||||
properties:
|
||||
episodeId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- episodeId
|
||||
- name
|
||||
additionalProperties: false
|
||||
description: A link to one of the caller's illness/condition episodes. `name` is the episode's user-facing label.
|
||||
InboundDocumentEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
@@ -25785,6 +25991,97 @@ components:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DocumentUsageEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/DocumentUsage"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DocumentUsage:
|
||||
type: object
|
||||
properties:
|
||||
usedBytes:
|
||||
type: number
|
||||
quotaBytes:
|
||||
type: number
|
||||
maxFileBytes:
|
||||
type: number
|
||||
acceptedExtensions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
linkedEpisodes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
episodeId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- episodeId
|
||||
- name
|
||||
additionalProperties: false
|
||||
required:
|
||||
- usedBytes
|
||||
- quotaBytes
|
||||
- maxFileBytes
|
||||
- acceptedExtensions
|
||||
- linkedEpisodes
|
||||
additionalProperties: false
|
||||
DocumentBulkEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/DocumentBulkResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DocumentBulkResponse:
|
||||
type: object
|
||||
properties:
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
ok:
|
||||
type: boolean
|
||||
error:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
required:
|
||||
- id
|
||||
- ok
|
||||
- error
|
||||
additionalProperties: false
|
||||
required:
|
||||
- results
|
||||
additionalProperties: false
|
||||
InboundDocumentDetailEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
@@ -25815,6 +26112,7 @@ components:
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
@@ -25859,6 +26157,15 @@ components:
|
||||
type: number
|
||||
pendingCount:
|
||||
type: number
|
||||
conditionLinks:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/DocumentConditionLink"
|
||||
servingClass:
|
||||
type: string
|
||||
enum:
|
||||
- inline
|
||||
- attachment
|
||||
createdAt:
|
||||
type: string
|
||||
updatedAt:
|
||||
@@ -25881,6 +26188,8 @@ components:
|
||||
- errorReason
|
||||
- factCount
|
||||
- pendingCount
|
||||
- conditionLinks
|
||||
- servingClass
|
||||
- createdAt
|
||||
- updatedAt
|
||||
- facts
|
||||
@@ -25993,6 +26302,23 @@ components:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
InboundRestoreEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/InboundDocumentDetail"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
InboundExtractEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in new issue
Block a user