```
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:
123
app/modules/risk/services/query.py
Normal file
123
app/modules/risk/services/query.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.http.pagination import bounded_limit
|
||||
from app.modules.business.constants import (
|
||||
DONE_STATUSES,
|
||||
GENERATED_RISK_EVENT_TYPES,
|
||||
PROJECT_CLOSED_STATUSES,
|
||||
SUPPLIER_RISK_LEVELS,
|
||||
StatusValue,
|
||||
)
|
||||
from app.modules.business.models import (
|
||||
FundAccount,
|
||||
Project,
|
||||
RiskEvent,
|
||||
RiskEventAction,
|
||||
Supplier,
|
||||
WorkTask,
|
||||
)
|
||||
from app.modules.business.service import serialize_model
|
||||
from app.modules.risk.constants import (
|
||||
RISK_SCORE_WEIGHTS,
|
||||
RiskEventPayloadKey,
|
||||
RiskSummaryKey,
|
||||
risk_level_for_score,
|
||||
)
|
||||
|
||||
|
||||
class RiskQueryMixin:
|
||||
def overdue_tasks(self) -> list[dict[str, Any]]:
|
||||
stmt = select(WorkTask).where(
|
||||
WorkTask.due_date.is_not(None),
|
||||
WorkTask.due_date < date.today(),
|
||||
WorkTask.status.notin_(DONE_STATUSES),
|
||||
)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
def delayed_projects(self) -> list[dict[str, Any]]:
|
||||
stmt = select(Project).where(
|
||||
Project.due_date.is_not(None),
|
||||
Project.due_date < date.today(),
|
||||
Project.status.notin_(PROJECT_CLOSED_STATUSES),
|
||||
)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
def over_budget_projects(self) -> list[dict[str, Any]]:
|
||||
stmt = select(Project).where(
|
||||
Project.budget_amount > 0,
|
||||
Project.actual_amount > Project.budget_amount,
|
||||
)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
def fund_risks(self) -> list[dict[str, Any]]:
|
||||
stmt = select(FundAccount).where(FundAccount.current_balance < FundAccount.safety_line)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
def supplier_risks(self) -> list[dict[str, Any]]:
|
||||
stmt = select(Supplier).where(
|
||||
(Supplier.blacklist_status != StatusValue.NORMAL)
|
||||
| Supplier.risk_level.in_(SUPPLIER_RISK_LEVELS)
|
||||
)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
def list_events(
|
||||
self,
|
||||
limit: int = 100,
|
||||
status_filter: str | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
limit_value = bounded_limit(limit)
|
||||
stmt = select(RiskEvent).order_by(RiskEvent.id.desc()).limit(limit_value)
|
||||
if status_filter:
|
||||
stmt = (
|
||||
select(RiskEvent)
|
||||
.where(RiskEvent.status == status_filter)
|
||||
.order_by(RiskEvent.id.desc())
|
||||
.limit(limit_value)
|
||||
)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
def list_actions(self, risk_event_id: int, limit: int = 100) -> list[dict[str, Any]]:
|
||||
self._get_event(risk_event_id)
|
||||
stmt = (
|
||||
select(RiskEventAction)
|
||||
.where(RiskEventAction.risk_event_id == risk_event_id)
|
||||
.order_by(RiskEventAction.id.desc())
|
||||
.limit(bounded_limit(limit))
|
||||
)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
def summary(self) -> dict[str, Any]:
|
||||
overdue_tasks = self.overdue_tasks()
|
||||
delayed_projects = self.delayed_projects()
|
||||
over_budget_projects = self.over_budget_projects()
|
||||
fund_risks = self.fund_risks()
|
||||
supplier_risks = self.supplier_risks()
|
||||
open_events = self.list_events(status_filter=StatusValue.OPEN)
|
||||
external_open_events = [
|
||||
item
|
||||
for item in open_events
|
||||
if item.get(RiskEventPayloadKey.RISK_TYPE) not in GENERATED_RISK_EVENT_TYPES
|
||||
]
|
||||
risk_score = (
|
||||
len(overdue_tasks) * RISK_SCORE_WEIGHTS[RiskSummaryKey.OVERDUE_TASKS]
|
||||
+ len(delayed_projects) * RISK_SCORE_WEIGHTS[RiskSummaryKey.DELAYED_PROJECTS]
|
||||
+ len(over_budget_projects)
|
||||
* RISK_SCORE_WEIGHTS[RiskSummaryKey.OVER_BUDGET_PROJECTS]
|
||||
+ len(fund_risks) * RISK_SCORE_WEIGHTS[RiskSummaryKey.FUND_RISKS]
|
||||
+ len(supplier_risks) * RISK_SCORE_WEIGHTS[RiskSummaryKey.SUPPLIER_RISKS]
|
||||
+ len(external_open_events) * RISK_SCORE_WEIGHTS[RiskSummaryKey.OPEN_EVENTS]
|
||||
)
|
||||
return {
|
||||
RiskSummaryKey.RISK_LEVEL: risk_level_for_score(risk_score),
|
||||
RiskSummaryKey.RISK_SCORE: Decimal(risk_score),
|
||||
RiskSummaryKey.OVERDUE_TASKS: overdue_tasks,
|
||||
RiskSummaryKey.DELAYED_PROJECTS: delayed_projects,
|
||||
RiskSummaryKey.OVER_BUDGET_PROJECTS: over_budget_projects,
|
||||
RiskSummaryKey.FUND_RISKS: fund_risks,
|
||||
RiskSummaryKey.SUPPLIER_RISKS: supplier_risks,
|
||||
RiskSummaryKey.OPEN_EVENTS: open_events,
|
||||
}
|
||||
Reference in New Issue
Block a user