|
|
@@ -20,13 +20,18 @@ from .production_execution_models import (
|
|
|
SegmentPackage,
|
|
|
)
|
|
|
from .production_models import (
|
|
|
+ MAX_PRODUCTION_PLAN_REVISIONS,
|
|
|
ProductionContractIssue,
|
|
|
ProductionInputCatalog,
|
|
|
+ SegmentDelivery,
|
|
|
+ SegmentValidationReport,
|
|
|
)
|
|
|
from .production_planning_models import (
|
|
|
PlannedSegment,
|
|
|
ProductionPlan,
|
|
|
ProductionPlanningPackage,
|
|
|
+ ProductionProgressDecision,
|
|
|
+ ProductionProgressPackage,
|
|
|
)
|
|
|
from .production_validation_models import (
|
|
|
ProductionValidationReport,
|
|
|
@@ -34,6 +39,22 @@ from .production_validation_models import (
|
|
|
)
|
|
|
|
|
|
|
|
|
+def remaining_plan_revisions(
|
|
|
+ plan_version: int,
|
|
|
+ max_plan_revisions: int,
|
|
|
+) -> int:
|
|
|
+ """由正式 Plan version 和 Run 预算确定性计算剩余 revision 次数。"""
|
|
|
+
|
|
|
+ if plan_version < 1:
|
|
|
+ raise ValueError("plan_version 必须至少为 1")
|
|
|
+ if not 0 <= max_plan_revisions <= MAX_PRODUCTION_PLAN_REVISIONS:
|
|
|
+ raise ValueError(
|
|
|
+ "max_plan_revisions 必须在 0 到 "
|
|
|
+ f"{MAX_PRODUCTION_PLAN_REVISIONS} 之间"
|
|
|
+ )
|
|
|
+ return max(0, max_plan_revisions - (plan_version - 1))
|
|
|
+
|
|
|
+
|
|
|
def evaluate_production_planning_package(
|
|
|
brief: ProductionBrief,
|
|
|
delivery: GlobalDataStageDelivery,
|
|
|
@@ -99,6 +120,324 @@ def evaluate_production_planning_package(
|
|
|
return issues
|
|
|
|
|
|
|
|
|
+def evaluate_production_progress_package(
|
|
|
+ package: ProductionProgressPackage,
|
|
|
+ *,
|
|
|
+ planning_package: ProductionPlanningPackage,
|
|
|
+ planning_package_ref: ImmutableJsonRef,
|
|
|
+ production_plan: ProductionPlan,
|
|
|
+ production_plan_ref: ImmutableJsonRef,
|
|
|
+ segment_package: SegmentPackage,
|
|
|
+ segment_package_ref: ImmutableJsonRef,
|
|
|
+ segment_delivery: SegmentDelivery,
|
|
|
+ segment_delivery_ref: ImmutableJsonRef,
|
|
|
+ segment_validation_report: SegmentValidationReport,
|
|
|
+ segment_validation_report_ref: ImmutableJsonRef,
|
|
|
+ accepted_segments: Iterable[AcceptedSegmentRef],
|
|
|
+ remaining_plan_revisions: int,
|
|
|
+) -> list[ProductionContractIssue]:
|
|
|
+ """重验 ProgressPackage 精确绑定当前正式 Segment 证据链。"""
|
|
|
+
|
|
|
+ issues: list[ProductionContractIssue] = []
|
|
|
+ expected_identity = (
|
|
|
+ package.run_id,
|
|
|
+ package.plan_id,
|
|
|
+ package.plan_version,
|
|
|
+ package.segment_id,
|
|
|
+ )
|
|
|
+ for label, value in (
|
|
|
+ (
|
|
|
+ "planning_package",
|
|
|
+ (
|
|
|
+ planning_package.run_id,
|
|
|
+ planning_package.plan_id,
|
|
|
+ planning_package.target_plan_version,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ "production_plan",
|
|
|
+ (
|
|
|
+ package.run_id,
|
|
|
+ production_plan.plan_id,
|
|
|
+ production_plan.plan_version,
|
|
|
+ package.segment_id,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ "segment_package",
|
|
|
+ (
|
|
|
+ segment_package.run_id,
|
|
|
+ segment_package.plan_id,
|
|
|
+ segment_package.plan_version,
|
|
|
+ segment_package.segment_id,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ "segment_delivery",
|
|
|
+ (
|
|
|
+ segment_delivery.run_id,
|
|
|
+ segment_delivery.plan_id,
|
|
|
+ segment_delivery.plan_version,
|
|
|
+ segment_delivery.segment_id,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ "segment_validation_report",
|
|
|
+ (
|
|
|
+ segment_validation_report.run_id,
|
|
|
+ segment_validation_report.plan_id,
|
|
|
+ segment_validation_report.plan_version,
|
|
|
+ segment_validation_report.segment_id,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ):
|
|
|
+ expected = (
|
|
|
+ expected_identity[:3]
|
|
|
+ if label == "planning_package"
|
|
|
+ else expected_identity
|
|
|
+ )
|
|
|
+ if value != expected:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_identity_mismatch",
|
|
|
+ f"{label} 与 ProductionProgressPackage 身份不一致",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ for field_name, expected in (
|
|
|
+ ("planning_package_ref", planning_package_ref),
|
|
|
+ ("production_plan_ref", production_plan_ref),
|
|
|
+ ("segment_package_ref", segment_package_ref),
|
|
|
+ ("segment_delivery_ref", segment_delivery_ref),
|
|
|
+ (
|
|
|
+ "segment_validation_report_ref",
|
|
|
+ segment_validation_report_ref,
|
|
|
+ ),
|
|
|
+ ):
|
|
|
+ if getattr(package, field_name) != expected:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_document_ref_mismatch",
|
|
|
+ f"ProductionProgressPackage.{field_name} 未绑定当前精确字节",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ if production_plan.planning_package_ref != planning_package_ref:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_plan_source_mismatch",
|
|
|
+ "ProductionPlan 没有绑定当前 PlanningPackage",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ segment_delivery.segment_package_uri != segment_package_ref.uri
|
|
|
+ or segment_delivery.segment_package_sha256
|
|
|
+ != segment_package_ref.document_sha256
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_delivery_source_mismatch",
|
|
|
+ "SegmentDelivery 没有绑定当前 SegmentPackage",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ segment_validation_report.executor_run_id
|
|
|
+ != segment_delivery.executor_run_id
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_validator_source_mismatch",
|
|
|
+ "SegmentValidationReport 与 SegmentDelivery executor_run_id 不一致",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+
|
|
|
+ expected_accepted = list(accepted_segments)
|
|
|
+ if package.accepted_segments != expected_accepted:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_accepted_snapshot_mismatch",
|
|
|
+ "ProductionProgressPackage 没有绑定当前 Accepted Segment 快照",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ known_ids = {item.segment_id for item in production_plan.segments}
|
|
|
+ for accepted in package.accepted_segments:
|
|
|
+ if accepted.segment_id not in known_ids:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_unknown_accepted_segment",
|
|
|
+ "Accepted Segment 不属于当前 ProductionPlan",
|
|
|
+ segment_id=accepted.segment_id,
|
|
|
+ )
|
|
|
+ current_accepted = next(
|
|
|
+ (
|
|
|
+ accepted
|
|
|
+ for accepted in package.accepted_segments
|
|
|
+ if accepted.segment_id == package.segment_id
|
|
|
+ ),
|
|
|
+ None,
|
|
|
+ )
|
|
|
+ if segment_validation_report.verdict == "PASS":
|
|
|
+ if current_accepted is None:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_pass_not_accepted",
|
|
|
+ "正式 PASS Segment 必须已进入 Accepted 快照",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ elif (
|
|
|
+ current_accepted.source_run_id != package.run_id
|
|
|
+ or current_accepted.source_plan_id != package.plan_id
|
|
|
+ or current_accepted.source_plan_version != package.plan_version
|
|
|
+ or current_accepted.segment_package_ref != segment_package_ref
|
|
|
+ or current_accepted.segment_delivery_ref != segment_delivery_ref
|
|
|
+ or current_accepted.validation_report_ref
|
|
|
+ != segment_validation_report_ref
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_pass_acceptance_mismatch",
|
|
|
+ "当前 PASS AcceptedSegmentRef 没有绑定同一正式证据链",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ elif current_accepted is not None:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_fail_cannot_be_accepted",
|
|
|
+ "正式 FAIL Segment 不得进入 Accepted 快照",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ if package.remaining_plan_revisions != remaining_plan_revisions:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_revision_budget_mismatch",
|
|
|
+ "ProductionProgressPackage 的 Plan revision 余额不一致",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ return issues
|
|
|
+
|
|
|
+
|
|
|
+def evaluate_production_progress_decision(
|
|
|
+ package: ProductionProgressPackage,
|
|
|
+ decision: ProductionProgressDecision,
|
|
|
+ *,
|
|
|
+ progress_package_ref: ImmutableJsonRef,
|
|
|
+ production_plan: ProductionPlan,
|
|
|
+ segment_validation_report: SegmentValidationReport,
|
|
|
+) -> list[ProductionContractIssue]:
|
|
|
+ """根据正式 verdict、pending DAG 和预算重算 Planner 路由决定。"""
|
|
|
+
|
|
|
+ issues: list[ProductionContractIssue] = []
|
|
|
+ if (
|
|
|
+ decision.run_id,
|
|
|
+ decision.plan_id,
|
|
|
+ decision.plan_version,
|
|
|
+ decision.segment_id,
|
|
|
+ ) != (
|
|
|
+ package.run_id,
|
|
|
+ package.plan_id,
|
|
|
+ package.plan_version,
|
|
|
+ package.segment_id,
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_decision_identity_mismatch",
|
|
|
+ "ProductionProgressDecision 与 ProgressPackage 身份不一致",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ if decision.progress_package_ref != progress_package_ref:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_decision_ref_mismatch",
|
|
|
+ "ProductionProgressDecision 没有绑定当前 ProgressPackage",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ segment_validation_report.run_id != package.run_id
|
|
|
+ or segment_validation_report.plan_id != package.plan_id
|
|
|
+ or segment_validation_report.plan_version != package.plan_version
|
|
|
+ or segment_validation_report.segment_id != package.segment_id
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_report_identity_mismatch",
|
|
|
+ "SegmentValidationReport 与 ProgressPackage 身份不一致",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+
|
|
|
+ allowed_actions = (
|
|
|
+ {"CONTINUE", "ADAPT", "STOP"}
|
|
|
+ if segment_validation_report.verdict == "PASS"
|
|
|
+ else {"REPLAN", "STOP"}
|
|
|
+ )
|
|
|
+ if decision.action not in allowed_actions:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_action_verdict_mismatch",
|
|
|
+ "Planner action 不符合正式 Segment verdict",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+
|
|
|
+ plan_ids = {item.segment_id for item in production_plan.segments}
|
|
|
+ if (
|
|
|
+ production_plan.plan_id != package.plan_id
|
|
|
+ or production_plan.plan_version != package.plan_version
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_plan_identity_mismatch",
|
|
|
+ "ProductionPlan 与 ProgressPackage 身份不一致",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ accepted_ids = {
|
|
|
+ item.segment_id for item in package.accepted_segments
|
|
|
+ }
|
|
|
+ pending_ids = plan_ids - accepted_ids
|
|
|
+ if decision.action == "ADAPT":
|
|
|
+ for segment_id in set(decision.affected_segment_ids) - pending_ids:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_adapt_segment_not_pending",
|
|
|
+ "ADAPT 只能修改尚未 Accepted 的 Segment",
|
|
|
+ segment_id=segment_id,
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ decision.action in {"ADAPT", "REPLAN"}
|
|
|
+ and package.remaining_plan_revisions == 0
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "production_progress_revision_budget_exhausted",
|
|
|
+ "ADAPT 或 REPLAN 没有剩余 Plan revision 预算",
|
|
|
+ segment_id=package.segment_id,
|
|
|
+ )
|
|
|
+ return issues
|
|
|
+
|
|
|
+
|
|
|
+def validate_production_progress_package(
|
|
|
+ package: ProductionProgressPackage,
|
|
|
+ **context: object,
|
|
|
+) -> None:
|
|
|
+ """ProgressPackage 不满足正式证据链时抛出结构化异常。"""
|
|
|
+
|
|
|
+ issues = evaluate_production_progress_package(package, **context)
|
|
|
+ if issues:
|
|
|
+ raise ProductionContractError(issues)
|
|
|
+
|
|
|
+
|
|
|
+def validate_production_progress_decision(
|
|
|
+ package: ProductionProgressPackage,
|
|
|
+ decision: ProductionProgressDecision,
|
|
|
+ **context: object,
|
|
|
+) -> None:
|
|
|
+ """ProgressDecision 不满足确定性路由规则时抛出结构化异常。"""
|
|
|
+
|
|
|
+ issues = evaluate_production_progress_decision(
|
|
|
+ package,
|
|
|
+ decision,
|
|
|
+ **context,
|
|
|
+ )
|
|
|
+ if issues:
|
|
|
+ raise ProductionContractError(issues)
|
|
|
+
|
|
|
+
|
|
|
def _dependency_issues(
|
|
|
plan: ProductionPlan,
|
|
|
) -> list[ProductionContractIssue]:
|
|
|
@@ -208,9 +547,15 @@ def evaluate_production_plan(
|
|
|
"initial_plan_cannot_regenerate",
|
|
|
"INITIAL ProductionPlan 不得声明 regenerate_segment_ids",
|
|
|
)
|
|
|
+ if planning_package.mode == "ADAPT" and plan.regenerate_segment_ids:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_plan_cannot_regenerate",
|
|
|
+ "ADAPT 只调整未来 Plan,不得声明 regenerate_segment_ids",
|
|
|
+ )
|
|
|
if planning_package.mode == "REPLAN":
|
|
|
authorized = set(
|
|
|
- planning_package.authorized_regenerate_segment_ids
|
|
|
+ planning_package.authorized_revision_segment_ids
|
|
|
)
|
|
|
for segment_id in set(plan.regenerate_segment_ids) - authorized:
|
|
|
add_production_issue(
|
|
|
@@ -347,37 +692,41 @@ def _protected_segment_payload(
|
|
|
}
|
|
|
|
|
|
|
|
|
-def evaluate_replan_transition(
|
|
|
+def _evaluate_revision_transition(
|
|
|
previous_plan: ProductionPlan,
|
|
|
current_plan: ProductionPlan,
|
|
|
planning_package: ProductionPlanningPackage,
|
|
|
+ *,
|
|
|
+ mode: str,
|
|
|
) -> list[ProductionContractIssue]:
|
|
|
- """验证版本推进、固定 Segment 映射、授权修改和有效返工。"""
|
|
|
+ """验证共同的版本推进、固定映射、授权修改和实质变化。"""
|
|
|
|
|
|
+ label = "Replan" if mode == "REPLAN" else "Adapt"
|
|
|
+ prefix = mode.lower()
|
|
|
issues: list[ProductionContractIssue] = []
|
|
|
- if planning_package.mode != "REPLAN":
|
|
|
+ if planning_package.mode != mode:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_package_mode_mismatch",
|
|
|
- "Replan transition 必须使用 REPLAN PlanningPackage",
|
|
|
+ f"{prefix}_package_mode_mismatch",
|
|
|
+ f"{label} transition 必须使用 {mode} PlanningPackage",
|
|
|
)
|
|
|
if current_plan.plan_id != previous_plan.plan_id:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_plan_id_changed",
|
|
|
- "Replan 不得改变 plan_id",
|
|
|
+ f"{prefix}_plan_id_changed",
|
|
|
+ f"{label} 不得改变 plan_id",
|
|
|
)
|
|
|
if current_plan.plan_version != previous_plan.plan_version + 1:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_version_not_next",
|
|
|
- "Replan 的 plan_version 必须精确加一",
|
|
|
+ f"{prefix}_version_not_next",
|
|
|
+ f"{label} 的 plan_version 必须精确加一",
|
|
|
)
|
|
|
if current_plan.plan_version != planning_package.target_plan_version:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_target_version_mismatch",
|
|
|
- "Replan 结果没有使用 PlanningPackage 的目标版本",
|
|
|
+ f"{prefix}_target_version_mismatch",
|
|
|
+ f"{label} 结果没有使用 PlanningPackage 的目标版本",
|
|
|
)
|
|
|
previous_identity = [
|
|
|
(item.segment_id, item.source_segment_id)
|
|
|
@@ -390,8 +739,8 @@ def evaluate_replan_transition(
|
|
|
if current_identity != previous_identity:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_segment_mapping_changed",
|
|
|
- "Replan 不得拆分、合并、新增或重排制作表 Segment",
|
|
|
+ f"{prefix}_segment_mapping_changed",
|
|
|
+ f"{label} 不得拆分、合并、新增或重排制作表 Segment",
|
|
|
)
|
|
|
return issues
|
|
|
if [item.segment_id for item in current_plan.timeline] != [
|
|
|
@@ -399,17 +748,17 @@ def evaluate_replan_transition(
|
|
|
]:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_timeline_order_changed",
|
|
|
- "Replan 不得改变 Segment 时间线顺序",
|
|
|
+ f"{prefix}_timeline_order_changed",
|
|
|
+ f"{label} 不得改变 Segment 时间线顺序",
|
|
|
)
|
|
|
|
|
|
known_ids = {item.segment_id for item in previous_plan.segments}
|
|
|
- authorized = set(planning_package.authorized_regenerate_segment_ids)
|
|
|
+ authorized = set(planning_package.authorized_revision_segment_ids)
|
|
|
for segment_id in authorized - known_ids:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_authorized_unknown_segment",
|
|
|
- "返工授权包含上一版 Plan 之外的 Segment",
|
|
|
+ f"{prefix}_authorized_unknown_segment",
|
|
|
+ "Plan revision 授权包含上一版 Plan 之外的 Segment",
|
|
|
segment_id=segment_id,
|
|
|
)
|
|
|
regenerated = set(current_plan.regenerate_segment_ids)
|
|
|
@@ -420,6 +769,12 @@ def evaluate_replan_transition(
|
|
|
"Planner 不得重做代码未授权的 Segment",
|
|
|
segment_id=segment_id,
|
|
|
)
|
|
|
+ if mode == "ADAPT" and regenerated:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_plan_cannot_regenerate",
|
|
|
+ "ADAPT 不得要求重新生成已执行 Segment",
|
|
|
+ )
|
|
|
|
|
|
current_by_id = {
|
|
|
item.segment_id: item for item in current_plan.segments
|
|
|
@@ -434,34 +789,69 @@ def evaluate_replan_transition(
|
|
|
if changed and previous.segment_id not in authorized:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "protected_pass_segment_changed",
|
|
|
- "未授权 PASS Segment 的业务执行输入必须保持不变",
|
|
|
+ (
|
|
|
+ "protected_pass_segment_changed"
|
|
|
+ if mode == "REPLAN"
|
|
|
+ else "adapt_protected_segment_changed"
|
|
|
+ ),
|
|
|
+ "未授权 Segment 的业务执行输入必须保持不变",
|
|
|
segment_id=previous.segment_id,
|
|
|
)
|
|
|
if changed and previous.segment_id in authorized:
|
|
|
changed_authorized.add(previous.segment_id)
|
|
|
- if not changed_authorized and not regenerated:
|
|
|
+ has_effect = bool(changed_authorized)
|
|
|
+ if mode == "REPLAN":
|
|
|
+ has_effect = has_effect or bool(regenerated)
|
|
|
+ if not has_effect:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_has_no_effect",
|
|
|
- "Replan 必须修改授权 Segment 或显式要求重新生成",
|
|
|
+ f"{prefix}_has_no_effect",
|
|
|
+ (
|
|
|
+ "Replan 必须修改授权 Segment 或显式要求重新生成"
|
|
|
+ if mode == "REPLAN"
|
|
|
+ else "ADAPT 必须实质修改至少一个授权的未来 Segment"
|
|
|
+ ),
|
|
|
)
|
|
|
return issues
|
|
|
|
|
|
|
|
|
-def evaluate_replan_planning_package(
|
|
|
+def evaluate_replan_transition(
|
|
|
+ previous_plan: ProductionPlan,
|
|
|
+ current_plan: ProductionPlan,
|
|
|
+ planning_package: ProductionPlanningPackage,
|
|
|
+) -> list[ProductionContractIssue]:
|
|
|
+ """验证正式失败后的 Plan revision。"""
|
|
|
+
|
|
|
+ return _evaluate_revision_transition(
|
|
|
+ previous_plan,
|
|
|
+ current_plan,
|
|
|
+ planning_package,
|
|
|
+ mode="REPLAN",
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def evaluate_adapt_transition(
|
|
|
+ previous_plan: ProductionPlan,
|
|
|
+ current_plan: ProductionPlan,
|
|
|
+ planning_package: ProductionPlanningPackage,
|
|
|
+) -> list[ProductionContractIssue]:
|
|
|
+ """验证 PASS 后只影响未来未接纳 Segment 的 Plan revision。"""
|
|
|
+
|
|
|
+ return _evaluate_revision_transition(
|
|
|
+ previous_plan,
|
|
|
+ current_plan,
|
|
|
+ planning_package,
|
|
|
+ mode="ADAPT",
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def evaluate_revision_planning_package(
|
|
|
previous: ProductionPlanningPackage,
|
|
|
current: ProductionPlanningPackage,
|
|
|
) -> list[ProductionContractIssue]:
|
|
|
- """验证 Replan Package 没有替换本 Run 的封印输入。"""
|
|
|
+ """验证 Plan revision Package 没有替换本 Run 的封印输入。"""
|
|
|
|
|
|
issues: list[ProductionContractIssue] = []
|
|
|
- if current.mode != "REPLAN":
|
|
|
- add_production_issue(
|
|
|
- issues,
|
|
|
- "replan_package_mode_mismatch",
|
|
|
- "下一版 ProductionPlanningPackage 必须使用 REPLAN mode",
|
|
|
- )
|
|
|
for field_name in (
|
|
|
"run_id",
|
|
|
"plan_id",
|
|
|
@@ -474,14 +864,137 @@ def evaluate_replan_planning_package(
|
|
|
if getattr(current, field_name) != getattr(previous, field_name):
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_sealed_input_changed",
|
|
|
- f"Replan 不得改变 ProductionPlanningPackage.{field_name}",
|
|
|
+ "revision_sealed_input_changed",
|
|
|
+ (
|
|
|
+ "Plan revision 不得改变 "
|
|
|
+ f"ProductionPlanningPackage.{field_name}"
|
|
|
+ ),
|
|
|
)
|
|
|
if current.target_plan_version != previous.target_plan_version + 1:
|
|
|
add_production_issue(
|
|
|
issues,
|
|
|
- "replan_package_version_not_next",
|
|
|
- "Replan PlanningPackage 的目标版本必须精确加一",
|
|
|
+ "revision_package_version_not_next",
|
|
|
+ "Plan revision PlanningPackage 的目标版本必须精确加一",
|
|
|
+ )
|
|
|
+ return issues
|
|
|
+
|
|
|
+
|
|
|
+def evaluate_replan_planning_package(
|
|
|
+ previous: ProductionPlanningPackage,
|
|
|
+ current: ProductionPlanningPackage,
|
|
|
+ *,
|
|
|
+ progress_decision_ref: ImmutableJsonRef | None = None,
|
|
|
+) -> list[ProductionContractIssue]:
|
|
|
+ """验证 Replan Package 的 mode 与共同封印输入。"""
|
|
|
+
|
|
|
+ issues = [
|
|
|
+ issue.model_copy(
|
|
|
+ update={
|
|
|
+ "code": {
|
|
|
+ "revision_sealed_input_changed": (
|
|
|
+ "replan_sealed_input_changed"
|
|
|
+ ),
|
|
|
+ "revision_package_version_not_next": (
|
|
|
+ "replan_package_version_not_next"
|
|
|
+ ),
|
|
|
+ }.get(issue.code, issue.code)
|
|
|
+ }
|
|
|
+ )
|
|
|
+ for issue in evaluate_revision_planning_package(previous, current)
|
|
|
+ ]
|
|
|
+ if current.mode != "REPLAN":
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "replan_package_mode_mismatch",
|
|
|
+ "下一版 ProductionPlanningPackage 必须使用 REPLAN mode",
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ progress_decision_ref is not None
|
|
|
+ and current.progress_decision_ref != progress_decision_ref
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "replan_progress_decision_ref_mismatch",
|
|
|
+ "Segment REPLAN PlanningPackage 没有绑定当前 ProgressDecision",
|
|
|
+ )
|
|
|
+ return issues
|
|
|
+
|
|
|
+
|
|
|
+def evaluate_adapt_planning_package(
|
|
|
+ previous: ProductionPlanningPackage,
|
|
|
+ current: ProductionPlanningPackage,
|
|
|
+ *,
|
|
|
+ progress_package: ProductionProgressPackage,
|
|
|
+ progress_package_ref: ImmutableJsonRef,
|
|
|
+ progress_decision: ProductionProgressDecision,
|
|
|
+ progress_decision_ref: ImmutableJsonRef,
|
|
|
+) -> list[ProductionContractIssue]:
|
|
|
+ """验证 ADAPT Package 精确继承正式 Review 的快照与授权。"""
|
|
|
+
|
|
|
+ issues = [
|
|
|
+ issue.model_copy(
|
|
|
+ update={
|
|
|
+ "code": {
|
|
|
+ "revision_sealed_input_changed": (
|
|
|
+ "adapt_sealed_input_changed"
|
|
|
+ ),
|
|
|
+ "revision_package_version_not_next": (
|
|
|
+ "adapt_package_version_not_next"
|
|
|
+ ),
|
|
|
+ }.get(issue.code, issue.code)
|
|
|
+ }
|
|
|
+ )
|
|
|
+ for issue in evaluate_revision_planning_package(previous, current)
|
|
|
+ ]
|
|
|
+ if current.mode != "ADAPT":
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_package_mode_mismatch",
|
|
|
+ "下一版 ProductionPlanningPackage 必须使用 ADAPT mode",
|
|
|
+ )
|
|
|
+ if current.progress_decision_ref != progress_decision_ref:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_progress_decision_ref_mismatch",
|
|
|
+ "ADAPT PlanningPackage 没有绑定当前 ProgressDecision",
|
|
|
+ )
|
|
|
+ if progress_decision.action != "ADAPT":
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_progress_decision_action_mismatch",
|
|
|
+ "ADAPT PlanningPackage 只能绑定 ADAPT Decision",
|
|
|
+ )
|
|
|
+ if progress_decision.progress_package_ref != progress_package_ref:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_progress_package_ref_mismatch",
|
|
|
+ "ADAPT Decision 没有绑定当前 ProgressPackage",
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ progress_package.plan_id != previous.plan_id
|
|
|
+ or progress_package.plan_version != previous.target_plan_version
|
|
|
+ or progress_decision.plan_id != previous.plan_id
|
|
|
+ or progress_decision.plan_version != previous.target_plan_version
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_progress_identity_mismatch",
|
|
|
+ "ADAPT review 身份不属于上一版 ProductionPlan",
|
|
|
+ )
|
|
|
+ if current.accepted_segments != progress_package.accepted_segments:
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_accepted_snapshot_mismatch",
|
|
|
+ "ADAPT PlanningPackage 必须精确继承 ProgressPackage Accepted 快照",
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ current.authorized_revision_segment_ids
|
|
|
+ != progress_decision.affected_segment_ids
|
|
|
+ ):
|
|
|
+ add_production_issue(
|
|
|
+ issues,
|
|
|
+ "adapt_authorized_segments_mismatch",
|
|
|
+ "ADAPT PlanningPackage 授权必须精确等于 Decision affected Segment",
|
|
|
)
|
|
|
return issues
|
|
|
|
|
|
@@ -500,6 +1013,20 @@ def validate_replan_transition(
|
|
|
raise ProductionContractError(issues)
|
|
|
|
|
|
|
|
|
+def validate_adapt_transition(
|
|
|
+ previous_plan: ProductionPlan,
|
|
|
+ current_plan: ProductionPlan,
|
|
|
+ planning_package: ProductionPlanningPackage,
|
|
|
+) -> None:
|
|
|
+ issues = evaluate_adapt_transition(
|
|
|
+ previous_plan,
|
|
|
+ current_plan,
|
|
|
+ planning_package,
|
|
|
+ )
|
|
|
+ if issues:
|
|
|
+ raise ProductionContractError(issues)
|
|
|
+
|
|
|
+
|
|
|
def ready_segment_ids(
|
|
|
plan: ProductionPlan,
|
|
|
accepted_segment_ids: Iterable[str],
|