Files
JiuContinent bf309ecdf7 ```
refactor(core,ai): 调整模块导入路径并移除废弃文件

- 修复 scheduler.py 中的导入路径错误,将 reports.service
  改为 reports.services
- 移除废弃的 app/core/background/task_queue.py 文件
- 移除废弃的 app/modules/ai_agent/adapters.py 文件
- 修复 ai_memory/service.py 中的导入路径错误,将
  events.service 改为 events.services
```
2026-07-09 18:50:52 +08:00

139 lines
4.8 KiB
Python

from datetime import date
from typing import Any
from app.modules.business.constants import (
DONE_STATUSES,
PROJECT_CLOSED_STATUSES,
)
from app.modules.business.models import (
Project,
WorkTask,
)
from app.modules.business.service import serialize_model
from app.modules.reports.constants import (
ATTENTION_SCORE_THRESHOLD,
HEALTH_PENALTY_WEIGHTS,
HEALTH_SCORE_MAX,
HEALTH_SCORE_MIN,
HEALTHY_SCORE_THRESHOLD,
HealthLevel,
LifecycleAttentionKey,
MetricKey,
ReportText,
)
class ReportLifecycleHealthMixin:
def _lifecycle_health(
self,
projects: dict[str, Any],
tasks: dict[str, Any],
risks: dict[str, Any],
suppliers: dict[str, Any],
include_global_risk: bool,
) -> dict[str, Any]:
penalty = (
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] * 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 >= ATTENTION_SCORE_THRESHOLD:
level = HealthLevel.ATTENTION
else:
level = HealthLevel.CRITICAL
return {MetricKey.SCORE: score, MetricKey.LEVEL: level}
def _lifecycle_attention(
self,
project_conditions: list[Any],
task_conditions: list[Any],
) -> dict[str, Any]:
delayed = self._records(
Project,
Project.due_date.is_not(None),
Project.due_date < date.today(),
Project.status.notin_(PROJECT_CLOSED_STATUSES),
*project_conditions,
limit=10,
order_by=Project.due_date.asc(),
)
over_budget = self._records(
Project,
Project.budget_amount > 0,
Project.actual_amount > Project.budget_amount,
*project_conditions,
limit=10,
order_by=Project.id.desc(),
)
overdue_tasks = self._records(
WorkTask,
WorkTask.due_date.is_not(None),
WorkTask.due_date < date.today(),
WorkTask.status.notin_(DONE_STATUSES),
*task_conditions,
limit=10,
order_by=WorkTask.due_date.asc(),
)
return {
LifecycleAttentionKey.DELAYED_PROJECTS: [serialize_model(item) for item in delayed],
LifecycleAttentionKey.OVER_BUDGET_PROJECTS: [
serialize_model(item) for item in over_budget
],
LifecycleAttentionKey.OVERDUE_TASKS: [
serialize_model(item) for item in overdue_tasks
],
}
def _lifecycle_recommendations(
self,
projects: dict[str, Any],
tasks: dict[str, Any],
procurements: dict[str, Any],
expenses: dict[str, Any],
funds: dict[str, Any],
suppliers: dict[str, Any],
risks: dict[str, Any],
include_global_risk: bool,
) -> list[str]:
recommendations: list[str] = []
if risks[MetricKey.DELAYED_PROJECTS]:
recommendations.append(ReportText.RECOMMEND_DELAYED)
if risks[MetricKey.OVER_BUDGET_PROJECTS]:
recommendations.append(ReportText.RECOMMEND_OVER_BUDGET)
if tasks[MetricKey.OVERDUE]:
recommendations.append(ReportText.RECOMMEND_OVERDUE_TASKS)
if (
procurements[MetricKey.PENDING_APPROVAL]
or expenses[MetricKey.PENDING_APPROVAL]
):
recommendations.append(ReportText.RECOMMEND_APPROVALS)
if include_global_risk and funds[MetricKey.RISK_ACCOUNTS]:
recommendations.append(ReportText.RECOMMEND_FUNDS)
if include_global_risk and suppliers[MetricKey.RISKY]:
recommendations.append(ReportText.RECOMMEND_SUPPLIERS)
if not recommendations:
recommendations.append(ReportText.RECOMMEND_STABLE)
return [str(item) for item in recommendations]