PMFlow/backend/app/schemas/pmflow_os.py

649 lines
23 KiB
Python

"""PMFlow OS read-side projection schemas."""
from __future__ import annotations
from typing import Any, Literal, Optional
from pydantic import BaseModel, Field
PMFlowStage = Literal["idea", "validation", "planning", "development", "testing", "release", "growth"]
PMFlowObjectStatus = Literal["draft", "confirmed", "in_progress", "blocked", "done", "released"]
PMFlowRiskLevel = Literal["low", "medium", "high"]
PMFlowExecutionStatus = Literal["queued", "pushed", "coding", "pr_open", "testing", "synced", "failed"]
PMFlowReleaseStatus = Literal["check_pending", "ready", "shipping", "live", "watch"]
PMFlowExecutionRuntimeTarget = Literal["local_bridge", "codex", "github_agent", "manual"]
PMFlowExecutionProgressSource = Literal["local_bridge", "codex", "github_agent", "manual"]
PMFlowExecutionProgressEventType = Literal[
"branch_created",
"commit",
"pr_opened",
"pr_synced",
"test_passed",
"test_failed",
"checks_passed",
"failed",
"blocked",
"merged",
"accepted",
"manual_progress",
]
PMFlowReleaseGateStatus = Literal["ready", "blocked", "needs_confirmation", "empty"]
PMFlowReleaseValidationDecision = Literal["enter_release_candidate", "keep_blocked"]
ProjectLoopStage = Literal["command_idle", "ai_extracting", "extraction_ready", "review_open", "writeback_success"]
PMFlowChangeGroup = Literal["new_object", "field_update", "risk", "decision"]
PMFlowChangeStatus = Literal["pending_review", "accepted", "edited", "deferred", "committed", "skipped"]
PMFlowReviewerRoutingStatus = Literal["empty", "ready", "warning"]
PMFlowReviewerRoutingSourceType = Literal["manual", "ai_extraction", "iteration_recommendation", "unknown"]
PMFlowReviewerRoutingPriority = Literal["normal", "elevated", "urgent"]
PMFlowReviewerConfidence = Literal["low", "medium", "high"]
PMFlowFallbackReviewerRole = Literal["OWNER", "REVIEWER", "MEMBER"]
PMFlowReviewDecisionAction = Literal["request_review", "approve", "request_changes", "reject"]
PMFlowApprovalState = Literal["not_requested", "requested", "approved", "changes_requested", "rejected"]
PMFlowAIFeedbackRating = Literal["positive", "negative"]
PMFlowAIFeedbackAnswerKind = Literal["chat_answer", "structured_extraction", "copilot_answer"]
PMFlowAIFeedbackTargetType = Literal["thread_message", "change_set", "copilot_answer"]
PMFlowExecutionCapabilityMode = Literal[
"ai_direct",
"ai_with_user_acceptance",
"ai_with_technical_review",
"needs_human_judgement",
"blocked",
]
PMFlowExecutionCapabilityStatus = Literal["ready", "partial", "blocked", "unknown"]
PMFlowHumanWorkType = Literal["focus", "review", "acceptance", "coordination"]
PMFlowCapacityStatus = Literal["normal", "attention", "near_full", "overloaded", "blocked", "manually_extended"]
PMFlowCapacityFitStatus = Literal["recommended", "allowed_with_warning", "not_recommended", "blocked"]
PMFlowCapacityOverrideType = Literal["extend_minutes", "overtime", "lock_capacity", "work_type_bias", "high_risk_exception"]
PMFlowOvertimeType = Literal["general", "focus", "review", "release_guard"]
PMFlowHumanTouchSourceType = Literal[
"changeset_confirmation",
"review",
"acceptance",
"release_decision",
"context_completion",
"ai_failure_recovery",
]
PMFlowHumanTouchBindingType = Literal["hard_bound", "role_bound", "ai_shiftable"]
PMFlowObjectType = Literal[
"Requirement",
"Decision",
"Task",
"Risk",
"ModelDecision",
"TestRun",
"Release",
"MetricSignal",
]
class PMFlowThreadMessageCreateRequest(BaseModel):
content: str = Field(min_length=1, max_length=12000)
source: str = "web"
attachments: list[dict[str, Any]] = Field(default_factory=list)
class PMFlowThreadMessageResponse(BaseModel):
id: str
threadId: str
projectId: str
role: str
author: str
content: str
createdAt: str
status: str = "sent"
source: str = "web"
metadata: dict[str, Any] = Field(default_factory=dict)
class PMFlowAIFeedbackCreateRequest(BaseModel):
target_type: PMFlowAIFeedbackTargetType
target_id: str = Field(min_length=1, max_length=128)
answer_kind: PMFlowAIFeedbackAnswerKind = "chat_answer"
rating: PMFlowAIFeedbackRating
reason_tags: list[str] = Field(default_factory=list)
comment: Optional[str] = Field(default=None, max_length=4000)
thread_id: Optional[str] = None
message_id: Optional[str] = None
change_set_id: Optional[str] = None
ai_call_log_id: Optional[str] = None
scene_key: Optional[str] = Field(default=None, max_length=128)
prompt_key: Optional[str] = Field(default=None, max_length=128)
prompt_release_id: Optional[int] = None
runtime_profile_id: Optional[str] = Field(default=None, max_length=128)
model_provider: Optional[str] = Field(default=None, max_length=64)
model_name: Optional[str] = Field(default=None, max_length=120)
feedback_metadata: dict[str, Any] = Field(default_factory=dict)
class PMFlowAIFeedbackResponse(BaseModel):
id: str
projectId: str
threadId: Optional[str] = None
messageId: Optional[str] = None
changeSetId: Optional[str] = None
aiCallLogId: Optional[str] = None
targetType: str
targetId: str
answerKind: str
rating: PMFlowAIFeedbackRating
reasonTags: list[str] = Field(default_factory=list)
comment: Optional[str] = None
sceneKey: Optional[str] = None
promptKey: Optional[str] = None
promptReleaseId: Optional[int] = None
runtimeProfileId: Optional[str] = None
modelProvider: Optional[str] = None
modelName: Optional[str] = None
metadata: dict[str, Any] = Field(default_factory=dict)
createdAt: str
updatedAt: str
class PMFlowProposedChangeItem(BaseModel):
group: PMFlowChangeGroup
object_type: PMFlowObjectType
object_id: Optional[str] = None
title: str = Field(min_length=1, max_length=255)
summary: str = Field(min_length=1)
diff: str = Field(default="")
payload: dict[str, Any] = Field(default_factory=dict)
confidence: float = Field(default=0.0, ge=0.0, le=1.0)
risk_level: PMFlowRiskLevel = "medium"
class PMFlowExtractionCreateRequest(BaseModel):
thread_id: str
message_id: str
scope: str = "latest_message"
summary: Optional[str] = None
headline: Optional[str] = None
routing_meta: dict[str, Any] = Field(default_factory=dict)
llm_call_id: Optional[str] = None
proposed_items: list[PMFlowProposedChangeItem] = Field(default_factory=list)
class PMFlowChangeItemEditRequest(BaseModel):
summary: Optional[str] = None
diff: Optional[str] = None
payload_patch: dict[str, Any] = Field(default_factory=dict)
reason: Optional[str] = None
class PMFlowChangeItemDeferRequest(BaseModel):
reason: Optional[str] = None
class PMFlowChangeSetCommitRequest(BaseModel):
item_ids: list[str] = Field(default_factory=list)
mode: Literal["atomic"] = "atomic"
note: Optional[str] = None
class PMFlowExecutionHandoffRequest(BaseModel):
task_id: str = Field(min_length=1)
repo_binding_id: Optional[str] = None
runtime_target: PMFlowExecutionRuntimeTarget = "local_bridge"
note: Optional[str] = Field(default=None, max_length=4000)
class PMFlowExecutionHandoffConsumeRequest(BaseModel):
runtime_target: Optional[PMFlowExecutionRuntimeTarget] = None
consumer_label: Optional[str] = Field(default=None, max_length=120)
note: Optional[str] = Field(default=None, max_length=4000)
class PMFlowExecutionProgressEventRequest(BaseModel):
task_id: str = Field(min_length=1)
event_type: PMFlowExecutionProgressEventType
source: PMFlowExecutionProgressSource = "manual"
repo_binding_id: Optional[str] = None
branch_name: Optional[str] = Field(default=None, max_length=255)
commit_sha: Optional[str] = Field(default=None, max_length=255)
pr_number: Optional[int] = Field(default=None, ge=1)
test_summary: Optional[str] = Field(default=None, max_length=4000)
workflow_state: Optional[str] = Field(default=None, max_length=120)
summary: Optional[str] = Field(default=None, max_length=4000)
event_source_id: Optional[str] = Field(default=None, max_length=255)
occurred_at: Optional[str] = None
class PMFlowReleaseValidationRunCreateRequest(BaseModel):
decision: PMFlowReleaseValidationDecision
note: Optional[str] = Field(default=None, max_length=4000)
class PMFlowReleaseFeedbackIngestRequest(BaseModel):
source_type: str = Field(default="manual", min_length=1, max_length=32)
title: Optional[str] = Field(default=None, max_length=255)
content: str = Field(min_length=1, max_length=12000)
charter_id: Optional[str] = None
metrics: dict[str, Any] = Field(default_factory=dict)
samples: list[dict[str, Any]] = Field(default_factory=list)
class PMFlowIterationRecommendationChangeSetDraftRequest(BaseModel):
note: Optional[str] = Field(default=None, max_length=4000)
class PMFlowIterationRecommendationGroupChangeSetDraftRequest(BaseModel):
recommendation_ids: list[str] = Field(default_factory=list)
note: Optional[str] = Field(default=None, max_length=4000)
class PMFlowReviewDecisionRequest(BaseModel):
action: PMFlowReviewDecisionAction
reviewer_id: Optional[int] = Field(default=None, ge=1)
reason: Optional[str] = Field(default=None, max_length=4000)
item_ids: list[str] = Field(default_factory=list)
class PMFlowChangeSetDetailResponse(BaseModel):
changeSet: dict[str, Any]
items: list[dict[str, Any]] = Field(default_factory=list)
evidence: list[dict[str, Any]] = Field(default_factory=list)
approvals: list[dict[str, Any]] = Field(default_factory=list)
class PMFlowThread(BaseModel):
id: str
title: str
projectId: str
objective: str
extractionStatus: Literal["idle", "loading", "ready"] = "idle"
messageIds: list[str] = Field(default_factory=list)
class PMFlowProjectSummary(BaseModel):
id: str
name: str
goal: str
stage: PMFlowStage
threadId: str
class PMFlowRequirement(BaseModel):
id: str
title: str
summary: str
stage: PMFlowStage = "planning"
status: PMFlowObjectStatus
linkedTaskIds: list[str] = Field(default_factory=list)
class PMFlowDecision(BaseModel):
id: str
title: str
summary: str
stage: PMFlowStage = "planning"
status: PMFlowObjectStatus
owner: str = "未分配"
class PMFlowTask(BaseModel):
id: str
title: str
summary: str
stage: PMFlowStage = "development"
status: PMFlowObjectStatus
assignee: str = "未分配"
acceptanceCriteria: list[str] = Field(default_factory=list)
requirementId: Optional[str] = None
class PMFlowRisk(BaseModel):
id: str
title: str
summary: str
stage: PMFlowStage = "planning"
status: PMFlowObjectStatus
level: PMFlowRiskLevel = "medium"
mitigation: str = "待补充缓解策略"
class PMFlowModelDecision(BaseModel):
id: str
title: str
summary: str
stage: PMFlowStage = "planning"
status: PMFlowObjectStatus
rationale: str = ""
class PMFlowTestRun(BaseModel):
id: str
title: str
summary: str
stage: PMFlowStage = "testing"
status: PMFlowObjectStatus
result: Literal["passed", "failed"]
linkedTaskId: Optional[str] = None
class PMFlowRelease(BaseModel):
id: str
title: str
summary: str
status: PMFlowReleaseStatus
stage: PMFlowStage = "release"
gateItems: list[dict[str, Any]] = Field(default_factory=list)
class PMFlowMetricSignal(BaseModel):
id: str
title: str
summary: str
stage: PMFlowStage = "growth"
status: PMFlowObjectStatus = "confirmed"
value: str
direction: Literal["up", "down", "flat"] = "flat"
class PMFlowFeedEvent(BaseModel):
id: str
kind: Literal["extraction", "review", "execution", "test", "release", "growth", "agent"]
title: str
summary: str
timestamp: str
class PMFlowExecutionContext(BaseModel):
taskId: Optional[str] = None
status: PMFlowExecutionStatus = "queued"
prompt: str = ""
commitLog: list[str] = Field(default_factory=list)
prStatus: str = "pending"
testSummary: str = "pending"
writebackStatus: str = "pending"
class PMFlowTeamInbox(BaseModel):
product: list[str] = Field(default_factory=list)
engineering: list[str] = Field(default_factory=list)
qa: list[str] = Field(default_factory=list)
growth: list[str] = Field(default_factory=list)
class PMFlowReviewerSuggestion(BaseModel):
userId: Optional[str] = None
displayName: Optional[str] = None
role: Optional[str] = None
reason: str
confidence: PMFlowReviewerConfidence = "low"
class PMFlowReviewerRoutingItem(BaseModel):
projectId: str
changeSetId: str
changeSetTitle: str
changeSetStatus: str
sourceType: PMFlowReviewerRoutingSourceType = "unknown"
reviewReason: str
riskLevel: PMFlowRiskLevel = "low"
priority: PMFlowReviewerRoutingPriority = "normal"
changeTypes: list[str] = Field(default_factory=list)
itemCount: int = 0
pendingItemCount: int = 0
evidenceCount: int = 0
suggestedReviewer: Optional[PMFlowReviewerSuggestion] = None
fallbackReviewerRole: Optional[PMFlowFallbackReviewerRole] = None
reviewDecision: Optional[PMFlowReviewDecisionAction] = None
approvalState: PMFlowApprovalState = "not_requested"
requestedAt: Optional[str] = None
decidedAt: Optional[str] = None
decisionReason: Optional[str] = None
targetUrl: str
createdAt: Optional[str] = None
updatedAt: Optional[str] = None
class PMFlowReviewerRoutingSummary(BaseModel):
totalPendingReview: int = 0
highRiskCount: int = 0
missingReviewerCount: int = 0
iterationDraftCount: int = 0
requestedReviewCount: int = 0
approvedCount: int = 0
changesRequestedCount: int = 0
rejectedCount: int = 0
class PMFlowReviewerRoutingProjection(BaseModel):
status: PMFlowReviewerRoutingStatus = "empty"
warningCode: Optional[str] = None
items: list[PMFlowReviewerRoutingItem] = Field(default_factory=list)
summary: PMFlowReviewerRoutingSummary = Field(default_factory=PMFlowReviewerRoutingSummary)
class PMFlowProjectMemory(BaseModel):
truthNodeFacts: list[dict[str, Any]] = Field(default_factory=list)
semanticFacts: list[dict[str, Any]] = Field(default_factory=list)
sourceSummary: dict[str, int] = Field(default_factory=dict)
class ProjectTruthStateProjection(BaseModel):
loopStage: ProjectLoopStage = "command_idle"
lastWritebackAt: Optional[str] = None
lastWritebackBy: Optional[str] = None
lastWritebackById: Optional[str] = None
project: PMFlowProjectSummary
thread: PMFlowThread
messages: list[dict[str, Any]] = Field(default_factory=list)
changeSets: list[dict[str, Any]] = Field(default_factory=list)
changeItems: list[dict[str, Any]] = Field(default_factory=list)
evidence: list[dict[str, Any]] = Field(default_factory=list)
requirements: list[PMFlowRequirement] = Field(default_factory=list)
decisions: list[PMFlowDecision] = Field(default_factory=list)
tasks: list[PMFlowTask] = Field(default_factory=list)
risks: list[PMFlowRisk] = Field(default_factory=list)
modelDecisions: list[PMFlowModelDecision] = Field(default_factory=list)
testRuns: list[PMFlowTestRun] = Field(default_factory=list)
releases: list[PMFlowRelease] = Field(default_factory=list)
metricSignals: list[PMFlowMetricSignal] = Field(default_factory=list)
approvals: list[dict[str, Any]] = Field(default_factory=list)
aiFeedback: list[PMFlowAIFeedbackResponse] = Field(default_factory=list)
feed: list[PMFlowFeedEvent] = Field(default_factory=list)
teamInbox: PMFlowTeamInbox = Field(default_factory=PMFlowTeamInbox)
reviewerRouting: PMFlowReviewerRoutingProjection = Field(default_factory=PMFlowReviewerRoutingProjection)
activeChangeSetId: Optional[str] = None
selectedTaskId: Optional[str] = None
execution: PMFlowExecutionContext = Field(default_factory=PMFlowExecutionContext)
projectMemory: PMFlowProjectMemory = Field(default_factory=PMFlowProjectMemory)
projectionWarnings: list[str] = Field(default_factory=list)
class ExecutionSummaryProjection(BaseModel):
projectId: str
execution: PMFlowExecutionContext
releaseGate: dict[str, Any] = Field(default_factory=dict)
releaseSignals: dict[str, Any] = Field(default_factory=dict)
devSync: dict[str, Any] = Field(default_factory=dict)
promptPackage: Optional[dict[str, Any]] = None
repoBindings: list[dict[str, Any]] = Field(default_factory=list)
activeDevTasks: list[dict[str, Any]] = Field(default_factory=list)
recentProgressEvents: list[dict[str, Any]] = Field(default_factory=list)
recentDeliveries: list[dict[str, Any]] = Field(default_factory=list)
warnings: list[str] = Field(default_factory=list)
class PMFlowExecutionModeCapability(BaseModel):
mode: PMFlowExecutionCapabilityMode
label: str
status: PMFlowExecutionCapabilityStatus = "unknown"
reason: str
targetHref: Optional[str] = None
class PMFlowToolReadinessItem(BaseModel):
key: str
label: str
status: PMFlowExecutionCapabilityStatus = "unknown"
summary: str
href: Optional[str] = None
class PMFlowPersonalExecutionReadiness(BaseModel):
status: PMFlowExecutionCapabilityStatus = "unknown"
toolReadiness: list[PMFlowToolReadinessItem] = Field(default_factory=list)
waitingForUser: dict[str, int] = Field(default_factory=dict)
blockers: list[str] = Field(default_factory=list)
recommendedNextStep: str = "回到项目主流程查看下一步。"
targetHref: str
class PMFlowTeamCapacitySignal(BaseModel):
status: PMFlowExecutionCapabilityStatus = "unknown"
label: str
workloadRatio: float = 0.0
pendingReviewCount: int = 0
activeExecutionCount: int = 0
class PMFlowTeamContextSignal(BaseModel):
status: PMFlowExecutionCapabilityStatus = "unknown"
label: str
evidenceCount: int = 0
recentContributionCount: int = 0
class PMFlowTeamToolSignal(BaseModel):
status: PMFlowExecutionCapabilityStatus = "unknown"
label: str
connectedTools: list[str] = Field(default_factory=list)
class PMFlowTeamExecutionSignal(BaseModel):
userId: str
displayName: str
role: str
judgementResponsibilities: list[str] = Field(default_factory=list)
reviewResponsibilities: list[str] = Field(default_factory=list)
capacity: PMFlowTeamCapacitySignal
context: PMFlowTeamContextSignal
toolStatus: PMFlowTeamToolSignal
referenceSkills: list[str] = Field(default_factory=list)
recommendedModes: list[PMFlowExecutionCapabilityMode] = Field(default_factory=list)
warnings: list[str] = Field(default_factory=list)
class PMFlowWorkTypeDistribution(BaseModel):
focus: float = 0.0
review: float = 0.0
acceptance: float = 0.0
coordination: float = 0.0
class PMFlowWorkTypeMinutes(BaseModel):
focus: int = 0
review: int = 0
acceptance: int = 0
coordination: int = 0
total: int = 0
class PMFlowHumanTouchEstimate(BaseModel):
sourceType: PMFlowHumanTouchSourceType
sourceId: str
targetHref: str
workType: PMFlowHumanWorkType
estimatedMinutes: int
riskLevel: PMFlowRiskLevel = "low"
urgency: str = "normal"
bindingType: PMFlowHumanTouchBindingType = "role_bound"
assignedUserId: Optional[str] = None
roleBinding: Optional[str] = None
contextReadiness: PMFlowExecutionCapabilityStatus = "unknown"
aiReductionPotentialMinutes: int = 0
title: str
summary: str
class PMFlowCapacityFit(BaseModel):
workType: PMFlowHumanWorkType
label: str
status: PMFlowCapacityFitStatus
estimatedMinutes: int
afterTotalUtilization: float
afterTypeShare: float
reason: str
class PMFlowCapacityOverridePayload(BaseModel):
userId: str
date: Optional[str] = None
overrideType: PMFlowCapacityOverrideType
addedMinutes: int = Field(default=0, ge=0, le=720)
overtimeType: Optional[PMFlowOvertimeType] = None
maxPlannedUtilization: Optional[float] = Field(default=None, ge=0.5, le=1.25)
workTypeBias: dict[str, float] = Field(default_factory=dict)
highRiskLimit: Optional[int] = Field(default=None, ge=0, le=20)
reason: str = Field(..., min_length=2, max_length=500)
expiresAt: Optional[str] = None
class PMFlowCapacityOverrideRecord(BaseModel):
id: str
userId: str
date: str
overrideType: PMFlowCapacityOverrideType
addedMinutes: int = 0
overtimeType: Optional[PMFlowOvertimeType] = None
maxPlannedUtilization: Optional[float] = None
workTypeBias: dict[str, float] = Field(default_factory=dict)
highRiskLimit: Optional[int] = None
reason: str
expiresAt: str
createdBy: str
createdAt: Optional[str] = None
class PMFlowMemberCapacityPlan(BaseModel):
userId: str
displayName: str
role: str
baseAvailableMinutes: int = 480
calendarBlockedMinutes: int = 0
effectiveMinutes: int = 480
reservedBufferMinutes: int = 72
maxPlannedUtilization: float = 0.85
suggestedBaseline: PMFlowWorkTypeDistribution = Field(default_factory=PMFlowWorkTypeDistribution)
currentMix: PMFlowWorkTypeDistribution = Field(default_factory=PMFlowWorkTypeDistribution)
plannedTouchMinutes: PMFlowWorkTypeMinutes = Field(default_factory=PMFlowWorkTypeMinutes)
capacityStatus: PMFlowCapacityStatus = "normal"
remainingMinutes: int = 0
bottleneckType: Optional[PMFlowHumanWorkType] = None
fatigueRisk: dict[str, str] = Field(default_factory=dict)
riskDensity: dict[str, int] = Field(default_factory=dict)
canTakeMore: list[PMFlowCapacityFit] = Field(default_factory=list)
manualOverride: Optional[PMFlowCapacityOverrideRecord] = None
explanations: list[str] = Field(default_factory=list)
class PMFlowDailyCapacityPlan(BaseModel):
projectId: str
date: str
generatedAt: str
teamDemandMix: PMFlowWorkTypeDistribution = Field(default_factory=PMFlowWorkTypeDistribution)
teamEffectiveMinutes: int = 0
teamPlannedTouchMinutes: PMFlowWorkTypeMinutes = Field(default_factory=PMFlowWorkTypeMinutes)
memberPlans: list[PMFlowMemberCapacityPlan] = Field(default_factory=list)
humanTouches: list[PMFlowHumanTouchEstimate] = Field(default_factory=list)
warnings: list[str] = Field(default_factory=list)
recommendations: list[str] = Field(default_factory=list)
class PMFlowCapacityOverrideSimulation(BaseModel):
beforePlan: PMFlowMemberCapacityPlan
afterPlan: PMFlowMemberCapacityPlan
impactSummary: list[str] = Field(default_factory=list)
warnings: list[str] = Field(default_factory=list)
class PMFlowExecutionCapabilitySnapshot(BaseModel):
projectId: str
executionModes: list[PMFlowExecutionModeCapability] = Field(default_factory=list)
personalReadiness: PMFlowPersonalExecutionReadiness
teamSignals: list[PMFlowTeamExecutionSignal] = Field(default_factory=list)
adaptiveCapacitySummary: Optional[PMFlowDailyCapacityPlan] = None
warnings: list[str] = Field(default_factory=list)