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 ```
139 lines
4.8 KiB
Python
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]
|