mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
feat(documents): content-index backfill job, rotation coverage, OpenAPI
P2-1-T7/T8/T9. - document-content-index-backfill: on-demand per-user pg-boss job that indexes a user's not-yet-indexed image/PDF documents via one provider transcription each. Consent + budget gated, id-cursor forward walk, capped per run, stops on budget exhaustion, resumable. Queue + worker registered in the maintenance registrar (no cron — deliberate action only). POST /reindex triggers it. - rotation: register DocumentContentIndex.textEncrypted and add a dedicated block that re-encrypts the text AND re-tokenises the blind index under the new active HKDF subkey in one update (P2-D7). - OpenAPI: suggest / summary / index / reindex paths; hasContentIndex on the document schema; assistAvailable + contentIndex on usage. Regenerated docs/api/openapi.yaml.
This commit is contained in:
1 parent
85a343a68c
commit
5a0fd47eb5
7 files changed
+901
-2
No files matched your search
+351
-1
@@ -9135,6 +9135,197 @@ paths:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/{id}/suggest:
|
||||
post:
|
||||
tags:
|
||||
- Documents
|
||||
summary: Suggest filing metadata (drafts only)
|
||||
description: "Optional AI assist on an already-stored document. Runs ONE provider call over the stored original (VISION,
|
||||
empty body) or browser-OCR'd text (TEXT, `application/json` `{ mode: \"text\", text }`, opt-in local OCR) and
|
||||
returns a `{ title, kind, documentDate }` DRAFT for the edit form. AI-consent / rate / budget gated (shares the
|
||||
6/hour extract bucket). WRITES NOTHING — never stages facts, never flips status; the user reviews and saves. 422
|
||||
(`documents.inbound.providerUnsupported`) with no provider configured. Never interprets or diagnoses; the title
|
||||
is a neutral filing label."
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
const: text
|
||||
text:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- DOCTOR_REPORT
|
||||
- DISCHARGE_LETTER
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
required:
|
||||
- mode
|
||||
- text
|
||||
responses:
|
||||
"200":
|
||||
description: The filing-metadata suggestion (drafts).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DocumentSuggestEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/{id}/summary:
|
||||
post:
|
||||
tags:
|
||||
- Documents
|
||||
summary: Summarise or transcribe a document (session-only)
|
||||
description: 'On-demand, SESSION-ONLY description of a stored document. `?mode=summary` (default) returns a short
|
||||
plain-language summary of WHAT the document is; `?mode=text` returns its raw transcribed text. The result is
|
||||
transient — nothing is persisted except the AI-budget ledger + audit log; it never reaches coach memory,
|
||||
snapshots, the structured stores, or the search index. The summary is descriptive only and never a diagnosis.
|
||||
Same VISION (empty body) / TEXT (`application/json` `{ mode: "text", text }`, opt-in local OCR) dispatch as
|
||||
extract. AI-consent / rate / budget gated. 422 with no provider.'
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: mode
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- summary
|
||||
- text
|
||||
default: summary
|
||||
description: "`summary` (plain-language description) or `text` (raw)."
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
const: text
|
||||
text:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- DOCTOR_REPORT
|
||||
- DISCHARGE_LETTER
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
required:
|
||||
- mode
|
||||
- text
|
||||
responses:
|
||||
"200":
|
||||
description: The session-only summary (`{ summary }`) or extracted text (`{ text }`).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DocumentSummaryEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/{id}/index:
|
||||
post:
|
||||
tags:
|
||||
- Documents
|
||||
summary: Build / refresh the content-search index
|
||||
description: "Populates or refreshes one document's content-search index so search matches INSIDE its body. VISION
|
||||
(empty body) decrypts the stored original and runs one provider transcription (AI-consent / rate / budget
|
||||
gated); TEXT (`application/json` `{ mode: \"text\", text }`, opt-in local OCR) indexes browser-OCR'd text with
|
||||
no provider egress. Persists ONLY AES-256-GCM ciphertext of the text plus opaque HMAC token hashes — no
|
||||
plaintext body, no plaintext token. Gated on the existing AI consent (no separate per-user toggle). Idempotent;
|
||||
re-indexing overwrites in place."
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
const: text
|
||||
text:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- DOCTOR_REPORT
|
||||
- DISCHARGE_LETTER
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
required:
|
||||
- mode
|
||||
- text
|
||||
responses:
|
||||
"200":
|
||||
description: The document was indexed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DocumentIndexEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/reindex:
|
||||
post:
|
||||
tags:
|
||||
- Documents
|
||||
summary: Index all documents for content search
|
||||
description: Enqueues a background job that content-indexes the caller's not-yet-indexed documents (one provider
|
||||
transcription each, bounded + resumable). Gated on the module, a configured vision provider (422
|
||||
`documents.inbound.providerUnsupported` otherwise), and the existing AI consent (403 otherwise). Returns
|
||||
immediately; the work runs off-request on the queue.
|
||||
responses:
|
||||
"200":
|
||||
description: Whether a backfill job was enqueued.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DocumentReindexEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/documents/inbound/{id}/facts/{factId}:
|
||||
patch:
|
||||
tags:
|
||||
@@ -25933,6 +26124,8 @@ components:
|
||||
enum:
|
||||
- inline
|
||||
- attachment
|
||||
hasContentIndex:
|
||||
type: boolean
|
||||
createdAt:
|
||||
type: string
|
||||
updatedAt:
|
||||
@@ -25953,6 +26146,7 @@ components:
|
||||
- pendingCount
|
||||
- conditionLinks
|
||||
- servingClass
|
||||
- hasContentIndex
|
||||
- createdAt
|
||||
- updatedAt
|
||||
additionalProperties: false
|
||||
@@ -25960,7 +26154,8 @@ components:
|
||||
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). `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
|
||||
download-only (`attachment`); render/download by it, never by MIME guess. `hasContentIndex` is true when the
|
||||
document has a content-search index (drives the re-index affordance). Treat an unknown `kind` value as OTHER
|
||||
when decoding.
|
||||
DocumentConditionLink:
|
||||
type: object
|
||||
@@ -26034,12 +26229,30 @@ components:
|
||||
- episodeId
|
||||
- name
|
||||
additionalProperties: false
|
||||
assistAvailable:
|
||||
type: boolean
|
||||
contentIndex:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
indexedCount:
|
||||
type: number
|
||||
totalCount:
|
||||
type: number
|
||||
required:
|
||||
- enabled
|
||||
- indexedCount
|
||||
- totalCount
|
||||
additionalProperties: false
|
||||
required:
|
||||
- usedBytes
|
||||
- quotaBytes
|
||||
- maxFileBytes
|
||||
- acceptedExtensions
|
||||
- linkedEpisodes
|
||||
- assistAvailable
|
||||
- contentIndex
|
||||
additionalProperties: false
|
||||
DocumentBulkEnvelope:
|
||||
type: object
|
||||
@@ -26166,6 +26379,8 @@ components:
|
||||
enum:
|
||||
- inline
|
||||
- attachment
|
||||
hasContentIndex:
|
||||
type: boolean
|
||||
createdAt:
|
||||
type: string
|
||||
updatedAt:
|
||||
@@ -26190,6 +26405,7 @@ components:
|
||||
- pendingCount
|
||||
- conditionLinks
|
||||
- servingClass
|
||||
- hasContentIndex
|
||||
- createdAt
|
||||
- updatedAt
|
||||
- facts
|
||||
@@ -26336,6 +26552,140 @@ components:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DocumentSuggestEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/DocumentSuggestResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DocumentSuggestResponse:
|
||||
type: object
|
||||
properties:
|
||||
suggestions:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
kind:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- DOCTOR_REPORT
|
||||
- DISCHARGE_LETTER
|
||||
- LAB_RESULT
|
||||
- IMAGING
|
||||
- PRESCRIPTION
|
||||
- REFERRAL
|
||||
- INSURANCE
|
||||
- VACCINATION
|
||||
- OTHER
|
||||
- type: "null"
|
||||
documentDate:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
required:
|
||||
- title
|
||||
- kind
|
||||
- documentDate
|
||||
additionalProperties: false
|
||||
required:
|
||||
- suggestions
|
||||
additionalProperties: false
|
||||
DocumentSummaryEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/DocumentSummaryResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DocumentSummaryResponse:
|
||||
type: object
|
||||
properties:
|
||||
summary:
|
||||
type: string
|
||||
text:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
DocumentIndexEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/DocumentIndexResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DocumentIndexResponse:
|
||||
type: object
|
||||
properties:
|
||||
documentId:
|
||||
type: string
|
||||
indexed:
|
||||
type: boolean
|
||||
tokenCount:
|
||||
type: number
|
||||
required:
|
||||
- documentId
|
||||
- indexed
|
||||
- tokenCount
|
||||
additionalProperties: false
|
||||
DocumentReindexEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/DocumentReindexResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DocumentReindexResponse:
|
||||
type: object
|
||||
properties:
|
||||
enqueued:
|
||||
type: boolean
|
||||
required:
|
||||
- enqueued
|
||||
additionalProperties: false
|
||||
ExtractedFactEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in new issue
Block a user