mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
refactor(coach): collapse the duplicate daily-cap export to OPERATOR_COST_CAP
The operator-cost cap carried two export names for one value; keep the accurate one (it gates only the operator-key path) and drop the alias.
This commit is contained in:
1 parent
e6ac604c83
commit
5f640e1420
4 files changed
+14
-19
No files matched your search
@@ -3,7 +3,6 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import { HttpError } from "@/lib/api-handler";
|
||||
import {
|
||||
buildDateKey,
|
||||
MAX_TOKENS_PER_USER_PER_DAY,
|
||||
OPERATOR_COST_CAP,
|
||||
USER_PLAN_CAP,
|
||||
resolveDailyCap,
|
||||
@@ -76,7 +75,7 @@ describe("budget", () => {
|
||||
|
||||
it("over-budget throws HttpError(429)", async () => {
|
||||
prismaMock.coachUsage.findUnique.mockResolvedValue({
|
||||
totalTokens: MAX_TOKENS_PER_USER_PER_DAY,
|
||||
totalTokens: OPERATOR_COST_CAP,
|
||||
});
|
||||
const { enforceBudget } = await import("../budget");
|
||||
await expect(enforceBudget("user-1", "2026-05-10")).rejects.toMatchObject({
|
||||
@@ -233,11 +232,13 @@ describe("reconcileSpend cached-token subtraction (F3)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("MAX_TOKENS_PER_USER_PER_DAY (F2 — reasoning-aware operator cap)", () => {
|
||||
describe("OPERATOR_COST_CAP (F2 — reasoning-aware operator cap)", () => {
|
||||
it("is sized for reasoning turns, not a single non-reasoning reply", () => {
|
||||
// Was 25_000 (≈ one gpt-5.x reasoning turn). Raised so the operator-key
|
||||
// path survives a normal day of reasoning turns.
|
||||
expect(MAX_TOKENS_PER_USER_PER_DAY).toBeGreaterThanOrEqual(150_000);
|
||||
expect(OPERATOR_COST_CAP).toBe(MAX_TOKENS_PER_USER_PER_DAY);
|
||||
expect(OPERATOR_COST_CAP).toBeGreaterThanOrEqual(150_000);
|
||||
// The user-plan cap is far more generous — a user's own egress is never
|
||||
// gated on the operator-cost ceiling.
|
||||
expect(USER_PLAN_CAP).toBeGreaterThan(OPERATOR_COST_CAP);
|
||||
});
|
||||
});
|
||||
@@ -34,13 +34,7 @@ import type { ProviderChainType } from "@/lib/ai/provider-chain";
|
||||
* surgery) while leaving room for a normal day of reasoning turns on the
|
||||
* operator's key. The cap stays a real ceiling on the operator's exposure.
|
||||
*/
|
||||
export const MAX_TOKENS_PER_USER_PER_DAY = 200_000;
|
||||
|
||||
/**
|
||||
* v1.21.0 (F1) — historical name retained for any external reference; the
|
||||
* operator-cost cap is the only cap the ledger enforces.
|
||||
*/
|
||||
export const OPERATOR_COST_CAP = MAX_TOKENS_PER_USER_PER_DAY;
|
||||
export const OPERATOR_COST_CAP = 200_000;
|
||||
|
||||
/**
|
||||
* v1.21.0 (F1) — the daily ceiling for a chain whose egress runs on the
|
||||
@@ -108,7 +102,7 @@ export async function getDailyTokenSpend(
|
||||
export async function enforceBudget(
|
||||
userId: string,
|
||||
dateKey: string = buildDateKey(),
|
||||
cap: number = MAX_TOKENS_PER_USER_PER_DAY,
|
||||
cap: number = OPERATOR_COST_CAP,
|
||||
): Promise<void> {
|
||||
const spent = await getDailyTokenSpend(userId, dateKey);
|
||||
if (spent >= cap) {
|
||||
@@ -148,7 +142,7 @@ export async function reserveBudget(
|
||||
userId: string,
|
||||
estimatedTokens: number,
|
||||
dateKey: string = buildDateKey(),
|
||||
cap: number = MAX_TOKENS_PER_USER_PER_DAY,
|
||||
cap: number = OPERATOR_COST_CAP,
|
||||
): Promise<ReserveBudgetResult> {
|
||||
const reserved =
|
||||
Number.isFinite(estimatedTokens) && estimatedTokens > 0
|
||||
|
||||
@@ -26,7 +26,7 @@ import { singleUserTurn } from "@/lib/ai/types";
|
||||
import {
|
||||
buildDateKey,
|
||||
getDailyTokenSpend,
|
||||
MAX_TOKENS_PER_USER_PER_DAY,
|
||||
OPERATOR_COST_CAP,
|
||||
recordSpend,
|
||||
} from "@/lib/ai/coach/budget";
|
||||
import {
|
||||
@@ -137,7 +137,7 @@ export async function deriveClarifyingQuestions(
|
||||
// Budget gate — same per-day ledger the Coach chat enforces.
|
||||
const dateKey = buildDateKey();
|
||||
const spent = await getDailyTokenSpend(userId, dateKey);
|
||||
if (spent >= MAX_TOKENS_PER_USER_PER_DAY) return fallback();
|
||||
if (spent >= OPERATOR_COST_CAP) return fallback();
|
||||
|
||||
const provider = await resolveProvider(userId);
|
||||
// v1.16.6 — same snapshot the Coach chat rides; lets the model
|
||||
|
||||
@@ -24,7 +24,7 @@ process.env.ENCRYPTION_KEY ??=
|
||||
|
||||
import { cookieJar, headerJar } from "./mock-next-headers";
|
||||
import { getPrismaClient, truncateAllTables } from "./setup";
|
||||
import { MAX_TOKENS_PER_USER_PER_DAY } from "@/lib/ai/coach/budget";
|
||||
import { OPERATOR_COST_CAP } from "@/lib/ai/coach/budget";
|
||||
|
||||
vi.mock("next/headers", async () => {
|
||||
const { cookieJar, headerJar } = await import("./mock-next-headers");
|
||||
@@ -227,7 +227,7 @@ describe("POST /api/insights/chat — integration", () => {
|
||||
data: {
|
||||
userId,
|
||||
dateKey,
|
||||
totalTokens: MAX_TOKENS_PER_USER_PER_DAY,
|
||||
totalTokens: OPERATOR_COST_CAP,
|
||||
messageCount: 50,
|
||||
},
|
||||
});
|
||||
@@ -251,7 +251,7 @@ describe("POST /api/insights/chat — integration", () => {
|
||||
const usage = await prisma.coachUsage.findUnique({
|
||||
where: { userId_dateKey: { userId, dateKey } },
|
||||
});
|
||||
expect(usage?.totalTokens).toBe(MAX_TOKENS_PER_USER_PER_DAY);
|
||||
expect(usage?.totalTokens).toBe(OPERATOR_COST_CAP);
|
||||
});
|
||||
|
||||
it("returns 404 (not 403) when conversationId belongs to another user", async () => {
|
||||
|
||||
Reference in new issue
Block a user