初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+164
View File
@@ -0,0 +1,164 @@
"""大周天抽取候选审核服务(记忆候选 + 世界实体候选)。
记忆候选 approve 派生为 ``memories``(source=big_cycle);世界实体候选 approve
→ confirmed 进入世界模型。审核仅 pending 可操作(见 [大周天记忆/世界抽取落地设计]
../docs/domain/core-domains.md §5)。
"""
from app.core.errors import AppError
from app.core.time import utc_now
from app.db.session import AsyncSessionLocal
from app.models.memory import Memory
from app.repositories import candidates as candidates_repo
from app.repositories import world as world_repo
from app.schemas.candidates import MemoryCandidateResponse
from app.schemas.common import Page
from app.schemas.world import WorldEntityResponse
from app.services.audit import record_audit_event
from app.services.serializers import (
character_world_entity_to_response,
memory_candidate_to_response,
)
async def list_memory_proposals(
*,
character_id: str,
source: str | None = None,
status: str | None = None,
page: int = 1,
page_size: int = 20,
) -> Page[MemoryCandidateResponse]:
async with AsyncSessionLocal() as session:
all_items = await candidates_repo.list_memory_candidates(
session, character_id, source=source, status=status
)
start = max(page - 1, 0) * page_size
items = all_items[start : start + page_size]
return Page(
items=[memory_candidate_to_response(i) for i in items],
page=page,
page_size=page_size,
total=len(all_items),
)
async def approve_memory_proposal(
candidate_id: str,
) -> MemoryCandidateResponse:
async with AsyncSessionLocal() as session:
candidate = await candidates_repo.get_memory_candidate(session, candidate_id)
if candidate is None:
raise AppError("not_found", "记忆候选不存在。")
if candidate.status != "pending":
raise AppError("conflict", f"候选当前状态为 {candidate.status},不可审核。")
session.add(
Memory(
character_id=candidate.character_id,
type=candidate.type,
title=candidate.title,
content=candidate.content,
source="big_cycle",
confidence=candidate.confidence,
importance=candidate.importance,
sensitivity=candidate.sensitivity,
evidence_ids_json=candidate.evidence_ids_json,
status="active",
)
)
candidate.status = "applied"
candidate.reviewed_at = utc_now()
await record_audit_event(
session,
action="extraction.memory.applied",
actor="user",
character_id=candidate.character_id,
input_summary=candidate.title[:200],
resource_type="memory_candidate",
resource_id=candidate.id,
)
await session.commit()
await session.refresh(candidate)
return memory_candidate_to_response(candidate)
async def reject_memory_proposal(
candidate_id: str,
) -> MemoryCandidateResponse:
return await _reject_memory(candidate_id)
async def list_world_entities_pending(
*,
character_id: str,
status: str | None = None,
page: int = 1,
page_size: int = 20,
) -> Page[WorldEntityResponse]:
async with AsyncSessionLocal() as session:
items, total = await world_repo.list_entities(
session, character_id, status=status, page=page, page_size=page_size
)
return Page(
items=[character_world_entity_to_response(i) for i in items],
page=page,
page_size=page_size,
total=total,
)
async def approve_world_entity(entity_id: str) -> WorldEntityResponse:
return await _review_world_entity(entity_id, "confirmed", decision="auto")
async def reject_world_entity(entity_id: str) -> WorldEntityResponse:
return await _review_world_entity(entity_id, "rejected", decision="denied")
async def _reject_memory(candidate_id: str) -> MemoryCandidateResponse:
async with AsyncSessionLocal() as session:
candidate = await candidates_repo.get_memory_candidate(session, candidate_id)
if candidate is None:
raise AppError("not_found", "记忆候选不存在。")
if candidate.status != "pending":
raise AppError("conflict", f"候选当前状态为 {candidate.status},不可审核。")
candidate.status = "rejected"
candidate.reviewed_at = utc_now()
await record_audit_event(
session,
action="extraction.memory.rejected",
actor="user",
decision="denied",
character_id=candidate.character_id,
input_summary=candidate.title[:200],
resource_type="memory_candidate",
resource_id=candidate.id,
)
await session.commit()
await session.refresh(candidate)
return memory_candidate_to_response(candidate)
async def _review_world_entity(
entity_id: str, target_status: str, *, decision: str
) -> WorldEntityResponse:
async with AsyncSessionLocal() as session:
entity = await world_repo.get_entity(session, entity_id)
if entity is None:
raise AppError("not_found", "世界实体不存在。")
if entity.status != "pending":
raise AppError("conflict", f"实体当前状态为 {entity.status},不可审核。")
entity.status = target_status
await record_audit_event(
session,
action=f"extraction.world.{target_status}",
actor="user",
decision=decision,
character_id=entity.character_id,
input_summary=entity.name[:200],
resource_type="character_world_entity",
resource_id=entity.id,
)
await session.commit()
await session.refresh(entity)
return character_world_entity_to_response(entity)