```
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 ```
This commit is contained in:
138
app/modules/reports/services/lifecycle/health.py
Normal file
138
app/modules/reports/services/lifecycle/health.py
Normal file
@@ -0,0 +1,138 @@
|
||||
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]
|
||||
Reference in New Issue
Block a user