관리자 증거와 계정 흐름을 정돈

This commit is contained in:
Yun Chan 2026-08-30 00:01:17 +09:00
parent 21bbe3f98d
commit 34aff65cb0
9 changed files with 1100 additions and 193 deletions

View file

@ -45,6 +45,15 @@ interface AdminUsersResponse {
users: AdminManagedUser[];
}
type UsageCostBasis =
| "provider_estimate"
| "provider_reported"
| "reference_rate"
| "reference_upper_bound"
| "partial"
| "partial_upper_bound"
| "unavailable";
interface AdminUsageBreakdown {
provider: string;
model: string;
@ -54,6 +63,11 @@ interface AdminUsageBreakdown {
tokens_in: number;
tokens_out: number;
cost_usd: number;
recorded_cost_usd?: number;
estimated_cost_usd?: number;
cost_basis?: UsageCostBasis;
rate_label?: string | null;
rate_source_url?: string | null;
}
interface AdminUsageDailyCost {
@ -62,13 +76,15 @@ interface AdminUsageDailyCost {
tokens_in: number;
tokens_out: number;
cost_usd: number;
cost_basis?: UsageCostBasis;
}
interface AdminUsageBudget {
limit_usd: number;
used_ratio: number;
remaining_usd: number | null;
status: "disabled" | "ok" | "warn" | "exceeded";
status: "disabled" | "ok" | "warn" | "exceeded" | "indeterminate";
cost_basis?: UsageCostBasis;
}
interface AdminUsageEvaluatorCache {
@ -94,6 +110,9 @@ interface AdminUsageResponse {
tokens_in: number;
tokens_out: number;
cost_usd: number;
recorded_cost_usd?: number;
estimated_cost_usd?: number;
cost_basis?: UsageCostBasis;
budget: AdminUsageBudget;
evaluator_cache?: AdminUsageEvaluatorCache;
by_provider: AdminUsageBreakdown[];
@ -927,20 +946,22 @@ test.describe("admin route guards", () => {
durable: true,
generated_at: 1_783_990_800,
window_days: 30,
total_turns: 37,
metered_turns: 37,
token_metered_turns: 35,
total_turns: 53,
metered_turns: 53,
token_metered_turns: 51,
token_unmetered_turns: 2,
tokens_in: 160_703,
tokens_out: 19_629,
cost_usd: 7.023222,
tokens_in: 240_950,
tokens_out: 22_907,
cost_usd: 7.064689,
recorded_cost_usd: 6.9612,
estimated_cost_usd: 0.062022,
estimated_cost_usd: 0.103489,
cost_basis: "reference_upper_bound",
budget: {
limit_usd: 20,
used_ratio: 0.3512,
remaining_usd: 12.976778,
used_ratio: 0.3532,
remaining_usd: 12.935311,
status: "ok",
cost_basis: "reference_upper_bound",
},
evaluator_cache: {
enabled: true,
@ -981,24 +1002,45 @@ test.describe("admin route guards", () => {
},
{
provider: "agy_cli",
model: "gemini-3.6-flash-high",
turns: 5,
token_metered_turns: 5,
model: "gemini-3.7-flash-high",
turns: 21,
token_metered_turns: 21,
token_unmetered_turns: 0,
tokens_in: 35_703,
tokens_out: 1_129,
cost_usd: 0.062022,
tokens_in: 115_950,
tokens_out: 4_407,
cost_usd: 0.103489,
recorded_cost_usd: 0,
estimated_cost_usd: 0.062022,
cost_basis: "reference_rate",
estimated_cost_usd: 0.103489,
cost_basis: "reference_upper_bound",
rate_label:
"Google Gemini 3.6 Flash 표준 단가 · 입력 $1.50/M · 캐시 $0.15/M · 출력 $7.50/M",
"Google Gemini 3.7 Flash 프로모션 표준 단가(2026-12-31까지) · 입력 $0.75/M · 캐시 $0.075/M · 출력 $3.75/M",
},
],
daily_cost: [
{ day: "2026-07-13", turns: 8, tokens_in: 32_000, tokens_out: 4_800, cost_usd: 1.42 },
{ day: "2026-07-14", turns: 10, tokens_in: 41_000, tokens_out: 6_100, cost_usd: 2.08 },
{ day: "2026-07-15", turns: 17, tokens_in: 87_703, tokens_out: 8_729, cost_usd: 3.183222 },
{
day: "2026-07-13",
turns: 8,
tokens_in: 32_000,
tokens_out: 4_800,
cost_usd: 1.42,
cost_basis: "provider_reported",
},
{
day: "2026-07-14",
turns: 10,
tokens_in: 41_000,
tokens_out: 6_100,
cost_usd: 2.08,
cost_basis: "provider_reported",
},
{
day: "2026-07-15",
turns: 35,
tokens_in: 167_950,
tokens_out: 12_007,
cost_usd: 3.564689,
cost_basis: "reference_upper_bound",
},
],
},
},
@ -1014,17 +1056,21 @@ test.describe("admin route guards", () => {
);
await expect(page.getByText("운영 DB 원장").first()).toBeVisible();
// 2026-07-27 D7: 합계 금액은 화면에 소수 2자리로 표시하고 원본 정밀도는 title 로 옮겼다.
await expect(page.locator(".aic-ledger")).toContainText("$7.02");
await expect(page.locator(".aic-ledger b").first()).toHaveAttribute("title", /7\.023222/);
await expect(page.locator(".aic-budget")).toContainText("94.6%");
await expect(page.locator(".aic-ledger")).toContainText("≤$7.06");
await expect(page.locator(".aic-ledger b").first()).toHaveAttribute("title", /≤\$7\.064689/);
await expect(page.locator(".aic-budget")).toContainText("잔여 ≥$12.94 · ≤35.3% 사용");
await expect(page.locator(".aic-table")).toContainText("gpt-5-mini");
await expect(page.locator(".aic-table")).toContainText("gemini-3.6-flash-high");
await expect(page.locator(".aic-table")).toContainText("참조단가");
await expect(page.locator(".aic-table")).toContainText("gemini-3.7-flash-high");
await expect(page.locator(".aic-table")).toContainText("참조 상한");
await expect(page.locator(".aic-table")).toContainText("≤$0.10");
await expect(page.locator(".aic-table")).toContainText("≤$0.0049");
await expect(page.locator(".aic-table")).toContainText("SDK 추정");
await expect(page.locator(".aic-table")).toContainText("미계량");
await expect(page.locator(".aic-table")).toContainText("$0.06");
await expect(page.locator(".aic-table")).toContainText("0.9%");
await expect(page.locator(".aic-ledger")).toContainText("기록 $6.96 · 참조 $0.06");
await expect(page.locator(".aic-table")).toContainText("$0.10");
await expect(page.locator(".aic-table")).toContainText("$0.0049");
await expect(page.locator(".aic-table tbody tr").first().locator("td").nth(6)).toHaveText("—");
await expect(page.locator(".aic-ledger")).toContainText("기록 $6.96 · 참조 ≤$0.10 · 참조 상한");
await expect(page.locator(".aic-chart__day").last()).toContainText("≤$3.56");
await expect(page.locator(".aic-cache-score")).toContainText("80%");
await expect(page.getByLabel("AI 기본 모델")).toHaveValue("gateway-default");
await expect(page.getByLabel("AI 엔진 공급자").locator("option")).toHaveCount(6);
@ -1072,6 +1118,95 @@ test.describe("admin route guards", () => {
await expectNoHorizontalOverflow(page);
});
test("shows partial AI cost without inventing a per-call average", async ({ page }) => {
await mockAdminSession(
page,
{
user_id: "partial-cost-admin",
email: "partial-cost-admin@twentyoz.kr",
display_name: "Partial Cost Admin",
role: "admin",
admin_access: true,
super_admin: true,
onboarding_completed_at: 1_782_900_000,
},
{
adminUsage: {
source: "database",
durable: true,
generated_at: 1_783_990_800,
window_days: 30,
total_turns: 2,
metered_turns: 2,
token_metered_turns: 2,
token_unmetered_turns: 0,
tokens_in: 2_000_000,
tokens_out: 2_000_000,
cost_usd: 4.5,
recorded_cost_usd: 0,
estimated_cost_usd: 4.5,
cost_basis: "partial",
budget: {
limit_usd: 20,
used_ratio: 0.225,
remaining_usd: 15.5,
status: "indeterminate",
cost_basis: "partial",
},
evaluator_cache: {
enabled: false,
entries: 0,
hits: 0,
misses: 0,
stores: 0,
evictions: 0,
requests: 0,
hit_rate: 0,
},
by_provider: [
{
provider: "agy_cli",
model: "gemini-3.7-flash-high",
turns: 2,
token_metered_turns: 2,
token_unmetered_turns: 0,
tokens_in: 2_000_000,
tokens_out: 2_000_000,
cost_usd: 4.5,
recorded_cost_usd: 0,
estimated_cost_usd: 4.5,
cost_basis: "partial",
rate_label: "일부 호출 미산정 · 표시액은 산정 가능분 합계",
},
],
daily_cost: [
{
day: "2026-08-13",
turns: 2,
tokens_in: 2_000_000,
tokens_out: 2_000_000,
cost_usd: 4.5,
cost_basis: "partial",
},
],
},
},
);
await page.goto("/admin/ai");
const row = page.locator(".aic-table tbody tr").filter({ hasText: "gemini-3.7-flash-high" });
await expect(row).toContainText("$4.50+");
await expect(row).toContainText("일부 산정");
await expect(row.locator("td").nth(5)).toHaveText("—");
await expect(row.locator("td").nth(6)).toHaveText("—");
await expect(page.locator(".aic-ledger b").first()).toHaveText("$4.50+");
await expect(page.locator(".aic-ledger article").last().locator("b")).toHaveText("—");
await expect(page.locator(".aic-budget")).toContainText("예산 미확정");
await expect(page.locator(".aic-budget")).toContainText("잔여 ≤$15.50 · ≥22.5% 사용");
await expect(page.locator(".aic-chart__value")).toHaveText("$4.50+");
});
test("keeps the boot diagnostic overlay off a rendered AI operations page", async ({ page }) => {
await mockAdminSession(page, {
user_id: "ai-watchdog-admin",