refactor(core,ai): 调整模块导入路径并移除废弃文件 - 修复 scheduler.py 中的导入路径错误,将 reports.service 改为 reports.services - 移除废弃的 app/core/background/task_queue.py 文件 - 移除废弃的 app/modules/ai_agent/adapters.py 文件 - 修复 ai_memory/service.py 中的导入路径错误,将 events.service 改为 events.services ```
96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
|
|
from sqlalchemy import func, select
|
|
|
|
from app.core.http.pagination import bounded_limit
|
|
from app.core.utils.time import utc_now
|
|
from app.modules.reports.constants import ReportStatus
|
|
|
|
|
|
def _money(value: Decimal | int | float | None) -> str:
|
|
"""Format a numeric value as a two-decimal money string."""
|
|
|
|
amount = Decimal(value or 0)
|
|
return f"{amount:,.2f}"
|
|
|
|
|
|
def _json_safe(value: Any) -> Any:
|
|
"""Convert nested report payloads into JSON-storable values."""
|
|
|
|
if isinstance(value, Decimal):
|
|
return float(value)
|
|
if isinstance(value, (datetime, date)):
|
|
return value.isoformat()
|
|
if isinstance(value, list):
|
|
return [_json_safe(item) for item in value]
|
|
if isinstance(value, dict):
|
|
return {key: _json_safe(item) for key, item in value.items()}
|
|
return value
|
|
|
|
|
|
def _next_code(prefix: str) -> str:
|
|
"""Build a compact unique code for generated report records."""
|
|
|
|
return f"{prefix}-{utc_now():%Y%m%d%H%M%S%f}"
|
|
|
|
|
|
def _rate(numerator: int | Decimal, denominator: int | Decimal) -> float:
|
|
"""Return a rounded percentage rate, using zero when the denominator is empty."""
|
|
|
|
if not denominator:
|
|
return 0.0
|
|
return round(float(numerator) / float(denominator) * 100, 2)
|
|
|
|
|
|
def _as_decimal(value: Decimal | int | float | None) -> Decimal:
|
|
return Decimal(str(value or 0))
|
|
|
|
|
|
class ReportQueryMixin:
|
|
def _count(self, model: type, *conditions: Any) -> int:
|
|
stmt = select(func.count()).select_from(model)
|
|
for condition in conditions:
|
|
stmt = stmt.where(condition)
|
|
return int(self.db.execute(stmt).scalar() or 0)
|
|
|
|
def _sum(self, column: Any, *conditions: Any) -> Decimal:
|
|
stmt = select(func.sum(column))
|
|
for condition in conditions:
|
|
stmt = stmt.where(condition)
|
|
return _as_decimal(self.db.execute(stmt).scalar())
|
|
|
|
def _avg(self, column: Any, *conditions: Any) -> float:
|
|
stmt = select(func.avg(column))
|
|
for condition in conditions:
|
|
stmt = stmt.where(condition)
|
|
value = self.db.execute(stmt).scalar()
|
|
return round(float(value or 0), 2)
|
|
|
|
def _group_counts(self, model: type, column: Any, *conditions: Any) -> dict[str, int]:
|
|
stmt = select(column, func.count()).select_from(model)
|
|
for condition in conditions:
|
|
stmt = stmt.where(condition)
|
|
stmt = stmt.group_by(column)
|
|
return {
|
|
str(key or ReportStatus.UNKNOWN): int(count)
|
|
for key, count in self.db.execute(stmt).all()
|
|
}
|
|
|
|
def _records(
|
|
self,
|
|
model: type,
|
|
*conditions: Any,
|
|
limit: int = 10,
|
|
order_by: Any | None = None,
|
|
) -> list[Any]:
|
|
stmt = select(model)
|
|
for condition in conditions:
|
|
stmt = stmt.where(condition)
|
|
if order_by is not None:
|
|
stmt = stmt.order_by(order_by)
|
|
else:
|
|
stmt = stmt.order_by(model.id.desc())
|
|
return list(self.db.execute(stmt.limit(bounded_limit(limit))).scalars())
|