관리자 사용량 근거를 정교화
This commit is contained in:
parent
ccdcfcd2f5
commit
707dba4f8f
10 changed files with 1136 additions and 209 deletions
184
apps/api/app/test_admin_usage_postgres.py
Normal file
184
apps/api/app/test_admin_usage_postgres.py
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
"""격리 PostgreSQL에서 관리자 비용 원장 SQL을 검증하는 선택형 통합 테스트."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import asyncpg
|
||||
|
||||
from . import db
|
||||
from .routes import admin as admin_routes
|
||||
from .services.llm_pricing import estimate_reference_cost
|
||||
|
||||
|
||||
TEST_DATABASE_URL = os.getenv("VIGNETTE_USAGE_TEST_DATABASE_URL", "").strip()
|
||||
|
||||
|
||||
@unittest.skipUnless(
|
||||
TEST_DATABASE_URL,
|
||||
"VIGNETTE_USAGE_TEST_DATABASE_URL이 설정된 새 격리 DB에서만 실행",
|
||||
)
|
||||
class AdminUsagePostgresTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self) -> None:
|
||||
parsed = urlparse(TEST_DATABASE_URL)
|
||||
if parsed.path != "/vignette_usage_test":
|
||||
self.fail("격리 DB 이름은 vignette_usage_test여야 한다")
|
||||
if db._pool is not None:
|
||||
self.fail("기존 애플리케이션 DB pool이 있는 프로세스에서는 실행할 수 없다")
|
||||
|
||||
bootstrap = await asyncpg.connect(TEST_DATABASE_URL)
|
||||
try:
|
||||
# 기존 스키마를 지우지 않는다. 이미 쓰인 DB라면 CREATE가 실패해 닫힌다.
|
||||
await bootstrap.execute(
|
||||
"""
|
||||
CREATE SCHEMA app;
|
||||
CREATE TABLE app.turns (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
speaker TEXT NOT NULL,
|
||||
llm_provider TEXT,
|
||||
model TEXT,
|
||||
tokens_in INT,
|
||||
tokens_out INT,
|
||||
cost_usd NUMERIC(12,6),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
"""
|
||||
)
|
||||
migration = (
|
||||
Path(__file__).resolve().parents[3]
|
||||
/ "infra"
|
||||
/ "db"
|
||||
/ "init"
|
||||
/ "18_admin_usage_ledger_index.sql"
|
||||
).read_text(encoding="utf-8")
|
||||
await bootstrap.execute(migration)
|
||||
finally:
|
||||
await bootstrap.close()
|
||||
|
||||
db._pool = await asyncpg.create_pool(
|
||||
dsn=TEST_DATABASE_URL,
|
||||
min_size=1,
|
||||
max_size=2,
|
||||
init=db._init_connection,
|
||||
)
|
||||
|
||||
async def asyncTearDown(self) -> None:
|
||||
if db._pool is not None:
|
||||
await db._pool.close()
|
||||
db._pool = None
|
||||
|
||||
async def test_usage_sql_preserves_pricing_boundaries_and_fake_exclusion(self) -> None:
|
||||
observed_at = datetime.now(timezone.utc)
|
||||
token_pairs = [(5_521, 210)] * 20 + [(5_530, 207)]
|
||||
assert db._pool is not None
|
||||
async with db._pool.acquire() as conn:
|
||||
await conn.executemany(
|
||||
"""
|
||||
INSERT INTO app.turns (
|
||||
speaker, llm_provider, model, tokens_in, tokens_out, cost_usd, created_at
|
||||
) VALUES ('client', $1, $2, $3, $4, 0, $5)
|
||||
""",
|
||||
[
|
||||
(
|
||||
"agy_cli",
|
||||
"gemini-3.7-flash-high",
|
||||
tokens_in,
|
||||
tokens_out,
|
||||
observed_at,
|
||||
)
|
||||
for tokens_in, tokens_out in token_pairs
|
||||
],
|
||||
)
|
||||
await conn.executemany(
|
||||
"""
|
||||
INSERT INTO app.turns (
|
||||
speaker, llm_provider, model, tokens_in, tokens_out, cost_usd, created_at
|
||||
) VALUES ('client', ' E2E ', ' FAKE-CLIENT ', 1, 1, 0, $1)
|
||||
""",
|
||||
[(observed_at,)] * 6,
|
||||
)
|
||||
|
||||
expected = estimate_reference_cost(
|
||||
provider="agy_cli",
|
||||
model="gemini-3.7-flash-high",
|
||||
tokens_in=115_950,
|
||||
tokens_out=4_407,
|
||||
priced_at=observed_at,
|
||||
)
|
||||
self.assertIsNotNone(expected)
|
||||
assert expected is not None
|
||||
|
||||
usage = await admin_routes._usage_from_database(window_days=36_500)
|
||||
|
||||
self.assertEqual(usage.total_turns, 21)
|
||||
self.assertEqual(usage.token_metered_turns, 21)
|
||||
self.assertEqual(usage.tokens_in, 115_950)
|
||||
self.assertEqual(usage.tokens_out, 4_407)
|
||||
self.assertEqual(usage.cost_usd, round(expected.cost_usd, 6))
|
||||
self.assertEqual(len(usage.by_provider), 1)
|
||||
self.assertEqual(usage.by_provider[0].model, "gemini-3.7-flash-high")
|
||||
self.assertEqual(usage.by_provider[0].cost_basis, "reference_upper_bound")
|
||||
|
||||
async with db._pool.acquire() as conn:
|
||||
index_definition = await conn.fetchval(
|
||||
"""
|
||||
SELECT indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'app'
|
||||
AND tablename = 'turns'
|
||||
AND indexname = 'idx_turns_admin_usage_created_at'
|
||||
"""
|
||||
)
|
||||
self.assertIn("created_at DESC", index_definition or "")
|
||||
self.assertIn("speaker = 'client'", index_definition or "")
|
||||
|
||||
async with db._pool.acquire() as conn:
|
||||
await conn.executemany(
|
||||
"""
|
||||
INSERT INTO app.turns (
|
||||
speaker, llm_provider, model, tokens_in, tokens_out, cost_usd, created_at
|
||||
) VALUES ('client', 'agy_cli', 'gemini-3.1-pro-high', 150000, 10000, 0, $1)
|
||||
""",
|
||||
[(observed_at,), (observed_at,)],
|
||||
)
|
||||
|
||||
tier_usage = await admin_routes._usage_from_database(window_days=36_500)
|
||||
tier_row = next(
|
||||
item for item in tier_usage.by_provider if item.model == "gemini-3.1-pro-high"
|
||||
)
|
||||
self.assertEqual(tier_row.turns, 2)
|
||||
self.assertEqual(tier_row.cost_usd, 0.84)
|
||||
|
||||
async with db._pool.acquire() as conn:
|
||||
await conn.executemany(
|
||||
"""
|
||||
INSERT INTO app.turns (
|
||||
speaker, llm_provider, model, tokens_in, tokens_out, cost_usd, created_at
|
||||
) VALUES ('client', 'agy_cli', 'gemini-3.7-flash-high', 1000000, 1000000, 0, $1)
|
||||
""",
|
||||
[
|
||||
(datetime(2026, 8, 12, 12, tzinfo=timezone.utc),),
|
||||
(datetime(2026, 8, 13, 12, tzinfo=timezone.utc),),
|
||||
],
|
||||
)
|
||||
|
||||
partial_usage = await admin_routes._usage_from_database(window_days=36_500)
|
||||
partial_row = next(
|
||||
item
|
||||
for item in partial_usage.by_provider
|
||||
if item.model == "gemini-3.7-flash-high"
|
||||
)
|
||||
self.assertEqual(partial_row.turns, 23)
|
||||
self.assertEqual(partial_row.cost_usd, round(expected.cost_usd + 4.5, 6))
|
||||
self.assertEqual(partial_usage.cost_basis, "partial_upper_bound")
|
||||
self.assertEqual(partial_usage.budget.cost_basis, "partial_upper_bound")
|
||||
self.assertEqual(partial_row.cost_basis, "partial_upper_bound")
|
||||
self.assertIn("일부 호출 미산정", partial_row.rate_label or "")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue