Skip to main content

rh_codegen/generators/
naming_manager.rs

1//! Centralized naming management for FHIR types and modules
2//!
3//! This module provides centralized naming conventions and type mapping
4//! to ensure consistent naming across the codebase.
5
6use once_cell::sync::Lazy;
7use std::collections::HashMap;
8
9/// Centralized mapping of FHIR resource names and their properties
10#[derive(Debug, Clone)]
11pub struct FhirResourceInfo {
12    /// The canonical FHIR resource name (e.g., "EvidenceVariable")
13    pub canonical_name: String,
14    /// The module name used in imports (e.g., "evidence_variable")
15    pub module_name: String,
16    /// Whether this is a core FHIR resource type
17    pub is_core_resource: bool,
18}
19
20impl FhirResourceInfo {
21    fn new(canonical_name: &str) -> Self {
22        Self {
23            canonical_name: canonical_name.to_string(),
24            module_name: crate::naming::Naming::to_snake_case(canonical_name),
25            is_core_resource: true,
26        }
27    }
28}
29
30/// Static mapping of all known FHIR resources
31static FHIR_RESOURCE_MAP: Lazy<HashMap<String, FhirResourceInfo>> = Lazy::new(|| {
32    let resource_names = [
33        "Account",
34        "ActivityDefinition",
35        "AdverseEvent",
36        "AllergyIntolerance",
37        "Appointment",
38        "AppointmentResponse",
39        "AuditEvent",
40        "Basic",
41        "Binary",
42        "BiologicallyDerivedProduct",
43        "BodyStructure",
44        "Bundle",
45        "CapabilityStatement",
46        "CarePlan",
47        "CareTeam",
48        "CatalogEntry",
49        "ChargeItem",
50        "ChargeItemDefinition",
51        "Claim",
52        "ClaimResponse",
53        "ClinicalImpression",
54        "CodeSystem",
55        "Communication",
56        "CommunicationRequest",
57        "CompartmentDefinition",
58        "Composition",
59        "ConceptMap",
60        "Condition",
61        "Consent",
62        "Contract",
63        "Coverage",
64        "CoverageEligibilityRequest",
65        "CoverageEligibilityResponse",
66        "DetectedIssue",
67        "Device",
68        "DeviceDefinition",
69        "DeviceMetric",
70        "DeviceRequest",
71        "DeviceUseStatement",
72        "DiagnosticReport",
73        "DocumentManifest",
74        "DocumentReference",
75        "EffectEvidenceSynthesis",
76        "Encounter",
77        "Endpoint",
78        "EnrollmentRequest",
79        "EnrollmentResponse",
80        "EpisodeOfCare",
81        "EventDefinition",
82        "Evidence",
83        "EvidenceVariable",
84        "ExampleScenario",
85        "ExplanationOfBenefit",
86        "FamilyMemberHistory",
87        "Flag",
88        "Goal",
89        "GraphDefinition",
90        "Group",
91        "GuidanceResponse",
92        "HealthcareService",
93        "ImagingStudy",
94        "Immunization",
95        "ImmunizationEvaluation",
96        "ImmunizationRecommendation",
97        "ImplementationGuide",
98        "InsurancePlan",
99        "Invoice",
100        "Library",
101        "Linkage",
102        "List",
103        "Location",
104        "Measure",
105        "MeasureReport",
106        "Media",
107        "Medication",
108        "MedicationAdministration",
109        "MedicationDispense",
110        "MedicationKnowledge",
111        "MedicationRequest",
112        "MedicationStatement",
113        "MedicinalProduct",
114        "MedicinalProductAuthorization",
115        "MedicinalProductContraindication",
116        "MedicinalProductIndication",
117        "MedicinalProductIngredient",
118        "MedicinalProductInteraction",
119        "MedicinalProductManufactured",
120        "MedicinalProductPackaged",
121        "MedicinalProductPharmaceutical",
122        "MedicinalProductUndesirableEffect",
123        "MessageDefinition",
124        "MessageHeader",
125        "MolecularSequence",
126        "NamingSystem",
127        "NutritionOrder",
128        "Observation",
129        "ObservationDefinition",
130        "OperationDefinition",
131        "OperationOutcome",
132        "Organization",
133        "OrganizationAffiliation",
134        "Patient",
135        "Parameters",
136        "PaymentNotice",
137        "PaymentReconciliation",
138        "Person",
139        "PlanDefinition",
140        "Practitioner",
141        "PractitionerRole",
142        "Procedure",
143        "Provenance",
144        "Questionnaire",
145        "QuestionnaireResponse",
146        "RelatedPerson",
147        "RequestGroup",
148        "ResearchDefinition",
149        "ResearchElementDefinition",
150        "ResearchStudy",
151        "ResearchSubject",
152        "RiskAssessment",
153        "RiskEvidenceSynthesis",
154        "Schedule",
155        "SearchParameter",
156        "ServiceRequest",
157        "Slot",
158        "Specimen",
159        "SpecimenDefinition",
160        "StructureDefinition",
161        "StructureMap",
162        "Subscription",
163        "Substance",
164        "SubstanceNucleicAcid",
165        "SubstancePolymer",
166        "SubstanceProtein",
167        "SubstanceReferenceInformation",
168        "SubstanceSourceMaterial",
169        "SubstanceSpecification",
170        "SupplyDelivery",
171        "SupplyRequest",
172        "Task",
173        "TerminologyCapabilities",
174        "TestReport",
175        "TestScript",
176        "ValueSet",
177        "VerificationResult",
178        "VisionPrescription",
179        // Base types
180        "DomainResource",
181        "Resource",
182        "MetadataResource",
183    ];
184
185    let mut map = HashMap::new();
186    for name in &resource_names {
187        map.insert(name.to_string(), FhirResourceInfo::new(name));
188    }
189    map
190});
191
192/// Static mapping of known FHIR datatypes
193static FHIR_DATATYPE_MAP: Lazy<HashMap<String, FhirResourceInfo>> = Lazy::new(|| {
194    let datatype_names = [
195        "Identifier",
196        "HumanName",
197        "Address",
198        "ContactPoint",
199        "Attachment",
200        "CodeableConcept",
201        "Coding",
202        "Quantity",
203        "Range",
204        "Period",
205        "Ratio",
206        "SampledData",
207        "Signature",
208        "Age",
209        "Count",
210        "Distance",
211        "Duration",
212        "Money",
213        "SimpleQuantity",
214        "Extension",
215        "Narrative",
216        "Annotation",
217        "Reference",
218        "Meta",
219        "ContactDetail",
220        "Contributor",
221        "DataRequirement",
222        "Expression",
223        "ParameterDefinition",
224        "RelatedArtifact",
225        "TriggerDefinition",
226        "UsageContext",
227        "Dosage",
228        "Population",
229        "ProductShelfLife",
230        "ProdCharacteristic",
231        "MarketingStatus",
232        "SubstanceAmount",
233        "Timing",
234        "ElementDefinition",
235        "Element",
236        "BackboneElement",
237    ];
238
239    let mut map = HashMap::new();
240    for name in &datatype_names {
241        let info = FhirResourceInfo {
242            canonical_name: name.to_string(),
243            module_name: crate::naming::Naming::to_snake_case(name),
244            is_core_resource: false,
245        };
246        map.insert(name.to_string(), info);
247    }
248    map
249});
250
251/// Centralized naming manager for FHIR types
252pub struct NamingManager;
253
254impl NamingManager {
255    /// Get information about a FHIR resource
256    pub fn get_resource_info(resource_name: &str) -> Option<&FhirResourceInfo> {
257        FHIR_RESOURCE_MAP.get(resource_name)
258    }
259
260    /// Get information about a FHIR datatype
261    pub fn get_datatype_info(datatype_name: &str) -> Option<&FhirResourceInfo> {
262        FHIR_DATATYPE_MAP.get(datatype_name)
263    }
264
265    /// Check if a type name represents a FHIR resource
266    pub fn is_fhir_resource(type_name: &str) -> bool {
267        FHIR_RESOURCE_MAP.contains_key(type_name)
268    }
269
270    /// Check if a type name represents a FHIR datatype
271    pub fn is_fhir_datatype(type_name: &str) -> bool {
272        FHIR_DATATYPE_MAP.contains_key(type_name)
273    }
274
275    /// Detect if a type name represents a nested structure and return the parent resource name
276    /// Uses longest-prefix matching to ensure correct parent identification
277    ///
278    /// Examples:
279    /// - "EvidenceVariableCharacteristic" -> Some("EvidenceVariable") (not "Evidence")
280    /// - "MeasureReportGroup" -> Some("MeasureReport") (not "Measure")
281    /// - "AccountCoverage" -> Some("Account")
282    pub fn detect_nested_structure_parent(type_name: &str) -> Option<String> {
283        let mut longest_match = None;
284        let mut longest_length = 0;
285
286        // Find the longest matching resource name prefix
287        for resource_name in FHIR_RESOURCE_MAP.keys() {
288            if type_name.starts_with(resource_name)
289                && type_name.len() > resource_name.len()
290                && resource_name.len() > longest_length
291            {
292                // Check that the character after the resource name is uppercase (indicating PascalCase)
293                if let Some(next_char) = type_name.chars().nth(resource_name.len()) {
294                    if next_char.is_uppercase() {
295                        longest_match = Some(resource_name.clone());
296                        longest_length = resource_name.len();
297                    }
298                }
299            }
300        }
301
302        longest_match
303    }
304
305    /// Get the correct import path for a given type name
306    pub fn get_import_path_for_type(type_name: &str) -> String {
307        // Check if it's a generated trait FIRST (highest priority)
308        if Self::is_generated_trait(type_name) {
309            return Self::get_trait_import_path(type_name);
310        }
311
312        // Check if it's likely a binding enum before checking nested structures
313        if Self::is_likely_binding_enum(type_name) {
314            return format!(
315                "crate::bindings::{}::{}",
316                crate::naming::Naming::to_snake_case(type_name),
317                type_name
318            );
319        }
320
321        // Check if it's a nested structure (most specific for non-traits)
322        if let Some(parent_resource) = Self::detect_nested_structure_parent(type_name) {
323            if let Some(resource_info) = Self::get_resource_info(&parent_resource) {
324                return format!(
325                    "crate::resources::{}::{}",
326                    resource_info.module_name, type_name
327                );
328            }
329        }
330
331        // Check if it's a known FHIR resource type
332        if let Some(resource_info) = Self::get_resource_info(type_name) {
333            return format!(
334                "crate::resources::{}::{}",
335                resource_info.module_name, type_name
336            );
337        }
338
339        // Check if it's a known FHIR datatype
340        if let Some(datatype_info) = Self::get_datatype_info(type_name) {
341            return format!(
342                "crate::datatypes::{}::{}",
343                datatype_info.module_name, type_name
344            );
345        }
346
347        // Check if it's a primitive type with special module mapping
348        if let Some(primitive_path) = Self::get_primitive_import_path(type_name) {
349            return primitive_path;
350        }
351
352        // Default to bindings for unknown types (likely enums)
353        format!(
354            "crate::bindings::{}::{}",
355            crate::naming::Naming::to_snake_case(type_name),
356            type_name
357        )
358    }
359
360    /// Get import path for primitive types
361    fn get_primitive_import_path(type_name: &str) -> Option<String> {
362        let module_name = match type_name {
363            "StringType" | "String" => "string",
364            "BooleanType" | "Boolean" => "boolean",
365            "IntegerType" | "Integer" => "integer",
366            "DecimalType" | "Decimal" => "decimal",
367            "UriType" | "Uri" => "uri",
368            "UrlType" | "Url" => "url",
369            "CanonicalType" | "Canonical" => "canonical",
370            "OidType" | "Oid" => "oid",
371            "UuidType" | "Uuid" => "uuid",
372            "InstantType" | "Instant" => "instant",
373            "DateType" | "Date" => "date",
374            "DateTimeType" | "DateTime" => "date_time",
375            "TimeType" | "Time" => "time",
376            "CodeType" | "Code" => "code",
377            "IdType" | "Id" => "id",
378            "MarkdownType" | "Markdown" => "markdown",
379            "Base64BinaryType" | "Base64Binary" => "base64binary",
380            "UnsignedIntType" | "UnsignedInt" => "unsigned_int",
381            "PositiveIntType" | "PositiveInt" => "positive_int",
382            "XhtmlType" | "Xhtml" => "xhtml",
383            _ => return None,
384        };
385
386        // Map the type name to the correct Type variant for import
387        let type_variant = match type_name {
388            "String" => "StringType",
389            "Boolean" => "BooleanType",
390            "Integer" => "IntegerType",
391            "Decimal" => "DecimalType",
392            "Uri" => "UriType",
393            "Url" => "UrlType",
394            "Canonical" => "CanonicalType",
395            "Oid" => "OidType",
396            "Uuid" => "UuidType",
397            "Instant" => "InstantType",
398            "Date" => "DateType",
399            "DateTime" => "DateTimeType",
400            "Time" => "TimeType",
401            "Code" => "CodeType",
402            "Id" => "IdType",
403            "Markdown" => "MarkdownType",
404            "Base64Binary" => "Base64BinaryType",
405            "UnsignedInt" => "UnsignedIntType",
406            "PositiveInt" => "PositiveIntType",
407            "Xhtml" => "XhtmlType",
408            _ => type_name, // Already has Type suffix
409        };
410
411        Some(format!("crate::primitives::{module_name}::{type_variant}"))
412    }
413
414    /// Check if a type is a generated trait
415    fn is_generated_trait(type_name: &str) -> bool {
416        type_name.ends_with("Accessors")
417            || type_name.ends_with("Mutators")
418            || type_name.ends_with("Helpers")
419    }
420
421    /// Check if a type is likely a binding enum (standalone enum type)
422    /// rather than a nested structure within a resource.
423    ///
424    /// Binding enums typically follow patterns like:
425    /// - TaskIntent, TaskStatus (not TaskSomethingElse)
426    /// - MedicationStatus, MedicationAdminStatus
427    /// - Values that end with common enum suffixes
428    fn is_likely_binding_enum(type_name: &str) -> bool {
429        // Common suffixes that indicate binding enums
430        let binding_suffixes = [
431            "Status",
432            "Intent",
433            "Use",
434            "Type",
435            "Mode",
436            "Code",
437            "Category",
438            "Priority",
439            "Severity",
440            "State",
441            "Kind",
442            "Outcome",
443            "Action",
444            "Meaning",
445            "Behavior",
446            "Operator",
447            "Required",
448            "Selection",
449            "Timing",
450            "Purpose",
451            "Scale",
452            "Modifier",
453            "Direction",
454            "Comparator",
455            "Relationship",
456            "Participation",
457            "Capability",
458            "Interaction",
459            "Support",
460            "Content",
461            "Hierarchy",
462            "Property",
463            "Aggregation",
464            "Slicing",
465            "Derivation",
466            "Representation",
467            "Handling",
468            "Version",
469            "Classification",
470            "Assurance",
471            "Calibration",
472            "Operational",
473            "Range",
474            "Significance",
475            "Request",
476            "Response",
477            "Preference",
478            "Strand",
479            "Channel",
480            "Trigger",
481            "Variable",
482            "Orientation",
483            "Repository",
484            "Element",
485            "Study",
486            "Subject",
487            "Result",
488            "Participant",
489            "Search",
490            "Filter",
491            "Xpath",
492            "Sort",
493            "System",
494            "Gender",
495            "Actuality",
496            "Criticality",
497            "Tolerance",
498            "Plan",
499            "Team",
500            "Item",
501            "Statement",
502            "Knowledge",
503            "Evaluation",
504            "Disposition",
505            "Invoice",
506            "Component",
507            "Issue",
508            "Header",
509            "Metric",
510            "Color",
511            "Network",
512            "Note",
513            "Observation",
514            "Parameter",
515            "Provenance",
516            "Entity",
517            "Role",
518            "Publication",
519            "Quality",
520            "Quantity",
521            "Artifact",
522            "Relation",
523            "Remittance",
524            "Report",
525            "Research",
526            "Resource",
527            "Restful",
528            "Sequence",
529            "Slot",
530            "Definition",
531            "Structure",
532            "Subscription",
533            "Substance",
534            "Supply",
535            "Delivery",
536            "Task",
537            "Udi",
538            "Units",
539            "Time",
540            "Verification",
541            "Versioning",
542            "Vision",
543            "Base",
544            "Eye",
545            "Confidentiality",
546        ];
547
548        // Known exceptions - types that end with binding suffixes but are actually nested structures
549        let nested_exceptions = [
550            "BundleEntry",
551            "AccountGuarantor",
552            "AccountCoverage",
553            "ParametersParameter",
554            "MeasureReportGroup",
555            "EvidenceVariableCharacteristic",
556            "ImplementationGuideGlobal",
557            "RiskEvidenceSynthesisCertainty",
558        ];
559
560        // If it's a known nested structure exception, it's not a binding enum
561        if nested_exceptions.contains(&type_name) {
562            return false;
563        }
564
565        // Check if the type name ends with any of the common binding suffixes
566        for suffix in &binding_suffixes {
567            if type_name.ends_with(suffix) {
568                // If it starts with a resource name + suffix and the suffix is in our list,
569                // it's likely a binding enum (e.g., TaskStatus, MedicationIntent)
570                if let Some(parent_resource) = Self::detect_nested_structure_parent(type_name) {
571                    let expected_suffix_part = &type_name[parent_resource.len()..];
572                    // Check if the suffix part ends with any of our known suffixes
573                    // This handles cases like "AdminStatus" where "Status" is the core suffix
574                    if binding_suffixes
575                        .iter()
576                        .any(|s| expected_suffix_part.ends_with(s))
577                    {
578                        return true;
579                    }
580                }
581
582                // Also consider standalone types that end with these suffixes
583                // (e.g., PublicationStatus, AddressUse) - but only if they don't have
584                // a detected parent resource or are simple standalone names
585                if Self::detect_nested_structure_parent(type_name).is_none() {
586                    return true;
587                }
588            }
589        }
590
591        false
592    }
593
594    /// Get import path for generated traits
595    fn get_trait_import_path(type_name: &str) -> String {
596        // Strip the suffix (Accessors, Mutators, Helpers) to get the module name
597        let module_name = if type_name.ends_with("Accessors") {
598            type_name.strip_suffix("Accessors").unwrap_or(type_name)
599        } else if type_name.ends_with("Mutators") {
600            type_name.strip_suffix("Mutators").unwrap_or(type_name)
601        } else if type_name.ends_with("Helpers") {
602            type_name.strip_suffix("Helpers").unwrap_or(type_name)
603        } else {
604            type_name
605        };
606
607        format!(
608            "crate::traits::{}::{}",
609            crate::naming::Naming::to_snake_case(module_name),
610            type_name
611        )
612    }
613}
614
615#[cfg(test)]
616mod tests {
617    use super::*;
618
619    #[test]
620    fn test_longest_prefix_matching() {
621        // Test that longest prefix wins for nested structure detection
622        assert_eq!(
623            NamingManager::detect_nested_structure_parent("EvidenceVariableCharacteristic"),
624            Some("EvidenceVariable".to_string())
625        );
626        assert_eq!(
627            NamingManager::detect_nested_structure_parent("MeasureReportGroup"),
628            Some("MeasureReport".to_string())
629        );
630        assert_eq!(
631            NamingManager::detect_nested_structure_parent("AccountCoverage"),
632            Some("Account".to_string())
633        );
634        assert_eq!(
635            NamingManager::detect_nested_structure_parent("ImplementationGuideGlobal"),
636            Some("ImplementationGuide".to_string())
637        );
638
639        // Test ConditionStage specifically
640        assert_eq!(
641            NamingManager::detect_nested_structure_parent("ConditionStage"),
642            Some("Condition".to_string())
643        );
644    }
645
646    #[test]
647    fn test_import_path_generation() {
648        // Test correct import paths for nested structures
649        assert_eq!(
650            NamingManager::get_import_path_for_type("EvidenceVariableCharacteristic"),
651            "crate::resources::evidence_variable::EvidenceVariableCharacteristic"
652        );
653        assert_eq!(
654            NamingManager::get_import_path_for_type("MeasureReportGroup"),
655            "crate::resources::measure_report::MeasureReportGroup"
656        );
657        assert_eq!(
658            NamingManager::get_import_path_for_type("AccountCoverage"),
659            "crate::resources::account::AccountCoverage"
660        );
661
662        // Test regular resources and datatypes still work
663        assert_eq!(
664            NamingManager::get_import_path_for_type("Patient"),
665            "crate::resources::patient::Patient"
666        );
667        assert_eq!(
668            NamingManager::get_import_path_for_type("Identifier"),
669            "crate::datatypes::identifier::Identifier"
670        );
671
672        // Test trait import paths
673        assert_eq!(
674            NamingManager::get_import_path_for_type("DomainResourceAccessors"),
675            "crate::traits::domain_resource::DomainResourceAccessors"
676        );
677        assert_eq!(
678            NamingManager::get_import_path_for_type("ResourceAccessors"),
679            "crate::traits::resource::ResourceAccessors"
680        );
681
682        // Test nested structure that might be falling through to bindings
683        assert_eq!(
684            NamingManager::get_import_path_for_type("RiskEvidenceSynthesisCertainty"),
685            "crate::resources::risk_evidence_synthesis::RiskEvidenceSynthesisCertainty"
686        );
687        assert_eq!(
688            NamingManager::get_import_path_for_type("ParametersParameter"),
689            "crate::resources::parameters::ParametersParameter"
690        );
691    }
692
693    #[test]
694    fn test_resource_and_datatype_detection() {
695        assert!(NamingManager::is_fhir_resource("Patient"));
696        assert!(NamingManager::is_fhir_resource("EvidenceVariable"));
697        assert!(NamingManager::is_fhir_resource("MeasureReport"));
698        assert!(!NamingManager::is_fhir_resource("Identifier"));
699
700        assert!(NamingManager::is_fhir_datatype("Identifier"));
701        assert!(NamingManager::is_fhir_datatype("CodeableConcept"));
702        assert!(!NamingManager::is_fhir_datatype("Patient"));
703    }
704
705    #[test]
706    fn test_edge_cases() {
707        // Test that non-nested structures return None
708        assert_eq!(
709            NamingManager::detect_nested_structure_parent("Patient"),
710            None
711        );
712        assert_eq!(
713            NamingManager::detect_nested_structure_parent("Identifier"),
714            None
715        );
716
717        // Test invalid nested structure names
718        assert_eq!(
719            NamingManager::detect_nested_structure_parent("PatientSomething"),
720            Some("Patient".to_string())
721        );
722    }
723
724    #[test]
725    fn test_binding_enum_detection() {
726        // Test that common binding enum patterns are detected correctly
727        assert!(NamingManager::is_likely_binding_enum("TaskIntent"));
728        assert!(NamingManager::is_likely_binding_enum("TaskStatus"));
729        assert!(NamingManager::is_likely_binding_enum("MedicationStatus"));
730        assert!(NamingManager::is_likely_binding_enum(
731            "MedicationAdminStatus"
732        ));
733        assert!(NamingManager::is_likely_binding_enum("PublicationStatus"));
734        assert!(NamingManager::is_likely_binding_enum("AddressUse"));
735        assert!(NamingManager::is_likely_binding_enum("ContactPointSystem"));
736        assert!(NamingManager::is_likely_binding_enum("RequestPriority"));
737
738        // Test that true nested structures are NOT detected as binding enums
739        assert!(!NamingManager::is_likely_binding_enum("AccountCoverage"));
740        assert!(!NamingManager::is_likely_binding_enum("MeasureReportGroup"));
741        assert!(!NamingManager::is_likely_binding_enum(
742            "EvidenceVariableCharacteristic"
743        ));
744        assert!(!NamingManager::is_likely_binding_enum("BundleEntry"));
745
746        // Test that regular types are not detected as binding enums
747        assert!(!NamingManager::is_likely_binding_enum("Patient"));
748        assert!(!NamingManager::is_likely_binding_enum("Identifier"));
749        assert!(!NamingManager::is_likely_binding_enum("CodeableConcept"));
750    }
751
752    #[test]
753    fn test_corrected_import_paths() {
754        // Test that binding enums now get the correct import paths
755        assert_eq!(
756            NamingManager::get_import_path_for_type("TaskIntent"),
757            "crate::bindings::task_intent::TaskIntent"
758        );
759        assert_eq!(
760            NamingManager::get_import_path_for_type("TaskStatus"),
761            "crate::bindings::task_status::TaskStatus"
762        );
763        assert_eq!(
764            NamingManager::get_import_path_for_type("MedicationStatus"),
765            "crate::bindings::medication_status::MedicationStatus"
766        );
767        assert_eq!(
768            NamingManager::get_import_path_for_type("PublicationStatus"),
769            "crate::bindings::publication_status::PublicationStatus"
770        );
771
772        // Test that nested structures still get resource paths
773        assert_eq!(
774            NamingManager::get_import_path_for_type("AccountCoverage"),
775            "crate::resources::account::AccountCoverage"
776        );
777        assert_eq!(
778            NamingManager::get_import_path_for_type("MeasureReportGroup"),
779            "crate::resources::measure_report::MeasureReportGroup"
780        );
781        // Test ConditionStage specifically
782        assert_eq!(
783            NamingManager::get_import_path_for_type("ConditionStage"),
784            "crate::resources::condition::ConditionStage"
785        );
786    }
787}