```
refactor(api): 使用常量替代硬编码字符串 - 在health_check接口中使用ApiResponseKey.STATUS和ApiStatus.OK常量 - 替换硬编码的状态返回值为枚举常量 refactor(core): 配置模块错误信息统一使用常量 - 从constants模块导入ConfigErrorDetail并替换CORS_ORIGINS和LEGACY_ALLOWED_QUERIES的验证错误信息 - 配置类中的默认值使用constants中定义的常量 feat(constants): 添加API响应、安全错误和配置错误常量类 - 新增ApiResponseKey用于API状态键名 - 新增ApiStatus用于API状态值 - 新增SecurityErrorDetail用于安全认证错误详情 - 新增ConfigErrorDetail用于配置验证错误详情 - 添加DEFAULT_MODEL_PROVIDER和DEFAULT_OPENCLAW_ACTION_JSON常量 refactor(security): 安全认证模块使用错误常量 - 将硬编码的安全错误信息替换为SecurityErrorDetail常量 - 包括API密钥、审批密钥和审计密钥的相关错误信息 refactor(ai-agent): AI代理适配器改进错误处理 - 将HTTP状态码替换为FastAPI状态常量 - 添加OpenClaw工具和操作的错误常量 - 修复健康检查和工具调用中的状态码比较逻辑 - 添加AIToolAuditKey用于工具审计键名 feat(ai-agent): 扩展AI代理常量定义 - 新增AIToolAuditKey用于工具审计字段 - 添加OpenClaw相关的错误常量如OPENCLAW_CHAT_PROVIDER_REQUIRED等 - 添加UNSUPPORTED_AI_SKILL_TEMPLATE模板字符串 refactor(approvals): 审批模块常量化重构 - 新增ApprovalPayloadKey用于审批载荷字段 - 添加approval_action函数和APPROVAL_ACTION_SEPARATOR分隔符 - 使用常量替换字面量值 feat(audit): 审计模块新增飞书事件动作类型 - 添加FEISHU_WEBHOOK_EVENT和FEISHU_LONG_CONNECTION_EVENT审计动作 refactor(business): 业务模块全面常量化 - 新增BusinessDomain枚举包含所有业务域 - 添加BusinessResponseKey、BusinessPayloadKey等常量类 - 重构DOMAIN_MODELS为frozenset以提高性能 - 添加normalize_domain等辅助函数用于域标准化 - 使用常量替换路由和业务服务中的硬编码字符串 - 添加业务错误常量和字段验证模板 refactor(feishu): 飞书客户端错误处理优化 - 将HTTP状态码替换为FastAPI标准状态常量 - 改进错误处理的一致性 refactor(approvals): 审批服务使用新常量结构 - 使用ApprovalPayloadKey常量重构载荷字段 - 使用approval_action函数统一动作命名格式 - 优化高风险域判断逻辑 ```
This commit is contained in:
@@ -35,17 +35,26 @@ from app.modules.business.models import (
|
||||
from app.modules.business.service import serialize_model
|
||||
from app.modules.feishu.service import FeishuService
|
||||
from app.modules.reports.constants import (
|
||||
ATTENTION_SCORE_THRESHOLD,
|
||||
HEALTH_PENALTY_WEIGHTS,
|
||||
HEALTH_SCORE_MAX,
|
||||
HEALTH_SCORE_MIN,
|
||||
HEALTHY_SCORE_THRESHOLD,
|
||||
LIFECYCLE_RISK_SCORE_WEIGHTS,
|
||||
HealthLevel,
|
||||
LifecycleAttentionKey,
|
||||
LifecycleFilterKey,
|
||||
LifecycleResponseKey,
|
||||
LifecycleSection,
|
||||
MetricKey,
|
||||
ReportResponseKey,
|
||||
ReportStatus,
|
||||
ReportText,
|
||||
ReportTitle,
|
||||
ReportType,
|
||||
WorkReportMetricKey,
|
||||
)
|
||||
from app.modules.risk.constants import RiskSummaryKey, risk_level_for_score
|
||||
from app.modules.risk.service import RiskService
|
||||
|
||||
|
||||
@@ -164,18 +173,22 @@ class ReportService:
|
||||
f"- 待处理费用:{expense_pending}",
|
||||
f"- 当前账户总余额:{_money(fund_total)}",
|
||||
(
|
||||
f"- 今日打卡记录:{attendance['total']},"
|
||||
f"异常:{attendance['abnormal_total']}"
|
||||
f"- 今日打卡记录:{attendance[ReportResponseKey.TOTAL]},"
|
||||
f"异常:{attendance[ReportResponseKey.ABNORMAL_TOTAL]}"
|
||||
),
|
||||
f"- 逾期任务:{len(risk_summary['overdue_tasks'])}",
|
||||
f"- 延期项目:{len(risk_summary['delayed_projects'])}",
|
||||
f"- 超预算项目:{len(risk_summary['over_budget_projects'])}",
|
||||
f"- 资金风险账户:{len(risk_summary['fund_risks'])}",
|
||||
f"- 供应商风险:{len(risk_summary['supplier_risks'])}",
|
||||
f"- 打开风险事件:{len(risk_summary['open_events'])}",
|
||||
f"- 综合风险等级:{risk_summary['risk_level']}",
|
||||
f"- 逾期任务:{len(risk_summary[RiskSummaryKey.OVERDUE_TASKS])}",
|
||||
f"- 延期项目:{len(risk_summary[RiskSummaryKey.DELAYED_PROJECTS])}",
|
||||
f"- 超预算项目:{len(risk_summary[RiskSummaryKey.OVER_BUDGET_PROJECTS])}",
|
||||
f"- 资金风险账户:{len(risk_summary[RiskSummaryKey.FUND_RISKS])}",
|
||||
f"- 供应商风险:{len(risk_summary[RiskSummaryKey.SUPPLIER_RISKS])}",
|
||||
f"- 打开风险事件:{len(risk_summary[RiskSummaryKey.OPEN_EVENTS])}",
|
||||
f"- 综合风险等级:{risk_summary[RiskSummaryKey.RISK_LEVEL]}",
|
||||
]
|
||||
return {"title": "每日经营晨报", "lines": lines, "content": "\n".join(lines)}
|
||||
return {
|
||||
ReportResponseKey.TITLE: ReportTitle.DAILY_BRIEF,
|
||||
ReportResponseKey.LINES: lines,
|
||||
ReportResponseKey.CONTENT: "\n".join(lines),
|
||||
}
|
||||
|
||||
def project_weekly(self) -> dict:
|
||||
active = self._count(
|
||||
@@ -199,7 +212,11 @@ class ReportService:
|
||||
)
|
||||
for item in over_budget[:10]:
|
||||
lines.append(f" - 超预算:{item.get('code')} {item.get('name')}")
|
||||
return {"title": "项目周报", "lines": lines, "content": "\n".join(lines)}
|
||||
return {
|
||||
ReportResponseKey.TITLE: ReportTitle.PROJECT_WEEKLY,
|
||||
ReportResponseKey.LINES: lines,
|
||||
ReportResponseKey.CONTENT: "\n".join(lines),
|
||||
}
|
||||
|
||||
def project_lifecycle_report(
|
||||
self,
|
||||
@@ -559,20 +576,14 @@ class ReportService:
|
||||
*risk_conditions,
|
||||
)
|
||||
risk_score = (
|
||||
overdue_tasks * 1
|
||||
+ delayed_projects * 3
|
||||
+ over_budget_projects * 4
|
||||
+ external_open_events * 2
|
||||
+ external_high_events * 3
|
||||
overdue_tasks * LIFECYCLE_RISK_SCORE_WEIGHTS[MetricKey.OVERDUE_TASKS]
|
||||
+ delayed_projects * LIFECYCLE_RISK_SCORE_WEIGHTS[MetricKey.DELAYED_PROJECTS]
|
||||
+ over_budget_projects * LIFECYCLE_RISK_SCORE_WEIGHTS[MetricKey.OVER_BUDGET_PROJECTS]
|
||||
+ external_open_events * LIFECYCLE_RISK_SCORE_WEIGHTS[MetricKey.EXTERNAL_OPEN_EVENTS]
|
||||
+ external_high_events * LIFECYCLE_RISK_SCORE_WEIGHTS[MetricKey.EXTERNAL_HIGH_EVENTS]
|
||||
)
|
||||
if risk_score >= 15:
|
||||
level = RiskLevel.HIGH
|
||||
elif risk_score >= 5:
|
||||
level = RiskLevel.MEDIUM
|
||||
else:
|
||||
level = RiskLevel.LOW
|
||||
return {
|
||||
MetricKey.RISK_LEVEL: level,
|
||||
MetricKey.RISK_LEVEL: risk_level_for_score(risk_score),
|
||||
MetricKey.RISK_SCORE: risk_score,
|
||||
MetricKey.OVERDUE_TASKS: overdue_tasks,
|
||||
MetricKey.DELAYED_PROJECTS: delayed_projects,
|
||||
@@ -602,19 +613,32 @@ class ReportService:
|
||||
include_global_risk: bool,
|
||||
) -> dict[str, Any]:
|
||||
penalty = (
|
||||
risks[MetricKey.OVERDUE_TASKS] * 3
|
||||
+ risks[MetricKey.DELAYED_PROJECTS] * 8
|
||||
+ risks[MetricKey.OVER_BUDGET_PROJECTS] * 10
|
||||
+ risks[MetricKey.EXTERNAL_HIGH_EVENTS] * 8
|
||||
+ max(0, projects[MetricKey.BUDGET_USAGE_RATE] - 100) * 0.4
|
||||
+ (100 - tasks[MetricKey.COMPLETION_RATE]) * 0.1
|
||||
risks[MetricKey.OVERDUE_TASKS] * HEALTH_PENALTY_WEIGHTS[MetricKey.OVERDUE_TASKS]
|
||||
+ risks[MetricKey.DELAYED_PROJECTS]
|
||||
* HEALTH_PENALTY_WEIGHTS[MetricKey.DELAYED_PROJECTS]
|
||||
+ risks[MetricKey.OVER_BUDGET_PROJECTS]
|
||||
* HEALTH_PENALTY_WEIGHTS[MetricKey.OVER_BUDGET_PROJECTS]
|
||||
+ risks[MetricKey.EXTERNAL_HIGH_EVENTS]
|
||||
* HEALTH_PENALTY_WEIGHTS[MetricKey.EXTERNAL_HIGH_EVENTS]
|
||||
+ max(
|
||||
HEALTH_SCORE_MIN,
|
||||
projects[MetricKey.BUDGET_USAGE_RATE] - HEALTH_SCORE_MAX,
|
||||
)
|
||||
* HEALTH_PENALTY_WEIGHTS[MetricKey.BUDGET_USAGE_RATE]
|
||||
+ (HEALTH_SCORE_MAX - tasks[MetricKey.COMPLETION_RATE])
|
||||
* HEALTH_PENALTY_WEIGHTS[MetricKey.COMPLETION_RATE]
|
||||
)
|
||||
if include_global_risk:
|
||||
penalty += suppliers[MetricKey.BLACKLISTED] * 10
|
||||
score = max(0, min(100, round(100 - penalty, 2)))
|
||||
if score >= 80:
|
||||
penalty += suppliers[MetricKey.BLACKLISTED] * HEALTH_PENALTY_WEIGHTS[
|
||||
MetricKey.BLACKLISTED
|
||||
]
|
||||
score = max(
|
||||
HEALTH_SCORE_MIN,
|
||||
min(HEALTH_SCORE_MAX, round(HEALTH_SCORE_MAX - penalty, 2)),
|
||||
)
|
||||
if score >= HEALTHY_SCORE_THRESHOLD:
|
||||
level = HealthLevel.HEALTHY
|
||||
elif score >= 60:
|
||||
elif score >= ATTENTION_SCORE_THRESHOLD:
|
||||
level = HealthLevel.ATTENTION
|
||||
else:
|
||||
level = HealthLevel.CRITICAL
|
||||
@@ -801,13 +825,13 @@ class ReportService:
|
||||
for status, count in sorted(status_counts.items()):
|
||||
lines.append(f"- {status}:{count}")
|
||||
return {
|
||||
"title": "打卡汇总",
|
||||
"work_date": target_date.isoformat(),
|
||||
"total": total,
|
||||
"abnormal_total": abnormal_total,
|
||||
"status_counts": status_counts,
|
||||
"lines": lines,
|
||||
"content": "\n".join(lines),
|
||||
ReportResponseKey.TITLE: ReportTitle.ATTENDANCE_SUMMARY,
|
||||
ReportResponseKey.WORK_DATE: target_date.isoformat(),
|
||||
ReportResponseKey.TOTAL: total,
|
||||
ReportResponseKey.ABNORMAL_TOTAL: abnormal_total,
|
||||
ReportResponseKey.STATUS_COUNTS: status_counts,
|
||||
ReportResponseKey.LINES: lines,
|
||||
ReportResponseKey.CONTENT: "\n".join(lines),
|
||||
}
|
||||
|
||||
def generate_work_report(
|
||||
@@ -833,14 +857,14 @@ class ReportService:
|
||||
)
|
||||
lines = self._work_report_lines(title, start, end, metrics, risk_summary)
|
||||
report = {
|
||||
"title": title,
|
||||
"report_type": report_type,
|
||||
"period_start": start.isoformat(),
|
||||
"period_end": end.isoformat(),
|
||||
"lines": lines,
|
||||
"content": "\n".join(lines),
|
||||
"metrics": metrics,
|
||||
"risk_summary": risk_summary,
|
||||
ReportResponseKey.TITLE: title,
|
||||
ReportResponseKey.REPORT_TYPE: report_type,
|
||||
ReportResponseKey.PERIOD_START: start.isoformat(),
|
||||
ReportResponseKey.PERIOD_END: end.isoformat(),
|
||||
ReportResponseKey.LINES: lines,
|
||||
ReportResponseKey.CONTENT: "\n".join(lines),
|
||||
ReportResponseKey.METRICS: metrics,
|
||||
ReportResponseKey.RISK_SUMMARY: risk_summary,
|
||||
}
|
||||
|
||||
record_data = None
|
||||
@@ -854,7 +878,7 @@ class ReportService:
|
||||
project_code=project_code,
|
||||
period_start=start,
|
||||
period_end=end,
|
||||
content=report["content"],
|
||||
content=report[ReportResponseKey.CONTENT],
|
||||
metrics=metrics,
|
||||
risk_summary=risk_summary,
|
||||
)
|
||||
@@ -873,7 +897,7 @@ class ReportService:
|
||||
)
|
||||
)
|
||||
|
||||
return {"report": report, "data": record_data}
|
||||
return {ReportResponseKey.REPORT: report, ReportResponseKey.DATA: record_data}
|
||||
|
||||
def _resolve_period(
|
||||
self,
|
||||
@@ -932,19 +956,25 @@ class ReportService:
|
||||
*task_filters,
|
||||
)
|
||||
return {
|
||||
"projects_total": self._count(Project, *project_filters),
|
||||
"active_projects": self._count(
|
||||
WorkReportMetricKey.PROJECTS_TOTAL: self._count(Project, *project_filters),
|
||||
WorkReportMetricKey.ACTIVE_PROJECTS: self._count(
|
||||
Project,
|
||||
Project.status.notin_(PROJECT_CLOSED_STATUSES),
|
||||
*project_filters,
|
||||
),
|
||||
"tasks_total": self._count(WorkTask, *task_filters),
|
||||
"tasks_completed": completed_tasks,
|
||||
"tasks_overdue": overdue_tasks,
|
||||
"procurements_pending": self._count(Procurement, *procurement_filters),
|
||||
"expenses_pending": self._count(Expense, *expense_filters),
|
||||
"attendance_total": self._count(AttendanceRecord, *attendance_filters),
|
||||
"open_risk_events": self._count(RiskEvent, *risk_filters),
|
||||
WorkReportMetricKey.TASKS_TOTAL: self._count(WorkTask, *task_filters),
|
||||
WorkReportMetricKey.TASKS_COMPLETED: completed_tasks,
|
||||
WorkReportMetricKey.TASKS_OVERDUE: overdue_tasks,
|
||||
WorkReportMetricKey.PROCUREMENTS_PENDING: self._count(
|
||||
Procurement,
|
||||
*procurement_filters,
|
||||
),
|
||||
WorkReportMetricKey.EXPENSES_PENDING: self._count(Expense, *expense_filters),
|
||||
WorkReportMetricKey.ATTENDANCE_TOTAL: self._count(
|
||||
AttendanceRecord,
|
||||
*attendance_filters,
|
||||
),
|
||||
WorkReportMetricKey.OPEN_RISK_EVENTS: self._count(RiskEvent, *risk_filters),
|
||||
}
|
||||
|
||||
def _work_report_lines(
|
||||
@@ -958,14 +988,20 @@ class ReportService:
|
||||
return [
|
||||
f"- 报告:{title}",
|
||||
f"- 周期:{start.isoformat()} 至 {end.isoformat()}",
|
||||
f"- 项目:总数 {metrics['projects_total']},活跃 {metrics['active_projects']}",
|
||||
f"- 任务:总数 {metrics['tasks_total']},完成 {metrics['tasks_completed']}",
|
||||
f"- 逾期任务:{metrics['tasks_overdue']}",
|
||||
f"- 待处理采购:{metrics['procurements_pending']}",
|
||||
f"- 待处理费用:{metrics['expenses_pending']}",
|
||||
f"- 打卡记录:{metrics['attendance_total']}",
|
||||
f"- 打开风险事件:{metrics['open_risk_events']}",
|
||||
f"- 综合风险等级:{risk_summary['risk_level']}",
|
||||
(
|
||||
f"- 项目:总数 {metrics[WorkReportMetricKey.PROJECTS_TOTAL]},"
|
||||
f"活跃 {metrics[WorkReportMetricKey.ACTIVE_PROJECTS]}"
|
||||
),
|
||||
(
|
||||
f"- 任务:总数 {metrics[WorkReportMetricKey.TASKS_TOTAL]},"
|
||||
f"完成 {metrics[WorkReportMetricKey.TASKS_COMPLETED]}"
|
||||
),
|
||||
f"- 逾期任务:{metrics[WorkReportMetricKey.TASKS_OVERDUE]}",
|
||||
f"- 待处理采购:{metrics[WorkReportMetricKey.PROCUREMENTS_PENDING]}",
|
||||
f"- 待处理费用:{metrics[WorkReportMetricKey.EXPENSES_PENDING]}",
|
||||
f"- 打卡记录:{metrics[WorkReportMetricKey.ATTENDANCE_TOTAL]}",
|
||||
f"- 打开风险事件:{metrics[WorkReportMetricKey.OPEN_RISK_EVENTS]}",
|
||||
f"- 综合风险等级:{risk_summary[RiskSummaryKey.RISK_LEVEL]}",
|
||||
]
|
||||
|
||||
def push_report(
|
||||
@@ -975,5 +1011,8 @@ class ReportService:
|
||||
receive_id_type: str,
|
||||
actor: str,
|
||||
) -> dict:
|
||||
card = FeishuService.build_basic_card(report["title"], report["lines"])
|
||||
card = FeishuService.build_basic_card(
|
||||
report[ReportResponseKey.TITLE],
|
||||
report[ReportResponseKey.LINES],
|
||||
)
|
||||
return FeishuService(self.db).send_card(card, receive_id, receive_id_type, actor)
|
||||
|
||||
Reference in New Issue
Block a user