144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
"""内驱力审核服务(次生目标 approve → 自动创建太一目标)。
|
|
|
|
审核 approve 时自动派生 Goal(太一枢任务级目标),
|
|
延后 / 驳回仅标记不变更目标树。
|
|
"""
|
|
|
|
from app.core.errors import AppError
|
|
from app.core.ids import new_goal_id
|
|
from app.core.time import utc_now
|
|
from app.db.session import AsyncSessionLocal
|
|
from app.models.drive import CuriosityQuestion, ExploratorySubgoal
|
|
from app.models.governance import Goal
|
|
from app.repositories import drive as drive_repo
|
|
from app.repositories.json_utils import loads_list
|
|
from app.schemas.common import Page
|
|
from app.schemas.drive import CuriosityResponse, SubgoalResponse
|
|
from app.services.audit import record_audit_event
|
|
|
|
|
|
async def list_curiosities(
|
|
*,
|
|
character_id: str,
|
|
source: str | None = None,
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
) -> Page[CuriosityResponse]:
|
|
async with AsyncSessionLocal() as session:
|
|
items, total = await drive_repo.list_curiosities(
|
|
session, character_id, source=source, page=page, page_size=page_size
|
|
)
|
|
return Page(
|
|
items=[_curiosity_to_response(i) for i in items],
|
|
page=page,
|
|
page_size=page_size,
|
|
total=total,
|
|
)
|
|
|
|
|
|
async def list_subgoals(
|
|
*,
|
|
character_id: str,
|
|
status: str | None = None,
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
) -> Page[SubgoalResponse]:
|
|
async with AsyncSessionLocal() as session:
|
|
items, total = await drive_repo.list_subgoals(
|
|
session, character_id, status=status, page=page, page_size=page_size
|
|
)
|
|
return Page(
|
|
items=[_subgoal_to_response(i) for i in items],
|
|
page=page,
|
|
page_size=page_size,
|
|
total=total,
|
|
)
|
|
|
|
|
|
async def approve_subgoal(subgoal_id: str) -> SubgoalResponse:
|
|
return await _review_subgoal(subgoal_id, "approved", decision="auto", create_goal=True)
|
|
|
|
|
|
async def defer_subgoal(subgoal_id: str) -> SubgoalResponse:
|
|
return await _review_subgoal(subgoal_id, "deferred", decision="auto", create_goal=False)
|
|
|
|
|
|
async def reject_subgoal(subgoal_id: str) -> SubgoalResponse:
|
|
return await _review_subgoal(subgoal_id, "rejected", decision="denied", create_goal=False)
|
|
|
|
|
|
async def _review_subgoal(
|
|
subgoal_id: str, target_status: str, *, decision: str, create_goal: bool
|
|
) -> SubgoalResponse:
|
|
async with AsyncSessionLocal() as session:
|
|
sub = await drive_repo.get_subgoal(session, subgoal_id)
|
|
if sub is None:
|
|
raise AppError("not_found", "探索性次生目标不存在。")
|
|
if sub.status != "candidate":
|
|
raise AppError("conflict", f"次生目标当前状态为 {sub.status},不可审核。")
|
|
sub.status = target_status
|
|
sub.reviewed_at = utc_now()
|
|
sub.reviewed_by = "user"
|
|
|
|
# approve 时自动创建太一任务级目标
|
|
if create_goal and sub.character_id:
|
|
goal = Goal(
|
|
id=new_goal_id(),
|
|
character_id=sub.character_id,
|
|
title=sub.title,
|
|
description=sub.description or "由次生目标自动生成",
|
|
level="task",
|
|
type="exploration",
|
|
status="active",
|
|
)
|
|
session.add(goal)
|
|
|
|
await record_audit_event(
|
|
session,
|
|
action=f"drive.subgoal.{target_status}",
|
|
actor="user",
|
|
risk_level=sub.risk_level,
|
|
decision=decision,
|
|
character_id=sub.character_id,
|
|
input_summary=sub.title[:200],
|
|
resource_type="exploratory_subgoal",
|
|
resource_id=sub.id,
|
|
)
|
|
await session.commit()
|
|
await session.refresh(sub)
|
|
return _subgoal_to_response(sub)
|
|
|
|
|
|
def _curiosity_to_response(c: CuriosityQuestion) -> CuriosityResponse:
|
|
return CuriosityResponse(
|
|
id=c.id,
|
|
character_id=c.character_id,
|
|
cycle_id=c.cycle_id,
|
|
question=c.question,
|
|
source=c.source,
|
|
related_entities=loads_list(c.related_entities_json),
|
|
expected_value=c.expected_value,
|
|
urgency=c.urgency,
|
|
risk_level=c.risk_level,
|
|
created_at=c.created_at,
|
|
)
|
|
|
|
|
|
def _subgoal_to_response(s: ExploratorySubgoal) -> SubgoalResponse:
|
|
return SubgoalResponse(
|
|
id=s.id,
|
|
character_id=s.character_id,
|
|
cycle_id=s.cycle_id,
|
|
parent_goal_id=s.parent_goal_id,
|
|
curiosity_question_id=s.curiosity_question_id,
|
|
title=s.title,
|
|
description=s.description,
|
|
expected_information_gain=s.expected_information_gain,
|
|
interruption_cost=s.interruption_cost,
|
|
risk_level=s.risk_level,
|
|
status=s.status,
|
|
reviewed_at=s.reviewed_at,
|
|
reviewed_by=s.reviewed_by,
|
|
created_at=s.created_at,
|
|
)
|