feat: 日报/周报月报分开接收人 + 人均产出只算中期提交人
All checks were successful
Build and Deploy Backend / build-and-deploy (push) Successful in 7m8s
Build and Deploy Web / build-and-deploy (push) Successful in 2m32s

- 日报推送12人(主管+组长+制片+股东)
- 周报/月报推送4人(仅股东+主管,含成本信息)
- 人均日产出分母改为只算有中期提交的人,与产出口径一致

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
seaislee1209 2026-03-05 21:31:45 +08:00
parent e8eb2b5b26
commit 6361c94204
6 changed files with 24 additions and 13 deletions

View File

@ -14,5 +14,7 @@ ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
FEISHU_APP_ID=cli_a90478156bf85bd7
FEISHU_APP_SECRET=87N2nnx6Yv56TPjl2GraLdKOjFiGOSGp
# 报告接收人手机号(逗号分隔)
# 日报接收人手机号(主管+组长+制片+股东)
DAILY_REPORT_RECEIVERS=18002277047,13811803069,13636518028,13811126887,19521028015,13570527019,15920585849,17762840667,17798147128,13726331058,13751770010,18826166683
# 周报/月报接收人手机号(含成本信息,仅股东+主管)
REPORT_RECEIVERS=18002277047,13811803069,13636518028,13811126887

View File

@ -36,4 +36,5 @@ FEISHU_APP_ID = os.getenv("FEISHU_APP_ID", "")
FEISHU_APP_SECRET = os.getenv("FEISHU_APP_SECRET", "")
# 报告接收人手机号
DAILY_REPORT_RECEIVERS = [p.strip() for p in os.getenv("DAILY_REPORT_RECEIVERS", "").split(",") if p.strip()]
REPORT_RECEIVERS = [p.strip() for p in os.getenv("REPORT_RECEIVERS", "").split(",") if p.strip()]

View File

@ -85,9 +85,11 @@ def get_dashboard(
Submission.project_phase == PhaseGroup.PRODUCTION,
).scalar() or 0
# 活跃人数
# 活跃人数(只算有中期提交的人,与产出口径一致)
active_users = db.query(Submission.user_id).filter(
Submission.submit_date >= month_start,
Submission.submit_date <= today,
Submission.project_phase == PhaseGroup.PRODUCTION,
).distinct().count()
working_days = max(1, (today - month_start).days + 1)
avg_daily = round(monthly_secs / max(1, active_users) / working_days, 1)

View File

@ -9,8 +9,8 @@ from auth import require_permission
router = APIRouter(prefix="/api/reports", tags=["AI报告"])
async def _push_card(card: dict, test_mobile: Optional[str] = None) -> dict:
"""推送卡片,支持 test_mobile 只推单人"""
async def _push_card(card: dict, test_mobile: Optional[str] = None, receivers: list = None) -> dict:
"""推送卡片,支持 test_mobile 只推单人,或指定 receivers 列表"""
from services.feishu_service import feishu
if test_mobile:
user_id = await feishu.get_user_id_by_mobile(test_mobile)
@ -20,7 +20,7 @@ async def _push_card(card: dict, test_mobile: Optional[str] = None) -> dict:
if ok:
return {"success": [test_mobile], "failed": []}
return {"success": [], "failed": [{"mobile": test_mobile, "reason": "发送失败"}]}
return await feishu.send_report_card_to_all(card)
return await feishu.send_report_card_to_all(card, receivers=receivers)
@router.post("/daily")
@ -32,10 +32,11 @@ async def trigger_daily_report(
"""手动触发日报生成并推送飞书"""
from services.report_service import generate_daily_report
from services.feishu_service import build_daily_card
from config import DAILY_REPORT_RECEIVERS
report = generate_daily_report(db)
card = build_daily_card(report["title"], report["card_data"])
push_result = await _push_card(card, test_mobile)
push_result = await _push_card(card, test_mobile, receivers=DAILY_REPORT_RECEIVERS)
return {
"message": "日报生成并推送完成",

View File

@ -3,7 +3,7 @@ import time
import json
import logging
import httpx
from config import FEISHU_APP_ID, FEISHU_APP_SECRET, REPORT_RECEIVERS
from config import FEISHU_APP_ID, FEISHU_APP_SECRET, REPORT_RECEIVERS, DAILY_REPORT_RECEIVERS
logger = logging.getLogger(__name__)
@ -387,15 +387,16 @@ class FeishuService:
}
return await self.send_card(user_id, card)
async def send_report_card_to_all(self, card: dict) -> dict:
"""所有配置的接收人发送卡片报告"""
async def send_report_card_to_all(self, card: dict, receivers: list = None) -> dict:
"""指定接收人发送卡片报告,默认使用 REPORT_RECEIVERS"""
results = {"success": [], "failed": []}
receiver_list = receivers or REPORT_RECEIVERS
if not REPORT_RECEIVERS:
if not receiver_list:
logger.warning("未配置报告接收人")
return results
for mobile in REPORT_RECEIVERS:
for mobile in receiver_list:
user_id = await self.get_user_id_by_mobile(mobile)
if not user_id:
results["failed"].append({"mobile": mobile, "reason": "未找到用户"})

View File

@ -17,6 +17,7 @@ async def _run_report_job(report_type: str):
from services.feishu_service import (
feishu, build_daily_card, build_weekly_card, build_monthly_card,
)
from config import DAILY_REPORT_RECEIVERS, REPORT_RECEIVERS
logger.info(f"[定时任务] 开始生成{report_type}...")
db = SessionLocal()
@ -24,18 +25,21 @@ async def _run_report_job(report_type: str):
if report_type == "日报":
result = generate_daily_report(db)
card = build_daily_card(result["title"], result["card_data"])
receivers = DAILY_REPORT_RECEIVERS
elif report_type == "周报":
result = generate_weekly_report(db)
card = build_weekly_card(result["title"], result["card_data"])
receivers = REPORT_RECEIVERS
elif report_type == "月报":
result = generate_monthly_report(db)
card = build_monthly_card(result["title"], result["card_data"])
receivers = REPORT_RECEIVERS
else:
logger.error(f"未知报告类型: {report_type}")
return
logger.info(f"[定时任务] {report_type}生成完成,开始推送飞书...")
push_result = await feishu.send_report_card_to_all(card)
logger.info(f"[定时任务] {report_type}生成完成,开始推送飞书{len(receivers)}人)...")
push_result = await feishu.send_report_card_to_all(card, receivers=receivers)
logger.info(f"[定时任务] {report_type}推送完成: {push_result}")
except Exception as e: