mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
fix(documents): name the document-AI capability in each route that can queue it, and catalogue the picker's error codes
The ingest path takes the capability from its caller, so the upload and
the import route each read getAiCapability("documentAi") in their own
source, where the AI route inventory classifies them.
This commit is contained in:
1 parent
caf3eb323d
commit
92f2cc729f
7 files changed
+68
-19
No files matched your search
@@ -31198,8 +31198,13 @@ components:
|
||||
documents.inbound.notIndexed, documents.inbound.pdfNeedsAnthropic,
|
||||
documents.inbound.providerUnsupported, documents.inbound.quotaExceeded, documents.inbound.rateLimited,
|
||||
documents.inbound.restoreDuplicate, documents.inbound.restoreGone, documents.inbound.sourceAliasLimit,
|
||||
documents.inbound.uploadBusy, documents.inbound.uploadTimeout, documents.inbound.vaccinationNotFound;
|
||||
encounter — encounter.invalid, encounter.link.invalid, encounter.practitioner-not-found,
|
||||
documents.inbound.uploadBusy, documents.inbound.uploadTimeout, documents.inbound.vaccinationNotFound,
|
||||
documents.sources.authRefused, documents.sources.badResponse, documents.sources.browserOnly,
|
||||
documents.sources.invalidAddress, documents.sources.linkTargetNotFound, documents.sources.notConnected,
|
||||
documents.sources.notFound, documents.sources.organizationRequired, documents.sources.originNotAllowed,
|
||||
documents.sources.rateLimited, documents.sources.redirected, documents.sources.tokenRequired,
|
||||
documents.sources.unavailable, documents.sources.unreachable, documents.sources.versionTooOld; encounter
|
||||
— encounter.invalid, encounter.link.invalid, encounter.practitioner-not-found,
|
||||
encounter.reminder-conflict, encounter.reminder-not-found; environment — environment.invalid,
|
||||
environment.no_home, environment.range_too_large, environment.travel.not_found; export —
|
||||
export.selection.empty, export.selection.unknown_leaf; familyHistory — familyHistory.invalid; feedback —
|
||||
|
||||
@@ -67,6 +67,11 @@ export const AI_ROUTES: Readonly<Record<string, AiRouteEntry>> = {
|
||||
capabilities: ["documentAi"],
|
||||
why: "The upload is data and always accepted; only the AI work queued after it follows the capability.",
|
||||
},
|
||||
"src/app/api/documents/sources/[system]/import/route.ts": {
|
||||
kind: "mixed",
|
||||
capabilities: ["documentAi"],
|
||||
why: "A picked document is stored whatever AI says; only the summary queued after a fresh insert follows the capability.",
|
||||
},
|
||||
"src/app/api/auth/me/documents-auto-ai-read/route.ts": {
|
||||
kind: "mixed",
|
||||
capabilities: ["documentAi"],
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
getClientIp,
|
||||
sanitiseZodIssues,
|
||||
} from "@/lib/api-response";
|
||||
import { getAiCapability } from "@/lib/ai/capabilities/gate";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { hashQueryTokens } from "@/lib/documents/content-index";
|
||||
import {
|
||||
@@ -480,6 +481,9 @@ async function processUpload(
|
||||
aiDeferred: ctx.aiDeferred,
|
||||
sourceKeyChecked: true,
|
||||
limits,
|
||||
// Only the AI work queued after a fresh insert follows the capability;
|
||||
// the upload itself is data and always accepted.
|
||||
documentAi: () => getAiCapability("documentAi"),
|
||||
});
|
||||
|
||||
switch (result.kind) {
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
safeJson,
|
||||
sanitiseZodIssues,
|
||||
} from "@/lib/api-response";
|
||||
import { getAiCapability } from "@/lib/ai/capabilities/gate";
|
||||
import { prisma } from "@/lib/db";
|
||||
import {
|
||||
ingestDocument,
|
||||
@@ -241,6 +242,9 @@ export const POST = apiHandler(
|
||||
aiDeferred: false,
|
||||
sourceKeyChecked: true,
|
||||
limits,
|
||||
// The import is data; only the summary queued after a fresh insert
|
||||
// follows the capability (and the person's auto-read setting).
|
||||
documentAi: () => getAiCapability("documentAi"),
|
||||
});
|
||||
|
||||
switch (result.kind) {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
import type { Buffer } from "node:buffer";
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import { getAiCapability } from "@/lib/ai/capabilities/gate";
|
||||
import type { AiCapabilityState } from "@/lib/ai/capabilities/types";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { prisma } from "@/lib/db";
|
||||
import {
|
||||
@@ -102,6 +102,13 @@ export interface IngestInput {
|
||||
sourceKeyChecked: boolean;
|
||||
/** The per-file cap and the quota, resolved by the caller. */
|
||||
limits: { maxFileBytes: number; quotaBytes: number };
|
||||
/**
|
||||
* The `documentAi` capability, asked by the caller's route only once a new
|
||||
* document was stored (a duplicate never asks). Passed in rather than asked
|
||||
* here so each route that can queue AI work names the capability in its own
|
||||
* source, where the AI route inventory reads it.
|
||||
*/
|
||||
documentAi: () => Promise<AiCapabilityState>;
|
||||
}
|
||||
|
||||
export type IngestResult =
|
||||
@@ -358,7 +365,7 @@ export async function ingestDocument(
|
||||
// the detail sheet offers "Generate summary" rather than a pending state.
|
||||
const documentAi = input.aiDeferred
|
||||
? { available: false as const, reason: "deferred" }
|
||||
: await getAiCapability("documentAi");
|
||||
: await input.documentAi();
|
||||
if (documentAi.available) {
|
||||
void enqueueDocumentSummary(userId, document.id);
|
||||
} else {
|
||||
|
||||
@@ -105,23 +105,32 @@ export const DOCUMENT_PICKER_LINK_KINDS = [
|
||||
export type DocumentPickerLinkKind =
|
||||
(typeof DOCUMENT_PICKER_LINK_KINDS)[number];
|
||||
|
||||
/** Why a request to a source failed, as `meta.errorCode` suffixes. */
|
||||
/**
|
||||
* Why a request to a source failed, as the `meta.errorCode` values the routes
|
||||
* emit. The route helper keys its status and wording on the last segment.
|
||||
*/
|
||||
export const DOCUMENT_SOURCE_ERROR_CODES = [
|
||||
"unavailable",
|
||||
"notConnected",
|
||||
"originNotAllowed",
|
||||
"unreachable",
|
||||
"redirected",
|
||||
"authRefused",
|
||||
"versionTooOld",
|
||||
"badResponse",
|
||||
"notFound",
|
||||
"linkTargetNotFound",
|
||||
"rateLimited",
|
||||
"documents.sources.unavailable",
|
||||
"documents.sources.notConnected",
|
||||
"documents.sources.originNotAllowed",
|
||||
"documents.sources.unreachable",
|
||||
"documents.sources.redirected",
|
||||
"documents.sources.authRefused",
|
||||
"documents.sources.versionTooOld",
|
||||
"documents.sources.badResponse",
|
||||
"documents.sources.notFound",
|
||||
"documents.sources.linkTargetNotFound",
|
||||
"documents.sources.rateLimited",
|
||||
] as const;
|
||||
export type DocumentSourceErrorCode =
|
||||
(typeof DOCUMENT_SOURCE_ERROR_CODES)[number];
|
||||
type SourceErrorSuffix<T> = T extends `documents.sources.${infer S}`
|
||||
? S
|
||||
: never;
|
||||
export type DocumentSourceErrorCode = SourceErrorSuffix<
|
||||
(typeof DOCUMENT_SOURCE_ERROR_CODES)[number]
|
||||
>;
|
||||
|
||||
export function documentSourceErrorCode(code: DocumentSourceErrorCode): string {
|
||||
export function documentSourceErrorCode(
|
||||
code: DocumentSourceErrorCode,
|
||||
): (typeof DOCUMENT_SOURCE_ERROR_CODES)[number] {
|
||||
return `documents.sources.${code}`;
|
||||
}
|
||||
@@ -136,6 +136,21 @@ export const ERROR_CODE_CATALOGUE: Readonly<Record<string, readonly string[]>> =
|
||||
"documents.inbound.uploadBusy",
|
||||
"documents.inbound.uploadTimeout",
|
||||
"documents.inbound.vaccinationNotFound",
|
||||
"documents.sources.authRefused",
|
||||
"documents.sources.badResponse",
|
||||
"documents.sources.browserOnly",
|
||||
"documents.sources.invalidAddress",
|
||||
"documents.sources.linkTargetNotFound",
|
||||
"documents.sources.notConnected",
|
||||
"documents.sources.notFound",
|
||||
"documents.sources.organizationRequired",
|
||||
"documents.sources.originNotAllowed",
|
||||
"documents.sources.rateLimited",
|
||||
"documents.sources.redirected",
|
||||
"documents.sources.tokenRequired",
|
||||
"documents.sources.unavailable",
|
||||
"documents.sources.unreachable",
|
||||
"documents.sources.versionTooOld",
|
||||
],
|
||||
encounter: [
|
||||
"encounter.invalid",
|
||||
|
||||
Reference in new issue
Block a user