mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
fix(ai): count the daily AI budget on the account's own day
The token ledger keyed every day on UTC while the daily briefing and the other per-day generations run on the account's local day, so east of UTC the first generations after local midnight were refused against the previous day's spent ceiling. The budget day is now the calendar day in the account's time zone; each request still resolves it once and books its reservation and reconcile on the same row. The medication extraction reads relative phrases against the same local day, the operator's spend card shows the operator's own day, and the limit copy says the budget resets at midnight in the person's time zone.
This commit is contained in:
1 parent
7d8ff0f2df
commit
dcb7a0bc09
35 files changed
+175
-65
No files matched your search
@@ -73,11 +73,13 @@ interface ProviderHealthSummary {
|
||||
const CENTRAL_TYPES = [...OPERATOR_HELD_PROVIDER_TYPES];
|
||||
|
||||
export const GET = apiHandler(async () => {
|
||||
await requireAdmin();
|
||||
const { user } = await requireAdmin();
|
||||
annotate({ action: { name: "admin.provider-health.get" } });
|
||||
|
||||
// The UTC day the ledger buckets by — the same key every reservation writes.
|
||||
const dateKey = buildDateKey();
|
||||
// The ledger buckets each account by its own local day; the operator's
|
||||
// "today" is the day in the operator's own zone, which on a single-zone
|
||||
// instance is everyone's.
|
||||
const dateKey = await buildDateKey(user.id);
|
||||
|
||||
const groups = await prisma.providerHealth.groupBy({
|
||||
// `lastStatus` joins the grouping key rather than an aggregate: the
|
||||
|
||||
@@ -145,7 +145,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
// `admin-key` is the operator's own credential and draws the operator
|
||||
// ceiling; an override names the caller's own key, and every other resolved
|
||||
// type is the user's own egress, so those draw the user-plan ceiling.
|
||||
const dateKey = buildDateKey();
|
||||
const dateKey = await buildDateKey(user.id);
|
||||
const reservation = await reserveBudget(
|
||||
user.id,
|
||||
AI_TEST_MAX_TOKENS,
|
||||
|
||||
@@ -183,7 +183,7 @@ async function reserveFor(
|
||||
providerType: ProviderChainType,
|
||||
tokens: number,
|
||||
): Promise<AiRunBudget | null> {
|
||||
const dateKey = buildDateKey();
|
||||
const dateKey = await buildDateKey(userId);
|
||||
const reservation = await reserveBudget(
|
||||
userId,
|
||||
tokens,
|
||||
|
||||
@@ -200,7 +200,7 @@ async function handleVisionIndex(
|
||||
const rl = await checkDocumentAiRateLimit(userId);
|
||||
if (!rl.allowed) return documentAiRateLimited(rl);
|
||||
|
||||
const dateKey = buildDateKey();
|
||||
const dateKey = await buildDateKey(userId);
|
||||
const reservation = await reserveBudget(
|
||||
userId,
|
||||
DOCUMENT_INDEX_RESERVE_TOKENS,
|
||||
|
||||
@@ -122,7 +122,7 @@ async function reserveFor(
|
||||
userId: string,
|
||||
providerType: ProviderChainType,
|
||||
): Promise<AiRunBudget | null> {
|
||||
const dateKey = buildDateKey();
|
||||
const dateKey = await buildDateKey(userId);
|
||||
const reservation = await reserveBudget(
|
||||
userId,
|
||||
AI_BUDGETS.documentAssist.maxTokens,
|
||||
|
||||
@@ -216,7 +216,7 @@ async function reserveFor(
|
||||
ask: SummaryRequest,
|
||||
providerType: ProviderChainType,
|
||||
): Promise<AiRunBudget | null> {
|
||||
const dateKey = buildDateKey();
|
||||
const dateKey = await buildDateKey(ask.userId);
|
||||
const reservation = await reserveBudget(
|
||||
ask.userId,
|
||||
budgetFor(ask.mode),
|
||||
|
||||
@@ -157,7 +157,7 @@ async function handleTextExtract(
|
||||
// v1.21.0 (F1) — the operator-cost cap applies only when the picked provider
|
||||
// egresses on the operator's own key; a BYOK / Codex / local pick runs on the
|
||||
// user's own plan and gets the generous user-plan ceiling.
|
||||
const dateKey = buildDateKey();
|
||||
const dateKey = await buildDateKey(userId);
|
||||
const reservation = await reserveBudget(
|
||||
userId,
|
||||
OCR_RESERVE_TOKENS.text,
|
||||
@@ -218,7 +218,7 @@ async function handleVisionExtract(
|
||||
// v1.21.0 (F1) — the operator-cost cap applies only when the picked vision
|
||||
// provider egresses on the operator's own key; a BYOK / Codex pick runs on
|
||||
// the user's own plan and gets the generous user-plan ceiling.
|
||||
const dateKey = buildDateKey();
|
||||
const dateKey = await buildDateKey(userId);
|
||||
const reservation = await reserveBudget(
|
||||
userId,
|
||||
OCR_RESERVE_TOKENS.vision,
|
||||
|
||||
@@ -225,7 +225,10 @@ async function handleExtract(request: NextRequest): Promise<Response> {
|
||||
chain.map((entry) => entry.providerType),
|
||||
);
|
||||
|
||||
const today = todayOverride ?? buildDateKey();
|
||||
// The account's own today: "tomorrow" in a description means the
|
||||
// person's tomorrow, and the same key is the budget day below.
|
||||
const accountToday = await buildDateKey(userId);
|
||||
const today = todayOverride ?? accountToday;
|
||||
const { systemPrompt, userPrompt } = buildMedicationExtractionPrompt({
|
||||
text,
|
||||
today,
|
||||
@@ -239,7 +242,7 @@ async function handleExtract(request: NextRequest): Promise<Response> {
|
||||
// paying for their own key by the operator's ceiling. `resolveDailyCap`
|
||||
// charges the operator ceiling only when the chain's primary is the
|
||||
// operator's own credential; every other chain gets the user-plan ceiling.
|
||||
const dateKey = buildDateKey();
|
||||
const dateKey = accountToday;
|
||||
const estimatedTokens =
|
||||
MAX_OUTPUT_TOKENS +
|
||||
Math.ceil((systemPrompt.length + userPrompt.length) / 4);
|
||||
|
||||
Reference in new issue
Block a user