use std::fs; use serde::Deserialize; #[derive(Debug, Deserialize)] struct HandoverAsset { #[serde(rename = "handoverDate")] handover_date: String, #[serde(rename = "closedRoadmapPlan")] closed_roadmap_plan: String, #[serde(rename = "postRoadmapPlan")] post_roadmap_plan: String, #[serde(rename = "roadmapClosureStatus")] roadmap_closure_status: String, #[serde(rename = "scopeStatement")] scope_statement: String, #[serde(rename = "mainlineFamilyStateMatrix")] mainline_family_state_matrix: Vec, #[serde(rename = "boundaryFamilyStateMatrix")] boundary_family_state_matrix: Vec, #[serde(rename = "deferredFamilyStateMatrix")] deferred_family_state_matrix: Vec, notes: Vec, } #[derive(Debug, Deserialize)] struct MainlineFamilyState { group: String, status: String, #[serde(rename = "representativeBaseline")] representative_baseline: String, #[serde(rename = "promotedExpansions")] promoted_expansions: u32, #[serde(rename = "candidateQueueCount")] candidate_queue_count: u32, } #[derive(Debug, Deserialize)] struct GroupState { group: String, status: String, } #[derive(Debug, Deserialize)] struct SceneExecutionBoard { #[serde(rename = "boardDate")] board_date: String, scope: String, #[serde(rename = "sourceAssets")] source_assets: SourceAssets, #[serde(rename = "statusVocabulary")] status_vocabulary: Vec, summary: ExecutionBoardSummary, scenes: Vec, notes: Vec, } #[derive(Debug, Deserialize)] struct SourceAssets { workbook: String, #[serde(rename = "ledgerSnapshot")] ledger_snapshot: String, #[serde(rename = "ledgerStatusOverlay")] ledger_status_overlay: String, #[serde(rename = "roadmapExecutionStatus")] roadmap_execution_status: String, } #[derive(Debug, Deserialize)] struct ExecutionBoardSummary { #[serde(rename = "totalScenes")] total_scenes: u32, #[serde(rename = "statusCounts")] status_counts: serde_json::Map, #[serde(rename = "selectedRealSamples")] selected_real_samples: u32, #[serde(rename = "executedRealSamples")] executed_real_samples: u32, #[serde(rename = "pendingRealSamples")] pending_real_samples: u32, } #[derive(Debug, Deserialize)] struct SceneBoardEntry { #[serde(rename = "sceneName")] scene_name: String, #[serde(rename = "snapshotGroupingResult")] snapshot_grouping_result: String, #[serde(rename = "snapshotFamilyJudgement")] snapshot_family_judgement: String, #[serde(rename = "hasExplicitValidationConclusion")] has_explicit_validation_conclusion: String, #[serde(rename = "snapshotValidationStatus")] snapshot_validation_status: String, #[serde(rename = "snapshotValidationResult")] snapshot_validation_result: String, #[serde(rename = "snapshotNote")] snapshot_note: String, #[serde(rename = "currentGroup")] current_group: Option, #[serde(rename = "currentStatus")] current_status: String, #[serde(rename = "currentSourceAsset")] current_source_asset: Option, #[serde(rename = "realSampleRecordId")] real_sample_record_id: Option, #[serde(rename = "realSampleLayerStatus")] real_sample_layer_status: String, } #[derive(Debug, Deserialize)] struct RealSampleValidationPlan { #[serde(rename = "planDate")] plan_date: String, scope: String, criteria: Vec, #[serde(rename = "selectedFamilies")] selected_families: Vec, notes: Vec, } #[derive(Debug, Deserialize)] struct SelectedFamily { group: String, #[serde(rename = "recordId")] record_id: String, #[serde(rename = "sceneName")] scene_name: String, #[serde(rename = "currentBoardStatus")] current_board_status: String, #[serde(rename = "sourceEvidence")] source_evidence: String, #[serde(rename = "selectionReason")] selection_reason: String, } #[derive(Debug, Deserialize)] struct RealSampleValidationTemplate { #[serde(rename = "templateDate")] template_date: String, scope: String, #[serde(rename = "requiredFields")] required_fields: Vec, #[serde(rename = "allowedValidationStates")] allowed_validation_states: Vec, notes: Vec, } #[derive(Debug, Deserialize)] struct RealSampleValidationRecords { #[serde(rename = "recordDate")] record_date: String, scope: String, records: Vec, notes: Vec, } #[derive(Debug, Deserialize)] struct RealSampleValidationRecord { #[serde(rename = "recordId")] record_id: String, group: String, #[serde(rename = "sceneName")] scene_name: String, #[serde(rename = "currentBoardStatus")] current_board_status: String, #[serde(rename = "validationState")] validation_state: String, #[serde(rename = "compileSuccess")] compile_success: Option, #[serde(rename = "readinessCorrectness")] readiness_correctness: Option, #[serde(rename = "dataCorrectness")] data_correctness: Option, #[serde(rename = "outputCorrectness")] output_correctness: Option, #[serde(rename = "failClosedCorrectness")] fail_closed_correctness: Option, result: String, #[serde(rename = "mismatchCodes")] mismatch_codes: Vec, #[serde(rename = "sourceEvidence")] source_evidence: Vec, notes: Vec, } #[derive(Debug, Deserialize)] struct MismatchTaxonomy { #[serde(rename = "taxonomyDate")] taxonomy_date: String, scope: String, codes: Vec, } #[derive(Debug, Deserialize)] struct MismatchCode { code: String, category: String, description: String, } #[derive(Debug, Deserialize)] struct BoundaryRuntimeEntryRules { #[serde(rename = "assetDate")] asset_date: String, scope: String, #[serde(rename = "boundaryReadiness")] boundary_readiness: Vec, #[serde(rename = "deferredFamilyEntryCriteria")] deferred_family_entry_criteria: Vec, #[serde(rename = "runtimeGapMatrix")] runtime_gap_matrix: Vec, prioritization: Vec, } #[derive(Debug, Deserialize)] struct BoundaryReadiness { group: String, status: String, readiness: String, #[serde(rename = "nextEntryCondition")] next_entry_condition: String, } #[derive(Debug, Deserialize)] struct DeferredFamilyEntryCriteria { group: String, #[serde(rename = "currentStatus")] current_status: String, #[serde(rename = "entryCriteria")] entry_criteria: Vec, } #[derive(Debug, Deserialize)] struct RuntimeGap { gap: String, category: String, status: String, blocks: Vec, } #[test] fn post_roadmap_handover_asset_is_actionable() { let handover = load_handover(); assert_eq!(handover.handover_date, "2026-04-18"); assert!(handover.closed_roadmap_plan.ends_with(".md")); assert!(handover.post_roadmap_plan.ends_with(".md")); assert_eq!(handover.roadmap_closure_status, "completed"); assert!(handover.scope_statement.contains("roadmap is closed")); assert_eq!(handover.mainline_family_state_matrix.len(), 3); assert_eq!(handover.boundary_family_state_matrix.len(), 3); assert_eq!(handover.deferred_family_state_matrix.len(), 2); assert!(!handover.notes.is_empty()); } #[test] fn post_roadmap_execution_board_stays_minimal_and_validation_oriented() { let board = load_execution_board(); assert_eq!(board.board_date, "2026-04-19"); assert_eq!(board.scope, "post-roadmap-minimum-current-execution-board"); assert!(board.source_assets.workbook.ends_with(".xlsx")); assert!(board.source_assets.ledger_snapshot.ends_with(".json")); assert!(board.source_assets.ledger_status_overlay.ends_with(".json")); assert!(board .source_assets .roadmap_execution_status .ends_with(".json")); assert_eq!(board.status_vocabulary.len(), 6); assert_eq!(board.summary.total_scenes, 102); assert_eq!(board.summary.selected_real_samples, 5); assert_eq!(board.summary.executed_real_samples, 5); assert_eq!(board.summary.pending_real_samples, 0); assert_eq!( board .summary .status_counts .get("promoted-baseline") .and_then(|value| value.as_u64()), Some(3) ); assert_eq!( board .summary .status_counts .get("boundary-family") .and_then(|value| value.as_u64()), Some(3) ); assert_eq!( board .summary .status_counts .get("unvalidated") .and_then(|value| value.as_u64()), Some(79) ); assert!(!board.notes.is_empty()); } #[test] fn post_roadmap_execution_board_maps_real_sample_records_immediately() { let board = load_execution_board(); let g2 = board .scenes .iter() .find(|item| item.scene_name == "台区线损大数据-月_周累计线损率统计分析") .expect("expected G2 board entry"); assert_eq!(g2.current_group.as_deref(), Some("G2")); assert_eq!(g2.current_status, "promoted-baseline"); assert_eq!(g2.real_sample_record_id.as_deref(), Some("rsv-g2-001")); assert_eq!(g2.real_sample_layer_status, "executed-pass"); let g1e = board .scenes .iter() .find(|item| item.scene_name == "高低压新增报装容量月度统计表") .expect("expected G1-E board entry"); assert_eq!(g1e.current_group.as_deref(), Some("G1-E")); assert_eq!(g1e.current_status, "promoted-baseline"); assert_eq!(g1e.real_sample_record_id.as_deref(), Some("rsv-g1e-001")); assert_eq!(g1e.real_sample_layer_status, "executed-pass"); let g3 = board .scenes .iter() .find(|item| item.scene_name == "95598工单明细表") .expect("expected G3 board entry"); assert_eq!(g3.current_group.as_deref(), Some("G3")); assert_eq!(g3.current_status, "promoted-baseline"); assert_eq!(g3.real_sample_record_id.as_deref(), Some("rsv-g3-001")); assert_eq!(g3.real_sample_layer_status, "executed-pass"); let g7 = board .scenes .iter() .find(|item| item.current_group.as_deref() == Some("G7")) .expect("expected G7 board entry"); assert_eq!(g7.current_group.as_deref(), Some("G7")); assert_eq!(g7.current_status, "boundary-family"); assert_eq!(g7.real_sample_record_id.as_deref(), Some("rsv-g7-001")); assert_eq!(g7.real_sample_layer_status, "executed-pass"); let g6 = board .scenes .iter() .find(|item| item.current_group.as_deref() == Some("G6")) .expect("expected G6 board entry"); assert_eq!(g6.current_group.as_deref(), Some("G6")); assert_eq!(g6.current_status, "boundary-family"); assert_eq!(g6.real_sample_record_id.as_deref(), Some("rsv-g6-001")); assert_eq!(g6.real_sample_layer_status, "executed-pass"); } #[test] fn post_roadmap_real_sample_validation_assets_are_consistent() { let plan = load_validation_plan(); let template = load_validation_template(); let records = load_validation_records(); let taxonomy = load_mismatch_taxonomy(); assert_eq!(plan.plan_date, "2026-04-18"); assert_eq!( plan.scope, "post-roadmap-first-round-real-sample-validation" ); assert_eq!(plan.criteria.len(), 5); assert_eq!(plan.selected_families.len(), 3); assert!(plan.selected_families.iter().any(|item| item.group == "G2")); assert!(plan .selected_families .iter() .any(|item| item.group == "G1-E")); assert!(plan.selected_families.iter().any(|item| item.group == "G3")); assert!(!plan.notes.is_empty()); assert_eq!(template.template_date, "2026-04-18"); assert_eq!(template.scope, "real-sample-validation-record-template"); assert!(template .required_fields .iter() .any(|item| item == "recordId")); assert!(template .allowed_validation_states .iter() .any(|item| item == "selected-not-yet-run")); assert!(!template.notes.is_empty()); assert_eq!(records.record_date, "2026-04-19"); assert_eq!( records.scope, "post-roadmap-first-round-real-sample-records" ); assert_eq!(records.records.len(), 5); assert!(!records.notes.is_empty()); assert_eq!(taxonomy.taxonomy_date, "2026-04-18"); assert_eq!(taxonomy.scope, "post-roadmap-real-sample-mismatch-taxonomy"); assert!(taxonomy .codes .iter() .any(|item| item.code == "archetype_mismatch")); assert!(taxonomy .codes .iter() .any(|item| item.code == "selected_not_run")); let g2 = records .records .iter() .find(|item| item.record_id == "rsv-g2-001") .expect("expected G2 record"); assert_eq!(g2.group, "G2"); assert_eq!(g2.validation_state, "executed-pass"); assert_eq!(g2.compile_success, Some(true)); assert_eq!(g2.result, "passed"); assert_eq!(g2.readiness_correctness, Some(true)); assert_eq!(g2.data_correctness, Some(true)); assert_eq!(g2.output_correctness, Some(true)); assert_eq!(g2.fail_closed_correctness, Some(true)); assert!(g2.mismatch_codes.is_empty()); let g1e = records .records .iter() .find(|item| item.record_id == "rsv-g1e-001") .expect("expected G1-E record"); assert_eq!(g1e.group, "G1-E"); assert_eq!(g1e.validation_state, "executed-pass"); assert_eq!(g1e.compile_success, Some(true)); assert_eq!(g1e.result, "passed"); assert!(g1e.mismatch_codes.is_empty()); let g3 = records .records .iter() .find(|item| item.record_id == "rsv-g3-001") .expect("expected G3 record"); assert_eq!(g3.group, "G3"); assert_eq!(g3.validation_state, "executed-pass"); assert_eq!(g3.compile_success, Some(true)); assert_eq!(g3.result, "passed"); assert_eq!(g3.data_correctness, Some(true)); assert_eq!(g3.output_correctness, Some(true)); assert!(g3.mismatch_codes.is_empty()); let g7 = records .records .iter() .find(|item| item.record_id == "rsv-g7-001") .expect("expected G7 record"); assert_eq!(g7.group, "G7"); assert_eq!(g7.validation_state, "executed-pass"); assert_eq!(g7.compile_success, Some(true)); assert_eq!(g7.result, "passed"); assert_eq!(g7.data_correctness, Some(true)); assert_eq!(g7.output_correctness, Some(true)); assert!(g7.mismatch_codes.is_empty()); let g6 = records .records .iter() .find(|item| item.record_id == "rsv-g6-001") .expect("expected G6 record"); assert_eq!(g6.group, "G6"); assert_eq!(g6.validation_state, "executed-pass"); assert_eq!(g6.compile_success, Some(true)); assert_eq!(g6.result, "passed"); assert_eq!(g6.data_correctness, Some(true)); assert_eq!(g6.output_correctness, Some(true)); assert!(g6.mismatch_codes.is_empty()); } #[test] fn post_roadmap_boundary_and_runtime_entry_rules_keep_scope_bounded() { let rules = load_boundary_runtime_rules(); assert_eq!(rules.asset_date, "2026-04-19"); assert_eq!(rules.scope, "post-roadmap-boundary-and-runtime-entry-rules"); assert_eq!(rules.boundary_readiness.len(), 3); assert!(rules.boundary_readiness.iter().all(|item| { matches!(item.group.as_str(), "G6" | "G7" | "G8") && item.status == "boundary-family-established" })); assert!(rules .boundary_readiness .iter() .any(|item| item.group == "G6" && item.readiness == "executed-pass")); assert!(rules .boundary_readiness .iter() .any(|item| item.group == "G8" && item.readiness == "hold-as-boundary")); assert!(rules .boundary_readiness .iter() .any(|item| item.group == "G7" && item.readiness == "executed-pass")); assert_eq!(rules.deferred_family_entry_criteria.len(), 2); assert!(rules .deferred_family_entry_criteria .iter() .any(|item| item.group == "G4" && item.current_status == "deferred")); assert!(rules .deferred_family_entry_criteria .iter() .any(|item| item.group == "G5" && item.current_status == "degraded")); assert_eq!(rules.runtime_gap_matrix.len(), 6); assert!(rules .runtime_gap_matrix .iter() .any(|item| item.gap == "login-recovery" && item.category == "runtime-platform-gap")); assert!(rules .runtime_gap_matrix .iter() .any(|item| item.gap == "host-runtime-integration")); assert!(rules.runtime_gap_matrix.iter().any(|item| item.gap == "g3-real-sample-output-contract-verification" && item.category == "mainline-contract-gap" && item.status == "closed-in-mainline")); assert!(rules .runtime_gap_matrix .iter() .any(|item| item.gap == "g2-real-sample-contract-correction" && item.category == "mainline-contract-gap" && item.status == "closed-in-mainline")); assert!(!rules.prioritization.is_empty()); assert!(rules .prioritization .iter() .any(|item| item.contains("Both G3 and G2"))); assert!(rules .prioritization .iter() .any(|item| item.contains("G7 is now the first boundary family"))); assert!(rules .prioritization .iter() .any(|item| item.contains("G6 is now the second boundary family"))); } fn load_handover() -> HandoverAsset { serde_json::from_str( &fs::read_to_string("tests/fixtures/generated_scene/post_roadmap_handover_2026-04-18.json") .unwrap(), ) .unwrap() } fn load_execution_board() -> SceneExecutionBoard { serde_json::from_str( &fs::read_to_string("tests/fixtures/generated_scene/scene_execution_board_2026-04-18.json") .unwrap(), ) .unwrap() } fn load_validation_plan() -> RealSampleValidationPlan { serde_json::from_str( &fs::read_to_string( "tests/fixtures/generated_scene/real_sample_validation_plan_2026-04-18.json", ) .unwrap(), ) .unwrap() } fn load_validation_template() -> RealSampleValidationTemplate { serde_json::from_str( &fs::read_to_string( "tests/fixtures/generated_scene/real_sample_validation_record_template_2026-04-18.json", ) .unwrap(), ) .unwrap() } fn load_validation_records() -> RealSampleValidationRecords { serde_json::from_str( &fs::read_to_string( "tests/fixtures/generated_scene/real_sample_validation_records_2026-04-18.json", ) .unwrap(), ) .unwrap() } fn load_mismatch_taxonomy() -> MismatchTaxonomy { serde_json::from_str( &fs::read_to_string( "tests/fixtures/generated_scene/real_sample_mismatch_taxonomy_2026-04-18.json", ) .unwrap(), ) .unwrap() } fn load_boundary_runtime_rules() -> BoundaryRuntimeEntryRules { serde_json::from_str( &fs::read_to_string( "tests/fixtures/generated_scene/boundary_runtime_entry_rules_2026-04-18.json", ) .unwrap(), ) .unwrap() }