import re from typing import Any from sqlalchemy.orm import Session from app.application.feishu.delivery import send_text_if_configured from app.application.feishu.results import command_result from app.core.config import get_settings from app.modules.ai_agent.constants import AIResponseKey from app.modules.feishu.constants import FeishuCommandName, FeishuReplyType from app.modules.feishu.service import FeishuService from app.modules.reports.chart import lifecycle_chart_alt, render_lifecycle_chart from app.modules.reports.constants import ReportResponseKey from app.modules.reports.services import ReportService PROJECT_FINANCE_PATTERN = re.compile(r"^项目资金\s+(.+)$") FINANCE_COMMANDS = {"资金需求", "未来30天资金需求"} def handle_finance_command( db: Session, feishu: FeishuService, command_text: str, chat_id: str | None, actor: str, auto_reply: bool, ) -> dict[str, Any] | None: """Handle project cash-needs commands.""" project_match = PROJECT_FINANCE_PATTERN.fullmatch(command_text) if command_text not in FINANCE_COMMANDS and project_match is None: return None command = ( FeishuCommandName.PROJECT_FINANCE if project_match else FeishuCommandName.FINANCE_NEEDS ) if not get_settings().finance_needs_enabled: return _text_result( feishu, command, "项目资金需求分析", "项目资金需求分析尚未启用,请先配置并启用财务只读同步。", chat_id, actor, auto_reply, ) project_code = project_match.group(1).strip() if project_match else None service = ReportService(db) preview = service.project_finance_needs_report( project_code=project_code, include_ai=False, actor=actor, ) if project_code and not preview["items"]: return _text_result( feishu, command, "项目资金需求分析", f"未找到项目“{project_code}”,请使用稳定项目编号或展示编号。", chat_id, actor, auto_reply, ) if not preview["summary"]["data_available"]: return _text_result( feishu, command, "项目资金需求分析", "项目财务数据未接入或无有效记录,暂不生成资金分析报告。", chat_id, actor, auto_reply, ) report = service.project_finance_needs_report( project_code=project_code, include_ai=True, actor=actor, ) ai_analysis = report.get("ai_analysis") or {} if not ai_analysis.get(AIResponseKey.OK): return _text_result( feishu, command, "AI 暂不可用", "AI 当前不可用,本次项目资金分析报告未发送。请检查模型服务。", chat_id, actor, auto_reply, ) provider_response = None if auto_reply: provider_response = _send_finance_card(feishu, chat_id, report, actor) return command_result( command, FeishuReplyType.CARD, report[ReportResponseKey.TITLE], report[ReportResponseKey.CONTENT], provider_response, report[ReportResponseKey.LINES], ) def _text_result( feishu: FeishuService, command: FeishuCommandName, title: str, content: str, chat_id: str | None, actor: str, auto_reply: bool, ) -> dict[str, Any]: response = send_text_if_configured(feishu, chat_id, content, actor) if auto_reply else None return command_result(command, FeishuReplyType.TEXT, title, content, response) def _send_finance_card( feishu: FeishuService, chat_id: str | None, report: dict[str, Any], actor: str, ) -> dict[str, Any] | None: settings = get_settings() if not (settings.feishu_app_id and settings.feishu_app_secret): return None chart_data = { "period": report.get("as_of"), "finance": report.get("finance_chart_data"), } image_result = feishu.upload_image(render_lifecycle_chart(chart_data), actor) image_key = (image_result.get("data") or {}).get("image_key") if not image_key: raise ValueError("Feishu image upload did not return image_key") card = FeishuService.build_basic_card( report[ReportResponseKey.TITLE], report[ReportResponseKey.LINES], image_key=image_key, image_alt=lifecycle_chart_alt(chart_data), ) return feishu.send_card(card, receive_id=chat_id, actor=actor)