177 lines
6.3 KiB
Python
177 lines
6.3 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
from ...services.sstc.charter_data import _build_charter_summary_payload, _load_signal_rows, _select_source_signal, _serialize_alignment_record, _serialize_charter_signal, _serialize_charter_task, _serialize_source_signal
|
|
from ...services.sstc.entity_lookup import _ensure_charter, _ensure_project
|
|
from ...services.sstc.feishu_routing import _backfill_feishu_sender_names
|
|
from ...services.sstc.governance import _require_manager
|
|
from ...services.sstc.signal_core import _as_dict, _parse_node_id, _parse_project_id, _sync_pmflow_rag_async
|
|
|
|
router = APIRouter(tags=["sstc"])
|
|
|
|
@router.get("/projects/{project_id}/charters/{charter_id}")
|
|
async def get_charter_detail(
|
|
project_id: str,
|
|
charter_id: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
pid = _parse_project_id(project_id)
|
|
cid = _parse_node_id(charter_id, "charter_id")
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
charter = await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=cid)
|
|
|
|
content = _as_dict(charter.content)
|
|
signal_ids = [sid for sid in content.get("signal_ids", []) if isinstance(sid, int)]
|
|
signal_rows = await _load_signal_rows(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
signal_ids=signal_ids,
|
|
)
|
|
if signal_rows:
|
|
await _backfill_feishu_sender_names(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
signals=signal_rows,
|
|
)
|
|
|
|
task_result = await db.execute(
|
|
select(Task).where(
|
|
and_(
|
|
Task.truth_node_id == cid,
|
|
Task.project_id == pid,
|
|
Task.tenant_id == tenant_id,
|
|
)
|
|
)
|
|
)
|
|
tasks = list(task_result.scalars().all())
|
|
|
|
align_result = await db.execute(
|
|
select(AlignmentRecord).where(
|
|
and_(
|
|
AlignmentRecord.truth_node_id == cid,
|
|
AlignmentRecord.tenant_id == tenant_id,
|
|
)
|
|
)
|
|
)
|
|
alignments = list(align_result.scalars().all())
|
|
source_signal = _select_source_signal(charter, signal_rows)
|
|
|
|
return success_response(
|
|
{
|
|
"id": str(charter.id),
|
|
"title": charter.title,
|
|
"content": content,
|
|
"signal_ids": signal_ids,
|
|
"signals": [_serialize_charter_signal(signal) for signal in signal_rows],
|
|
"source_signal": _serialize_source_signal(source_signal) if source_signal else None,
|
|
"tasks": [_serialize_charter_task(task) for task in tasks],
|
|
"alignment_records": [_serialize_alignment_record(record) for record in alignments],
|
|
"status": charter.status,
|
|
"version": charter.version,
|
|
}
|
|
)
|
|
|
|
@router.patch("/projects/{project_id}/charters/{charter_id}")
|
|
async def update_charter(
|
|
project_id: str,
|
|
charter_id: str,
|
|
body: CharterUpdateRequest,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
_require_manager(current_user)
|
|
pid = _parse_project_id(project_id)
|
|
cid = _parse_node_id(charter_id, "charter_id")
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid)
|
|
charter = await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=cid)
|
|
|
|
before = {
|
|
"title": charter.title,
|
|
"content": _as_dict(charter.content),
|
|
"version": charter.version,
|
|
"status": charter.status,
|
|
}
|
|
aligned = (charter.status or "").lower() in ALIGNED_STATUSES
|
|
if aligned and body.force_new_version:
|
|
new_node = TruthNode(
|
|
node_type="charter",
|
|
parent_id=int(charter.id),
|
|
project_id=pid,
|
|
created_by=int(current_user.id),
|
|
title=compact_title((body.title or charter.title), max_len=24),
|
|
content=body.content if body.content is not None else _as_dict(charter.content),
|
|
version=int(charter.version or 1) + 1,
|
|
status="draft",
|
|
tenant_id=tenant_id,
|
|
)
|
|
db.add(new_node)
|
|
await log_operation(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
user_id=int(current_user.id),
|
|
project_id=pid,
|
|
entity_type="charter",
|
|
entity_id=str(charter.id),
|
|
action="new_version",
|
|
before_data=before,
|
|
after_data={"new_charter_id": "pending"},
|
|
)
|
|
await db.commit()
|
|
await db.refresh(new_node)
|
|
await AIPMOKernelService.invalidate_world_model_cache(tenant_id=tenant_id, project_id=pid)
|
|
_sync_pmflow_rag_async(
|
|
tenant_id=tenant_id,
|
|
record_type="charter",
|
|
record_id=int(new_node.id),
|
|
project_id=pid,
|
|
title=new_node.title,
|
|
content=_as_dict(new_node.content),
|
|
)
|
|
new_payload = _build_charter_summary_payload(new_node)
|
|
new_payload["new_version_created"] = True
|
|
return success_response(new_payload)
|
|
|
|
if body.title is not None:
|
|
charter.title = compact_title(body.title, max_len=24)
|
|
if body.content is not None:
|
|
charter.content = body.content
|
|
charter.status = "draft"
|
|
await log_operation(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
user_id=int(current_user.id),
|
|
project_id=pid,
|
|
entity_type="charter",
|
|
entity_id=str(charter.id),
|
|
action="update",
|
|
before_data=before,
|
|
after_data={
|
|
"title": charter.title,
|
|
"content": _as_dict(charter.content),
|
|
"version": charter.version,
|
|
"status": charter.status,
|
|
},
|
|
)
|
|
await db.commit()
|
|
await db.refresh(charter)
|
|
await AIPMOKernelService.invalidate_world_model_cache(tenant_id=tenant_id, project_id=pid)
|
|
_sync_pmflow_rag_async(
|
|
tenant_id=tenant_id,
|
|
record_type="charter",
|
|
record_id=int(charter.id),
|
|
project_id=pid,
|
|
title=charter.title,
|
|
content=_as_dict(charter.content),
|
|
)
|
|
update_payload = _build_charter_summary_payload(charter)
|
|
update_payload["new_version_created"] = False
|
|
return success_response(update_payload)
|
|
|