from __future__ import annotations import json from datetime import datetime, timedelta, timezone from typing import Any from sqlalchemy import func, select from sqlalchemy.orm import Session from app.core.logging import get_logger from app.models.financial_record import ExpenseClaim from app.services.runtime_chat import RuntimeChatService logger = get_logger("app.services.hermes_expense_report") class HermesExpenseReportService: def __init__(self, db: Session) -> None: self.db = db self.chat_service = RuntimeChatService(db) def generate_weekly_report(self, log_id: str | None = None) -> None: logger.info("Starting Hermes weekly expense report generation...") # 1. 聚合数据 aggregated_data = self._aggregate_recent_expenses(days=7) if not aggregated_data.get("total_amount"): logger.info("No expense data in the last 7 days. Skipping report.") return # 2. 传入大模型分析 report_markdown = self._generate_insights_with_llm(aggregated_data) if not report_markdown: logger.warning("Failed to generate expense report from LLM.") return # 3. 模拟发送报告 self._deliver_report(report_markdown, log_id) logger.info("Hermes weekly expense report generation completed.") def _aggregate_recent_expenses(self, days: int = 7) -> dict[str, Any]: target_date = datetime.now(timezone.utc) - timedelta(days=days) # 基础过滤:最近N天且不是驳回状态的单据 base_filter = [ ExpenseClaim.occurred_at >= target_date, ExpenseClaim.status != "rejected" ] # 1. 按部门汇总 dept_stmt = select( ExpenseClaim.department_name, func.sum(ExpenseClaim.amount).label("total") ).where(*base_filter).group_by(ExpenseClaim.department_name) dept_results = self.db.execute(dept_stmt).all() by_department = {row.department_name or "Unknown": float(row.total or 0) for row in dept_results} # 2. 按类目汇总 type_stmt = select( ExpenseClaim.expense_type, func.sum(ExpenseClaim.amount).label("total") ).where(*base_filter).group_by(ExpenseClaim.expense_type) type_results = self.db.execute(type_stmt).all() by_expense_type = {row.expense_type or "Unknown": float(row.total or 0) for row in type_results} # 3. 总花费 total_amount = sum(by_department.values()) return { "period": f"Last {days} days", "total_amount": total_amount, "by_department": by_department, "by_expense_type": by_expense_type } def _generate_insights_with_llm(self, data: dict[str, Any]) -> str | None: system_prompt = ( "你是公司的财务分析专家。请根据提供的最近期业务开销数据,撰写一份简洁有力的【高管费控洞察周报】。\n" "要求:\n" "1. 不要机械地罗列数字,要像人一样指出异常(例如:哪个部门花钱最多?打车费是不是异常高?)。\n" "2. 给出 1 条削减成本的实操建议。\n" "3. 纯 Markdown 格式输出,不超过 300 字。" ) messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"开销统计数据:\n{json.dumps(data, ensure_ascii=False, indent=2)}"} ] response = self.chat_service.complete( messages, max_tokens=800, temperature=0.4 ) return response def _deliver_report(self, report_markdown: str, log_id: str | None) -> None: # TODO: 未来在这里接入企微/钉钉机器人或邮件发送接口 logger.info(f"\n================ Hermes Weekly Report [LogID: {log_id}] ================\n" f"{report_markdown}\n" f"==========================================================================")