CI / test (push) Canceled after 0s
## 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>
121 lines
5.4 KiB
Python
121 lines
5.4 KiB
Python
"""DashboardQueryService - read-only usage/cost queries for the Dashboard
|
|
screen (R08-T13, extracted from ``ui/dashboard_tab.py::DashboardTab``, lines
|
|
196-199/256-261/284-322 of the original 437-line file: ``_pricing``,
|
|
``_period_range``'s date-math, and the ``period_totals``/``period_breakdown``
|
|
calls ``_refresh_chart`` made directly).
|
|
|
|
``ui/dashboard_tab.py`` called ``core/usage_tracker.py``/``core/model_
|
|
pricing.py`` directly from FIVE different methods spread across what is now
|
|
three widgets (``token_usage_card_widget.py``, ``usage_chart_widget.py``,
|
|
``habits_widget.py``) — each recomputing the same merged pricing dict. This
|
|
service is the one place that merge happens now; the three widgets share it
|
|
instead of each calling ``core.usage_tracker``/``core.model_pricing`` on
|
|
their own.
|
|
|
|
Pure Python: no Qt. Wraps ``core/usage_tracker.py`` (a plain-Python module
|
|
already) rather than reimplementing any of its date/cost math.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, timedelta
|
|
from typing import Any, Dict, List, Tuple
|
|
|
|
|
|
class DashboardQueryService:
|
|
"""Usage/cost queries scoped to one ``AppContext``.
|
|
|
|
Args:
|
|
ctx: ``AppContext`` — read for ``ctx.config`` (pricing table,
|
|
currency, budget) and nothing else; this class does no I/O of
|
|
its own beyond what ``core.usage_tracker`` already does.
|
|
directory: Optional custom usage directory. If None, uses default USAGE_DIR.
|
|
"""
|
|
|
|
def __init__(self, ctx: Any, directory: Optional[Path] = None) -> None:
|
|
"""``directory`` để None thì đọc thư mục telemetry mặc định; test trỏ nó vào
|
|
``tmp_path`` để không chạm dữ liệu thật.
|
|
"""
|
|
self.ctx = ctx
|
|
self._directory = directory
|
|
|
|
def pricing(self) -> Dict[str, Any]:
|
|
"""The merged price table (defaults + user overrides), synced from
|
|
Monitoring's model-pricing table first so cost figures always agree
|
|
between the two screens."""
|
|
from cowork_local.core import model_pricing as mp
|
|
from cowork_local.core import usage_tracker as ut
|
|
|
|
mp.sync_to_usage(self.ctx.config)
|
|
return {**ut.DEFAULT_PRICING, **(self.ctx.config.data.get("usage") or {})}
|
|
|
|
def period_range(self, granularity: str, offset: int) -> Tuple[date, date]:
|
|
"""The SELECTED period as an inclusive ``(start, end)`` date range —
|
|
drives every widget on the screen (cards, chart, habits)."""
|
|
from cowork_local.core import usage_tracker as ut
|
|
|
|
start, end = ut.period_bounds(granularity, offset)
|
|
return start, end - timedelta(days=1) # load_events end is inclusive
|
|
|
|
def summary(self, start: date, end: date) -> Dict[str, Any]:
|
|
"""Everything the stat cards + habits panel need for one period:
|
|
the raw events, ``usage_tracker.summarize``'s aggregate stats, the
|
|
per-bucket costs, and their total — computed once so both widgets
|
|
read the same numbers instead of loading events twice."""
|
|
from cowork_local.core import usage_tracker as ut
|
|
|
|
events = ut.load_events(start, end, directory=self._directory)
|
|
pricing = self.pricing()
|
|
stats = ut.summarize(events)
|
|
costs = ut.cost_usd_events(events, pricing)
|
|
return {
|
|
"events": events,
|
|
"pricing": pricing,
|
|
"stats": stats,
|
|
"costs": costs,
|
|
"total_cost": sum(costs.values()),
|
|
}
|
|
|
|
def chart_series(self, granularity: str, offset: int, metric: str
|
|
) -> List[Tuple[str, float]]:
|
|
"""``(label, value)`` points for the spline chart — WEEK -> 7 days,
|
|
MONTH -> weeks, YEAR -> 12 months, in whichever ``metric``
|
|
("tokens" | "cost") was selected."""
|
|
from cowork_local.core import usage_tracker as ut
|
|
|
|
events = ut.load_events(directory=self._directory) # all events; breakdown slices by period
|
|
pricing = self.pricing()
|
|
parts = ut.period_breakdown(events, granularity, pricing, offset=offset)
|
|
mi = 0 if metric == "tokens" else 1 # (label, tokens, cost) -> +1 for the value
|
|
return [(row[0], float(row[mi + 1])) for row in parts]
|
|
|
|
def period_totals(self, granularity: str, offset: int) -> Tuple[float, float]:
|
|
"""``(tokens, cost)`` totals for one period — used to compute the
|
|
vs-previous-period delta the chart's reference line shows."""
|
|
from cowork_local.core import usage_tracker as ut
|
|
|
|
events = ut.load_events(directory=self._directory)
|
|
return ut.period_totals(events, granularity, self.pricing(), offset)
|
|
|
|
def period_range_label(self, granularity: str, offset: int) -> str:
|
|
"""Nhãn hiển thị của một kỳ (tuần/tháng/năm cộng độ lệch)."""
|
|
from cowork_local.core import usage_tracker as ut
|
|
|
|
return ut.period_range_label(granularity, offset)
|
|
|
|
def budget_status(self):
|
|
"""Tình trạng ngân sách: đã dùng bao nhiêu, còn lại bao nhiêu, có vượt ngưỡng chưa."""
|
|
from cowork_local.core import usage_tracker as ut
|
|
|
|
return ut.budget_status(self.ctx.config)
|
|
|
|
def set_budget(self, amount: float, currency: str) -> None:
|
|
"""Đặt hạn mức ngân sách mới — mở một chu kỳ đếm mới, chi tiêu trước đó không
|
|
còn được tính vào.
|
|
"""
|
|
from cowork_local.core import usage_tracker as ut
|
|
|
|
ut.set_budget(self.ctx.config, amount, currency)
|
|
|
|
|
|
__all__ = ["DashboardQueryService"]
|