## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
This commit was merged in pull request #7.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
"""Bảng giá và quy đổi token thành tiền — R09-T02.
|
||||
|
||||
Tách khỏi ``usage_tracker.py``: ghi nhận mức dùng và tính tiền là hai việc
|
||||
khác nhau. Bảng giá đổi theo nhà cung cấp, cách ghi nhận thì không.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import date, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
from ..config import CONFIG_DIR
|
||||
from . import model_pricing as mp
|
||||
|
||||
DEFAULT_PRICING = {
|
||||
"price_per_mtok_in_usd": 0.5, # USD per 1M input tokens (flat fallback rate)
|
||||
"price_per_mtok_out_usd": 1.5, # USD per 1M output tokens
|
||||
"price_per_mtok_cache_usd": 0.1, # USD per 1M cached tokens
|
||||
"currency": "USD", # display currency: USD | VND | JPY
|
||||
"usd_to_vnd": 25000.0,
|
||||
"usd_to_jpy": 150.0,
|
||||
# Per-model price table (USD / 1M tokens): {model: {"in","out","cache"}}.
|
||||
# Events whose model has an entry are costed with ITS rates; everything
|
||||
# else falls back to the flat price_per_mtok_* rates above. Edited in the
|
||||
# Monitoring Overview's pricing table.
|
||||
"model_prices": {},
|
||||
# Reference URL of the price list the table was filled from (set in
|
||||
# Settings; shown as a link beside the table — informational only, the
|
||||
# app never scrapes it).
|
||||
"pricing_url": "",
|
||||
}
|
||||
|
||||
_CURRENCY_FMT = {"USD": ("$", 4), "VND": ("₫", 0), "JPY": ("¥", 1)}
|
||||
|
||||
SUPPORTED_CURRENCIES = tuple(_CURRENCY_FMT)
|
||||
|
||||
def cost_usd(summary: Dict[str, Any], pricing: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Quy số token thành tiền (USD) theo bảng đơn giá, tách riêng vào/ra/cache."""
|
||||
p = {**DEFAULT_PRICING, **(pricing or {})}
|
||||
return {
|
||||
"in": summary.get("in", 0) / 1e6 * float(p["price_per_mtok_in_usd"]),
|
||||
"out": summary.get("out", 0) / 1e6 * float(p["price_per_mtok_out_usd"]),
|
||||
"cache": summary.get("cache", 0) / 1e6 * float(p["price_per_mtok_cache_usd"]),
|
||||
}
|
||||
|
||||
def cost_usd_events(events: List[Dict[str, Any]], pricing: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Per-bucket USD cost computed EVENT BY EVENT so the per-model price
|
||||
table applies: an event whose ``model`` has an entry in
|
||||
``pricing["model_prices"]`` is costed with that model's own rates; any
|
||||
other event uses the flat ``price_per_mtok_*`` rates. With an empty
|
||||
table this equals ``cost_usd(summarize(events), pricing)`` exactly."""
|
||||
p = {**DEFAULT_PRICING, **(pricing or {})}
|
||||
table = p.get("model_prices") or {}
|
||||
flat = {"in": float(p["price_per_mtok_in_usd"]),
|
||||
"out": float(p["price_per_mtok_out_usd"]),
|
||||
"cache": float(p["price_per_mtok_cache_usd"])}
|
||||
out = {"in": 0.0, "out": 0.0, "cache": 0.0}
|
||||
for e in events:
|
||||
rates = table.get(e.get("model", "")) or {}
|
||||
for bucket in ("in", "out", "cache"):
|
||||
try:
|
||||
rate = float(rates.get(bucket, flat[bucket]))
|
||||
except (TypeError, ValueError):
|
||||
rate = flat[bucket]
|
||||
out[bucket] += e.get(bucket, 0) / 1e6 * rate
|
||||
return out
|
||||
|
||||
def format_cost(usd: float, pricing: Dict[str, Any], digits: Optional[int] = None) -> str:
|
||||
"""Format a USD amount in the display currency. ``digits`` caps the number
|
||||
of decimal places (e.g. ``digits=2`` for the Total cost / Budget cards, so
|
||||
USD shows $1.23 not the default up-to-4 $1.2345) — never ADDS decimals to a
|
||||
currency that uses fewer (VND stays whole, JPY one place)."""
|
||||
p = {**DEFAULT_PRICING, **(pricing or {})}
|
||||
cur = p.get("currency", "USD")
|
||||
rate = {"USD": 1.0, "VND": float(p["usd_to_vnd"]), "JPY": float(p["usd_to_jpy"])}.get(cur, 1.0)
|
||||
symbol, cur_digits = _CURRENCY_FMT.get(cur, ("$", 2))
|
||||
if digits is not None:
|
||||
cur_digits = min(cur_digits, digits)
|
||||
value = usd * rate
|
||||
return f"{symbol}{value:,.{cur_digits}f}"
|
||||
|
||||
def format_cost_compact(usd: float, pricing: Dict[str, Any]) -> str:
|
||||
"""Compact cost format for the Dashboard chart's y-axis/endpoint labels —
|
||||
always 2 decimals (not format_cost's up-to-4 for USD) and abbreviated with
|
||||
K/M above 1,000/1,000,000, same convention as ``fmt_tokens``. The chart's
|
||||
y-axis label box is narrow; the longer full-precision string used to
|
||||
overflow it, visually clipping/obscuring the leading currency symbol."""
|
||||
p = {**DEFAULT_PRICING, **(pricing or {})}
|
||||
cur = p.get("currency", "USD")
|
||||
rate = {"USD": 1.0, "VND": float(p["usd_to_vnd"]), "JPY": float(p["usd_to_jpy"])}.get(cur, 1.0)
|
||||
symbol, _digits = _CURRENCY_FMT.get(cur, ("$", 2))
|
||||
value = usd * rate
|
||||
sign = "-" if value < 0 else ""
|
||||
value = abs(value)
|
||||
if value >= 1_000_000:
|
||||
body = f"{value / 1_000_000:,.2f}M"
|
||||
elif value >= 1_000:
|
||||
body = f"{value / 1_000:,.2f}K"
|
||||
else:
|
||||
body = f"{value:,.2f}"
|
||||
return f"{sign}{symbol}{body}"
|
||||
Reference in New Issue
Block a user