use std::fs; use serde::Deserialize; #[derive(Debug, Deserialize)] struct G3CandidateBatch { #[serde(rename = "batchId")] batch_id: String, family: String, source: String, #[serde(rename = "ledgerClusterLabel")] ledger_cluster_label: String, #[serde(rename = "selectionRule")] selection_rule: String, #[serde(rename = "candidateCount")] candidate_count: u32, #[serde(rename = "representativeBaseline")] representative_baseline: String, #[serde(rename = "firstExpansionBaseline")] first_expansion_baseline: String, #[serde(rename = "promotedBatchExpansionBaselines")] promoted_batch_expansion_baselines: Vec, #[serde(rename = "expectedSharedContract")] expected_shared_contract: SharedContract, candidates: Vec, notes: Vec, } #[derive(Debug, Deserialize)] struct SharedContract { archetype: String, #[serde(rename = "requiredPaginationFields")] required_pagination_fields: Vec, #[serde(rename = "requiredJoinKeyPatterns")] required_join_key_patterns: Vec, #[serde(rename = "requiredAggregateRulePatterns")] required_aggregate_rule_patterns: Vec, } #[derive(Debug, Deserialize)] struct PromotedExpansionBaseline { #[serde(rename = "fixtureDir")] fixture_dir: String, #[serde(rename = "sceneId")] scene_id: String, #[serde(rename = "sceneName")] scene_name: String, assertions: ExpansionAssertions, } #[derive(Debug, Deserialize)] struct ExpansionAssertions { #[serde(rename = "expectedPaginationField")] expected_pagination_field: String, #[serde(rename = "requiredJoinKey")] required_join_key: String, #[serde(rename = "requiredAggregateRule")] required_aggregate_rule: String, } #[derive(Debug, Deserialize)] struct BatchCandidate { #[serde(rename = "sceneKey")] scene_key: String, #[serde(rename = "ledgerGroupingResult")] ledger_grouping_result: String, #[serde(rename = "ledgerFamilyJudgement")] ledger_family_judgement: String, #[serde(rename = "batchRole")] batch_role: String, } #[test] fn g3_candidate_batch_asset_is_actionable() { let batch = load_batch(); assert_eq!( batch.batch_id, "g3-95598-ticket-family-candidates-2026-04-18" ); assert_eq!(batch.family, "G3"); assert!(batch.source.ends_with(".json")); assert_eq!(batch.ledger_cluster_label, "95598-ticket-family-candidate"); assert!(batch .selection_rule .contains("95598-ticket-family-candidate")); assert_eq!(batch.candidate_count, 11); assert_eq!( batch.expected_shared_contract.archetype, "paginated_enrichment" ); assert!(batch .expected_shared_contract .required_pagination_fields .iter() .any(|item| item == "pageNum")); assert!(batch .expected_shared_contract .required_pagination_fields .iter() .any(|item| item == "pageNo")); assert!(batch .expected_shared_contract .required_join_key_patterns .iter() .any(|item| item == "ticketNo")); assert!(batch .expected_shared_contract .required_aggregate_rule_patterns .iter() .any(|item| item == "aggregate:riskLevel")); assert_eq!(batch.candidates.len() as u32, batch.candidate_count); assert_eq!(batch.promoted_batch_expansion_baselines.len(), 10); assert!(!batch.notes.is_empty()); } #[test] fn g3_candidate_batch_promotes_second_round_expansion_baselines() { let batch = load_batch(); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_workorder") && item.scene_id == "p1-g3-paginated-expansion-workorder-report" && item.scene_name == "P1 G3 paginated expansion workorder report" && item.assertions.expected_pagination_field == "pageNo" && item.assertions.required_join_key == "workOrderNo" && item.assertions.required_aggregate_rule == "aggregate:sourceType")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_orderno") && item.scene_id == "p1-g3-paginated-expansion-orderno-report" && item.scene_name == "P1 G3 paginated expansion orderno report" && item.assertions.expected_pagination_field == "page" && item.assertions.required_join_key == "orderNo" && item.assertions.required_aggregate_rule == "aggregate:sourceType")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_source_distribution") && item.scene_id == "p1-g3-paginated-expansion-source-distribution-report" && item.scene_name == "P1 G3 paginated expansion source distribution report" && item.assertions.expected_pagination_field == "pageNum" && item.assertions.required_join_key == "ticketNo" && item.assertions.required_aggregate_rule == "aggregate:sourceType")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_service_risk") && item.scene_id == "p1-g3-paginated-expansion-service-risk-report" && item.scene_name == "P1 G3 paginated expansion service risk report" && item.assertions.expected_pagination_field == "pageNo" && item.assertions.required_join_key == "ticketNo" && item.assertions.required_aggregate_rule == "aggregate:riskLevel")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_timeout_warning") && item.scene_id == "p1-g3-paginated-expansion-timeout-warning-report" && item.scene_name == "P1 G3 paginated expansion timeout warning report" && item.assertions.expected_pagination_field == "pageNum" && item.assertions.required_join_key == "ticketNo" && item.assertions.required_aggregate_rule == "aggregate:riskLevel")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_device_monitor_weekly") && item.scene_id == "p1-g3-paginated-expansion-device-monitor-weekly-report" && item.assertions.expected_pagination_field == "pageNo" && item.assertions.required_join_key == "ticketNo" && item.assertions.required_aggregate_rule == "aggregate:sourceType")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_customer_satisfaction") && item.scene_id == "p1-g3-paginated-expansion-customer-satisfaction-report" && item.assertions.expected_pagination_field == "page" && item.assertions.required_join_key == "ticketNo" && item.assertions.required_aggregate_rule == "aggregate:sourceType")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_repair_return") && item.scene_id == "p1-g3-paginated-expansion-repair-return-report" && item.assertions.expected_pagination_field == "pageNum" && item.assertions.required_join_key == "ticketNo" && item.assertions.required_aggregate_rule == "aggregate:riskLevel")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_repair_daily_control") && item.scene_id == "p1-g3-paginated-expansion-repair-daily-control-report" && item.assertions.expected_pagination_field == "pageNo" && item.assertions.required_join_key == "ticketNo" && item.assertions.required_aggregate_rule == "aggregate:riskLevel")); assert!(batch .promoted_batch_expansion_baselines .iter() .any(|item| item .fixture_dir .ends_with("paginated_enrichment_expansion_business_stats") && item.scene_id == "p1-g3-paginated-expansion-business-stats-report" && item.assertions.expected_pagination_field == "page" && item.assertions.required_join_key == "ticketNo" && item.assertions.required_aggregate_rule == "aggregate:sourceType")); } #[test] fn g3_candidate_batch_keeps_anchor_and_promoted_candidates_visible() { let batch = load_batch(); assert!(batch .candidates .iter() .any(|item| item.scene_key == "95598_ticket_detail" && item.batch_role == "p0-anchor")); assert!(batch.candidates.iter().any(|item| { item.scene_key == "95598_ticket_12398_process_timeout_detail" && item.batch_role == "first-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "ticket_source_distribution_analysis" && item.batch_role == "fourth-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "process_timeout_risk_ticket_detail" && item.batch_role == "fifth-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "ticket_timeout_warning_detail" && item.batch_role == "sixth-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "95598_ticket_12398_device_monitor_weekly" && item.batch_role == "seventh-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "95598_ticket_customer_satisfaction_daily" && item.batch_role == "eighth-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "95598_ticket_repair_return_analysis" && item.batch_role == "ninth-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "95598_ticket_repair_daily_control" && item.batch_role == "tenth-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "power_supply_service_ticket_business_stats" && item.batch_role == "eleventh-expansion-anchor" })); assert!(batch.candidates.iter().any(|item| { item.scene_key == "service_risk_ticket_detail" && item.batch_role == "third-expansion-anchor" })); assert!(batch.candidates.iter().all(|item| { item.ledger_grouping_result == "95598-ticket-family-candidate" && item.ledger_family_judgement == "pending-regroup" })); assert!(batch .representative_baseline .ends_with("paginated_enrichment")); assert!(batch .first_expansion_baseline .ends_with("paginated_enrichment_expansion")); } fn load_batch() -> G3CandidateBatch { serde_json::from_str( &fs::read_to_string("tests/fixtures/generated_scene/g3_candidate_batch_2026-04-18.json") .unwrap(), ) .unwrap() }