Files
company-ai-platform/app/application/feishu/handlers/market.py
JiuContinent db751f03b4 ```
refactor(Dockerfile): 使用requirements.txt替代硬编码依赖

将Dockerfile中的硬编码pip包列表替换为通过requirements.txt文件安装,
提高依赖管理的灵活性和可维护性。

feat(scheduling): 移除内置APScheduler,采用独立调度系统

移除app/core/background/scheduler.py中原来的APScheduler实现,
改为使用新的应用级调度系统app.application.scheduling。

refactor(task_queue): 调整任务队列模块结构和导入路径

将任务队列相关常量从app.core.background.task_queue.constants迁移至
app.tasks.constants,并更新所有相关导入路径和引用。

refactor(events): 将事件服务重构为独立的应用层组件

将事件分发逻辑从核心层迁移到应用层,使用app.application.events.EventDispatchService
替代原有的app.modules.events.services.EventService。

feat(ai_memory): 增强AI记忆自动写入的安全策略

新增ai_memory_blocked_content_terms配置项用于阻止敏感内容,
添加TTL过期机制控制自动写入条目的生命周期。

fix(security): 强化生产环境安全验证机制

增加model_validator确保生产环境中数据库连接、API密钥、CORS设置等
关键安全配置符合要求。

feat(risks): 优化风险事件操作动作的外键约束

为RiskEventAction模型的风险事件ID字段添加外键约束,
防止孤立记录并增强数据完整性。

refactor(audit): 优化审计服务方法命名和事务处理

将AuditService的log方法重命名为record以反映其阶段行为,
并调整事务提交时机以提高性能。

feat(events): 增强领域事件并发处理和响应模型

添加事件锁定机制防止重复处理,更新API响应模型以提供
更准确的数据类型定义。
```
2026-07-15 16:36:42 +08:00

244 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import re
from typing import Any
from fastapi import HTTPException
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.feishu.constants import FeishuCommandName, FeishuReplyType
from app.modules.feishu.service import FeishuService
from app.modules.market.chart import render_market_chart
from app.modules.market.service import MarketService
STOCK_ANALYSIS_PATTERN = re.compile(
r"^(?:股票分析|估值分析|财报分析)\s+([0-9]{6}(?:\.(?:SH|SZ|BJ))?)$", re.IGNORECASE
)
WATCHLIST_ADD_PATTERN = re.compile(r"^加入自选\s+([0-9]{6}(?:\.(?:SH|SZ|BJ))?)$", re.IGNORECASE)
MARKET_COMMANDS = {
"市场分析",
"今日收盘分析",
"本周市场分析",
"宏观金融分析",
"最新公告",
}
INDUSTRY_ANALYSIS_PATTERN = re.compile(r"^行业分析\s+(.+)$")
STOCK_COMPARE_PATTERN = re.compile(
r"^股票对比\s+([0-9]{6}(?:\.(?:SH|SZ|BJ))?)\s+"
r"([0-9]{6}(?:\.(?:SH|SZ|BJ))?)$",
re.IGNORECASE,
)
def handle_market_command(
db: Session,
feishu: FeishuService,
text: str,
chat_id: str | None,
actor: str,
auto_reply: bool,
) -> dict[str, Any] | None:
"""Handle market analysis and watchlist commands."""
stock = STOCK_ANALYSIS_PATTERN.fullmatch(text)
add = WATCHLIST_ADD_PATTERN.fullmatch(text)
industry = INDUSTRY_ANALYSIS_PATTERN.fullmatch(text)
comparison = STOCK_COMPARE_PATTERN.fullmatch(text)
if (
text not in MARKET_COMMANDS
and text != "查看自选"
and not stock
and not add
and not industry
and not comparison
):
return None
if not get_settings().market_analysis_enabled:
return _text_result(
feishu,
FeishuCommandName.MARKET_OVERVIEW,
"市场分析",
"市场分析尚未启用,请配置市场数据源后启用。",
chat_id,
actor,
auto_reply,
)
service = MarketService(db)
if add:
if get_settings().read_only_mode:
return _text_result(
feishu,
FeishuCommandName.WATCHLIST_ADD,
"自选股",
"当前为只读模式,不能修改自选股。请由管理员启用操作后重试。",
chat_id,
actor,
auto_reply,
)
item = service.add_watchlist(actor, add.group(1))
return _text_result(
feishu,
FeishuCommandName.WATCHLIST_ADD,
"自选股",
f"已加入自选:{item['symbol']}",
chat_id,
actor,
auto_reply,
)
if text == "查看自选":
items = service.watchlist(actor)
content = "自选股:" + ("".join(item["symbol"] for item in items) or "暂无")
return _text_result(
feishu,
FeishuCommandName.WATCHLIST_LIST,
"自选股",
content,
chat_id,
actor,
auto_reply,
)
if text == "最新公告":
items = service.announcements(limit=10)["items"]
content = (
"最新公告:\n"
+ "\n".join(
f"- {item['announcement_date']} {item['symbol'] or '市场'}{item['title']}"
for item in items
)
if items
else "公告元数据尚未接入。"
)
return _text_result(
feishu,
FeishuCommandName.MARKET_ANNOUNCEMENTS,
"最新公告",
content,
chat_id,
actor,
auto_reply,
)
if industry:
try:
data = service.industry_analysis(industry.group(1).strip())
content = (
f"{data['industry']} 平均涨跌 {data['average_pct_change']}%\n"
+ "\n".join(
f"- {item['name']}{item['symbol']}{item['pct_change']}%"
for item in data["items"][:10]
)
)
except HTTPException:
content = "未找到该行业的最新市场数据。"
return _text_result(
feishu,
FeishuCommandName.MARKET_OVERVIEW,
"行业分析",
content,
chat_id,
actor,
auto_reply,
)
if comparison:
try:
content = service.compare_stocks([comparison.group(1), comparison.group(2)])["content"]
except HTTPException:
content = "至少一只股票缺少可用行情,暂时无法比较。"
return _text_result(
feishu,
FeishuCommandName.STOCK_ANALYSIS,
"股票对比",
content,
chat_id,
actor,
auto_reply,
)
return _analysis_result(service, feishu, text, stock.group(1) if stock else None, chat_id, actor, auto_reply)
def _analysis_result(
service: MarketService,
feishu: FeishuService,
text: str,
symbol: str | None,
chat_id: str | None,
actor: str,
auto_reply: bool,
) -> dict[str, Any]:
command = FeishuCommandName.STOCK_ANALYSIS if symbol else FeishuCommandName.MARKET_OVERVIEW
if text == "宏观金融分析":
command = FeishuCommandName.MARKET_MACRO
try:
if symbol:
report = service.stock_analysis(symbol, True, actor)
elif text == "本周市场分析":
report = service.weekly_overview(include_ai=True, actor=actor)
elif text == "宏观金融分析":
report = service.macro_analysis(include_ai=True, actor=actor)
else:
report = service.market_overview(include_ai=True, actor=actor)
except HTTPException:
return _text_result(
feishu,
command,
"股票分析",
"未找到该股票的可用行情,请确认代码或先执行行情同步。",
chat_id,
actor,
auto_reply,
)
ai = report.get("ai_analysis") or {}
if not report.get("data_available"):
content = "市场数据未接入,暂不生成分析报告。"
elif not ai.get("ok"):
content = "AI 当前不可用,本次市场分析报告未发送。"
else:
content = report["content"]
response = _send_analysis(feishu, text, report, content, chat_id, actor) if auto_reply else None
return command_result(
command,
FeishuReplyType.CARD if ai.get("ok") and text != "宏观金融分析" else FeishuReplyType.TEXT,
report["title"],
content,
response,
report["lines"] if ai.get("ok") else None,
)
def _send_analysis(
feishu: FeishuService,
text: str,
report: dict[str, Any],
content: str,
chat_id: str | None,
actor: str,
) -> dict[str, Any] | None:
ai = report.get("ai_analysis") or {}
if not ai.get("ok") or text == "宏观金融分析":
return send_text_if_configured(feishu, chat_id, content, actor)
image = feishu.upload_image(render_market_chart(report), actor)
image_key = (image.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["title"],
report["lines"],
image_key=image_key,
image_alt=report["title"],
)
return feishu.send_card(card, receive_id=chat_id, actor=actor)
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)