1use super::equation::{Citation, EquationClass};
7use super::experiment::{
8 ExperimentHypothesis, ExperimentSpec, FalsificationAction, FalsificationCriterion,
9};
10use super::model_card::{EmcBuilder, EquationModelCard, VerificationTest};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::path::Path;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct EmcYaml {
18 #[serde(default = "default_emc_version")]
20 pub emc_version: String,
21 #[serde(default)]
23 pub emc_id: String,
24 pub identity: EmcIdentityYaml,
26 pub governing_equation: GoverningEquationYaml,
28 #[serde(default)]
30 pub analytical_derivation: Option<AnalyticalDerivationYaml>,
31 #[serde(default)]
33 pub domain_of_validity: Option<DomainValidityYaml>,
34 #[serde(default)]
36 pub verification_tests: Option<VerificationTestsYaml>,
37 #[serde(default)]
39 pub falsification_criteria: Option<FalsificationCriteriaYaml>,
40}
41
42fn default_emc_version() -> String {
43 "1.0".to_string()
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct EmcIdentityYaml {
49 pub name: String,
50 #[serde(default = "default_version")]
51 pub version: String,
52 #[serde(default)]
53 pub authors: Vec<AuthorYaml>,
54 #[serde(default)]
55 pub status: String,
56 #[serde(default)]
57 pub description: String,
58}
59
60fn default_version() -> String {
61 "1.0.0".to_string()
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct AuthorYaml {
67 pub name: String,
68 #[serde(default)]
69 pub affiliation: String,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct GoverningEquationYaml {
75 pub latex: String,
77 #[serde(default)]
79 pub plain_text: String,
80 #[serde(default)]
82 pub description: String,
83 #[serde(default)]
85 pub variables: Vec<VariableYaml>,
86 #[serde(default)]
88 pub equation_type: String,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct VariableYaml {
94 pub symbol: String,
95 pub description: String,
96 #[serde(default)]
97 pub units: String,
98 #[serde(default)]
99 pub r#type: String,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct AnalyticalDerivationYaml {
105 #[serde(default)]
106 pub primary_citation: Option<CitationYaml>,
107 #[serde(default)]
108 pub supporting_citations: Vec<CitationYaml>,
109 #[serde(default)]
110 pub derivation_method: String,
111 #[serde(default)]
112 pub derivation_summary: String,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct CitationYaml {
118 pub authors: Vec<String>,
119 #[serde(default)]
120 pub title: String,
121 #[serde(default)]
122 pub year: u32,
123 #[serde(default)]
124 pub journal: String,
125 #[serde(default)]
126 pub publisher: String,
127 #[serde(default)]
128 pub volume: Option<u32>,
129 #[serde(default)]
130 pub issue: Option<u32>,
131 #[serde(default)]
132 pub pages: String,
133 #[serde(default)]
134 pub doi: Option<String>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct DomainValidityYaml {
140 #[serde(default)]
141 pub parameters: HashMap<String, ParameterConstraintYaml>,
142 #[serde(default)]
143 pub assumptions: Vec<String>,
144 #[serde(default)]
145 pub limitations: Vec<LimitationYaml>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ParameterConstraintYaml {
151 #[serde(default)]
152 pub min: Option<f64>,
153 #[serde(default)]
154 pub max: Option<f64>,
155 #[serde(default)]
156 pub units: String,
157 #[serde(default)]
158 pub physical_constraint: String,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct LimitationYaml {
164 pub description: String,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct VerificationTestsYaml {
170 #[serde(default)]
171 pub tests: Vec<VerificationTestYaml>,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct VerificationTestYaml {
177 pub id: String,
178 pub name: String,
179 #[serde(default)]
180 pub r#type: String,
181 #[serde(default)]
182 pub parameters: HashMap<String, f64>,
183 #[serde(default)]
184 pub expected: HashMap<String, serde_yaml::Value>,
185 #[serde(default)]
186 pub tolerance: Option<f64>,
187 #[serde(default)]
188 pub description: String,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct FalsificationCriteriaYaml {
194 #[serde(default)]
195 pub criteria: Vec<FalsificationCriterionYaml>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct FalsificationCriterionYaml {
201 pub id: String,
202 pub name: String,
203 pub condition: String,
204 #[serde(default)]
205 pub threshold: Option<f64>,
206 #[serde(default)]
207 pub severity: String,
208 #[serde(default)]
209 pub interpretation: String,
210}
211
212impl EmcYaml {
213 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> {
218 let content = std::fs::read_to_string(path.as_ref())
219 .map_err(|e| format!("Failed to read EMC file: {e}"))?;
220 Self::from_yaml(&content)
221 }
222
223 pub fn from_yaml(yaml: &str) -> Result<Self, String> {
228 serde_yaml::from_str(yaml).map_err(|e| format!("Failed to parse EMC YAML: {e}"))
229 }
230
231 pub fn to_model_card(&self) -> Result<EquationModelCard, String> {
236 let mut builder = EmcBuilder::new()
237 .name(&self.identity.name)
238 .version(&self.identity.version)
239 .equation(&self.governing_equation.latex)
240 .description(&self.governing_equation.description);
241
242 let class = match self
244 .governing_equation
245 .equation_type
246 .to_lowercase()
247 .as_str()
248 {
249 "queueing" | "queue" => EquationClass::Queueing,
250 "statistical" => EquationClass::Statistical,
251 "inventory" => EquationClass::Inventory,
252 "optimization" => EquationClass::Optimization,
253 "ml" | "machine_learning" => EquationClass::MachineLearning,
254 _ => EquationClass::Conservation,
256 };
257 builder = builder.class(class);
258
259 if let Some(ref derivation) = self.analytical_derivation {
261 if let Some(ref cite) = derivation.primary_citation {
262 let authors: Vec<&str> = cite.authors.iter().map(String::as_str).collect();
263 let venue = if cite.journal.is_empty() {
264 &cite.publisher
265 } else {
266 &cite.journal
267 };
268 let mut citation = Citation::new(&authors, venue, cite.year);
269 if !cite.title.is_empty() {
270 citation = citation.with_title(&cite.title);
271 }
272 if let Some(ref doi) = cite.doi {
273 citation = citation.with_doi(doi);
274 }
275 builder = builder.citation(citation);
276 }
277 }
278
279 for var in &self.governing_equation.variables {
281 builder = builder.add_variable(&var.symbol, &var.description, &var.units);
282 }
283
284 if let Some(ref tests) = self.verification_tests {
286 for test in &tests.tests {
287 let expected = test
288 .expected
289 .get("value")
290 .and_then(serde_yaml::Value::as_f64)
291 .unwrap_or(0.0);
292 let tolerance = test.tolerance.unwrap_or(1e-6);
293 let mut vtest = VerificationTest::new(&test.name, expected, tolerance);
294 for (name, &val) in &test.parameters {
295 vtest = vtest.with_input(name, val);
296 }
297 builder = builder.add_verification_test_full(vtest);
298 }
299 }
300
301 builder.build()
305 }
306
307 pub fn validate_schema(&self) -> Result<(), Vec<String>> {
316 let mut errors = Vec::new();
317
318 if self.identity.name.is_empty() {
320 errors.push("EMC must have a name (identity.name)".to_string());
321 }
322
323 if self.governing_equation.latex.is_empty() {
325 errors
326 .push("EMC must have a governing equation (governing_equation.latex)".to_string());
327 }
328
329 if self.governing_equation.description.len() < 10 {
331 errors.push(
332 "EMC governing equation should have a meaningful description (min 10 chars)"
333 .to_string(),
334 );
335 }
336
337 if let Some(ref derivation) = self.analytical_derivation {
339 if let Some(ref cite) = derivation.primary_citation {
340 if cite.authors.is_empty() {
341 errors.push("Primary citation must have at least one author".to_string());
342 }
343 if cite.year == 0 {
344 errors.push("Primary citation must have a valid year".to_string());
345 }
346 } else {
347 errors.push("EMC must have a primary citation (EDD-03)".to_string());
348 }
349 } else {
350 errors.push("EMC must have analytical derivation section with citation".to_string());
351 }
352
353 if let Some(ref tests) = self.verification_tests {
355 if tests.tests.is_empty() {
356 errors.push("EMC must have at least one verification test (EDD-04)".to_string());
357 }
358 } else {
359 errors.push("EMC must have verification tests section".to_string());
360 }
361
362 if let Some(ref criteria) = self.falsification_criteria {
364 if criteria.criteria.is_empty() {
365 errors.push("EMC must have at least one falsification criterion".to_string());
366 }
367 }
368
369 for var in &self.governing_equation.variables {
371 if var.symbol.is_empty() {
372 errors.push("Variable must have a symbol".to_string());
373 }
374 }
375
376 if errors.is_empty() {
377 Ok(())
378 } else {
379 Err(errors)
380 }
381 }
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct ExperimentYaml {
387 #[serde(default = "default_experiment_version")]
389 pub experiment_version: String,
390 #[serde(default)]
392 pub experiment_id: String,
393 pub metadata: ExperimentMetadataYaml,
395 #[serde(default)]
397 pub equation_model_card: Option<EmcReferenceYaml>,
398 #[serde(default)]
400 pub hypothesis: Option<HypothesisYaml>,
401 pub reproducibility: ReproducibilityYaml,
403 #[serde(default)]
405 pub simulation: Option<SimulationYaml>,
406 #[serde(default)]
408 pub falsification: Option<ExperimentFalsificationYaml>,
409}
410
411fn default_experiment_version() -> String {
412 "1.0".to_string()
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct ExperimentMetadataYaml {
418 pub name: String,
419 #[serde(default)]
420 pub description: String,
421 #[serde(default)]
422 pub tags: Vec<String>,
423}
424
425#[derive(Debug, Clone, Serialize, Deserialize)]
427pub struct EmcReferenceYaml {
428 #[serde(default)]
429 pub emc_ref: String,
430 #[serde(default)]
431 pub emc_file: String,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct HypothesisYaml {
437 pub null_hypothesis: String,
438 #[serde(default)]
439 pub alternative_hypothesis: String,
440 #[serde(default)]
441 pub expected_outcome: String,
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct ReproducibilityYaml {
447 pub seed: u64,
448 #[serde(default = "default_true")]
449 pub ieee_strict: bool,
450}
451
452fn default_true() -> bool {
453 true
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct SimulationYaml {
459 #[serde(default)]
460 pub duration: Option<DurationYaml>,
461 #[serde(default)]
462 pub parameters: HashMap<String, serde_yaml::Value>,
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct DurationYaml {
468 #[serde(default)]
469 pub warmup: f64,
470 #[serde(default)]
471 pub simulation: f64,
472 #[serde(default = "default_replications")]
473 pub replications: u32,
474}
475
476fn default_replications() -> u32 {
477 30
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
482pub struct ExperimentFalsificationYaml {
483 #[serde(default)]
484 pub import_from_emc: bool,
485 #[serde(default)]
486 pub criteria: Vec<FalsificationCriterionYaml>,
487 #[serde(default)]
488 pub jidoka: Option<JidokaYaml>,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct JidokaYaml {
494 #[serde(default)]
495 pub enabled: bool,
496 #[serde(default)]
497 pub stop_on_severity: String,
498}
499
500impl ExperimentYaml {
501 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> {
506 let content = std::fs::read_to_string(path.as_ref())
507 .map_err(|e| format!("Failed to read experiment file: {e}"))?;
508 Self::from_yaml(&content)
509 }
510
511 pub fn from_yaml(yaml: &str) -> Result<Self, String> {
516 serde_yaml::from_str(yaml).map_err(|e| format!("Failed to parse experiment YAML: {e}"))
517 }
518
519 pub fn to_experiment_spec(&self) -> Result<ExperimentSpec, String> {
524 let mut builder = ExperimentSpec::builder()
525 .name(&self.metadata.name)
526 .seed(self.reproducibility.seed)
527 .description(&self.metadata.description);
528
529 if let Some(ref emc) = self.equation_model_card {
531 if !emc.emc_ref.is_empty() {
532 builder = builder.emc_reference(&emc.emc_ref);
533 }
534 }
535
536 if let Some(ref hyp) = self.hypothesis {
538 let hypothesis =
539 ExperimentHypothesis::new(&hyp.null_hypothesis, &hyp.alternative_hypothesis);
540 builder = builder.hypothesis(hypothesis);
541 }
542
543 if let Some(ref sim) = self.simulation {
545 if let Some(ref dur) = sim.duration {
546 builder = builder
547 .warmup(dur.warmup)
548 .run_length(dur.simulation)
549 .replications(dur.replications);
550 }
551 }
552
553 if let Some(ref fals) = self.falsification {
555 for crit in &fals.criteria {
556 let action = match crit.severity.to_lowercase().as_str() {
557 "critical" => FalsificationAction::RejectModel,
558 "major" => FalsificationAction::Stop,
559 "minor" => FalsificationAction::Warn,
560 _ => FalsificationAction::FlagReview,
561 };
562 let criterion = FalsificationCriterion::new(&crit.name, &crit.condition, action);
563 builder = builder.add_falsification_criterion(criterion);
564 }
565 }
566
567 builder.build()
568 }
569
570 pub fn validate_schema(&self) -> Result<(), Vec<String>> {
579 let mut errors = Vec::new();
580
581 if self.metadata.name.is_empty() {
583 errors.push("Experiment must have a name (metadata.name)".to_string());
584 }
585
586 if self.reproducibility.seed == 0 {
589 }
591
592 if let Some(ref emc) = self.equation_model_card {
594 if emc.emc_ref.is_empty() && emc.emc_file.is_empty() {
595 errors.push("Experiment must reference an EMC (emc_ref or emc_file)".to_string());
596 }
597 }
598
599 if let Some(ref hyp) = self.hypothesis {
601 if hyp.null_hypothesis.len() < 10 {
602 errors.push(
603 "Hypothesis must have a meaningful null hypothesis (min 10 chars)".to_string(),
604 );
605 }
606 if !hyp.expected_outcome.is_empty()
607 && hyp.expected_outcome != "reject"
608 && hyp.expected_outcome != "fail_to_reject"
609 {
610 errors.push(format!(
611 "Expected outcome must be 'reject' or 'fail_to_reject', got '{}'",
612 hyp.expected_outcome
613 ));
614 }
615 } else {
616 errors.push("Experiment must have a hypothesis section".to_string());
617 }
618
619 if let Some(ref fals) = self.falsification {
621 if fals.criteria.is_empty() && !fals.import_from_emc {
622 errors.push(
623 "Experiment must have falsification criteria or import_from_emc=true"
624 .to_string(),
625 );
626 }
627 for crit in &fals.criteria {
628 if crit.condition.is_empty() {
629 errors.push(format!("Criterion '{}' must have a condition", crit.name));
630 }
631 }
632 } else {
633 errors.push("Experiment must have falsification section".to_string());
634 }
635
636 if let Some(ref sim) = self.simulation {
638 if let Some(ref dur) = sim.duration {
639 if dur.simulation <= 0.0 {
640 errors.push("Simulation duration must be positive".to_string());
641 }
642 if dur.replications == 0 {
643 errors.push("Replications must be at least 1".to_string());
644 }
645 }
646 }
647
648 if errors.is_empty() {
649 Ok(())
650 } else {
651 Err(errors)
652 }
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659
660 const SAMPLE_EMC_YAML: &str = r#"
661emc_version: "1.0"
662emc_id: "EMC-TEST-001"
663
664identity:
665 name: "Little's Law"
666 version: "1.0.0"
667 status: "production"
668
669governing_equation:
670 latex: "L = \\lambda W"
671 plain_text: "WIP = Throughput × Cycle Time"
672 description: "Fundamental theorem of queueing theory"
673 equation_type: "queueing"
674 variables:
675 - symbol: "L"
676 description: "Average WIP"
677 units: "items"
678 - symbol: "λ"
679 description: "Arrival rate"
680 units: "items/time"
681 - symbol: "W"
682 description: "Cycle time"
683 units: "time"
684
685analytical_derivation:
686 primary_citation:
687 authors: ["Little, J.D.C."]
688 title: "A Proof for the Queuing Formula: L = λW"
689 journal: "Operations Research"
690 year: 1961
691
692verification_tests:
693 tests:
694 - id: "LL-001"
695 name: "Basic validation"
696 parameters:
697 lambda: 5.0
698 W: 2.0
699 expected:
700 value: 10.0
701 tolerance: 0.001
702"#;
703
704 #[test]
705 fn test_parse_emc_yaml() {
706 let emc = EmcYaml::from_yaml(SAMPLE_EMC_YAML);
707 assert!(emc.is_ok());
708 let emc = emc.ok().unwrap();
709 assert_eq!(emc.identity.name, "Little's Law");
710 assert_eq!(emc.governing_equation.variables.len(), 3);
711 }
712
713 #[test]
714 fn test_emc_to_model_card() {
715 let emc_yaml = EmcYaml::from_yaml(SAMPLE_EMC_YAML).ok().unwrap();
716 let model_card = emc_yaml.to_model_card();
717 assert!(model_card.is_ok());
718 let mc = model_card.ok().unwrap();
719 assert_eq!(mc.name, "Little's Law");
720 assert!(mc.equation.contains("lambda"));
721 }
722
723 const SAMPLE_EXPERIMENT_YAML: &str = r#"
724experiment_version: "1.0"
725experiment_id: "EXP-001"
726
727metadata:
728 name: "Little's Law Validation"
729 description: "Validate L = λW under stochastic conditions"
730
731equation_model_card:
732 emc_ref: "operations/littles_law"
733
734hypothesis:
735 null_hypothesis: "L ≠ λW under stochastic conditions"
736 alternative_hypothesis: "L = λW holds"
737 expected_outcome: "reject"
738
739reproducibility:
740 seed: 42
741 ieee_strict: true
742
743simulation:
744 duration:
745 warmup: 100.0
746 simulation: 1000.0
747 replications: 30
748
749falsification:
750 criteria:
751 - id: "FC-001"
752 name: "Linear relationship"
753 condition: "R² < 0.95"
754 severity: "critical"
755"#;
756
757 #[test]
758 fn test_parse_experiment_yaml() {
759 let exp = ExperimentYaml::from_yaml(SAMPLE_EXPERIMENT_YAML);
760 assert!(exp.is_ok());
761 let exp = exp.ok().unwrap();
762 assert_eq!(exp.metadata.name, "Little's Law Validation");
763 assert_eq!(exp.reproducibility.seed, 42);
764 }
765
766 #[test]
767 fn test_experiment_to_spec() {
768 let exp_yaml = ExperimentYaml::from_yaml(SAMPLE_EXPERIMENT_YAML)
769 .ok()
770 .unwrap();
771 let spec = exp_yaml.to_experiment_spec();
772 assert!(spec.is_ok());
773 let spec = spec.ok().unwrap();
774 assert_eq!(spec.seed(), 42);
775 assert_eq!(spec.name(), "Little's Law Validation");
776 }
777
778 #[test]
779 fn test_emc_schema_validation() {
780 let emc = EmcYaml::from_yaml(SAMPLE_EMC_YAML).ok().unwrap();
781 let result = emc.validate_schema();
782 assert!(
783 result.is_ok(),
784 "EMC should pass schema validation: {:?}",
785 result.err()
786 );
787 }
788
789 #[test]
790 fn test_experiment_schema_validation() {
791 let exp = ExperimentYaml::from_yaml(SAMPLE_EXPERIMENT_YAML)
792 .ok()
793 .unwrap();
794 let result = exp.validate_schema();
795 assert!(
796 result.is_ok(),
797 "Experiment should pass schema validation: {:?}",
798 result.err()
799 );
800 }
801
802 #[test]
803 fn test_emc_missing_required_field() {
804 let invalid_yaml = r#"
805emc_version: "1.0"
806identity:
807 name: "Missing equation"
808 version: "1.0.0"
809governing_equation:
810 latex: "x = y"
811"#;
812 let emc = EmcYaml::from_yaml(invalid_yaml);
813 if let Ok(emc) = emc {
815 let _result = emc.validate_schema();
817 }
818 }
819
820 #[test]
821 fn test_default_emc_version() {
822 assert_eq!(default_emc_version(), "1.0");
823 }
824
825 #[test]
826 fn test_default_version() {
827 assert_eq!(default_version(), "1.0.0");
828 }
829
830 #[test]
831 fn test_default_experiment_version() {
832 assert_eq!(default_experiment_version(), "1.0");
833 }
834
835 #[test]
836 fn test_default_true() {
837 assert!(default_true());
838 }
839
840 #[test]
841 fn test_default_replications() {
842 assert_eq!(default_replications(), 30);
843 }
844
845 #[test]
846 fn test_emc_from_file_not_found() {
847 let result = EmcYaml::from_file("nonexistent.yaml");
848 assert!(result.is_err());
849 assert!(result.err().unwrap().contains("Failed to read"));
850 }
851
852 #[test]
853 fn test_experiment_from_file_not_found() {
854 let result = ExperimentYaml::from_file("nonexistent.yaml");
855 assert!(result.is_err());
856 assert!(result.err().unwrap().contains("Failed to read"));
857 }
858
859 #[test]
860 fn test_emc_invalid_yaml() {
861 let invalid = "not: [valid: yaml: content";
862 let result = EmcYaml::from_yaml(invalid);
863 assert!(result.is_err());
864 assert!(result.err().unwrap().contains("Failed to parse"));
865 }
866
867 #[test]
868 fn test_experiment_invalid_yaml() {
869 let invalid = "not: [valid: yaml: content";
870 let result = ExperimentYaml::from_yaml(invalid);
871 assert!(result.is_err());
872 assert!(result.err().unwrap().contains("Failed to parse"));
873 }
874
875 #[test]
876 fn test_emc_validation_missing_name() {
877 let yaml = r#"
878identity:
879 name: ""
880 version: "1.0.0"
881governing_equation:
882 latex: "x = y"
883 description: "A valid description"
884"#;
885 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
886 let result = emc.validate_schema();
887 assert!(result.is_err());
888 let errors = result.err().unwrap();
889 assert!(errors.iter().any(|e| e.contains("name")));
890 }
891
892 #[test]
893 fn test_emc_validation_missing_latex() {
894 let yaml = r#"
895identity:
896 name: "Test"
897 version: "1.0.0"
898governing_equation:
899 latex: ""
900 description: "A valid description"
901"#;
902 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
903 let result = emc.validate_schema();
904 assert!(result.is_err());
905 let errors = result.err().unwrap();
906 assert!(errors.iter().any(|e| e.contains("governing equation")));
907 }
908
909 #[test]
910 fn test_emc_validation_short_description() {
911 let yaml = r#"
912identity:
913 name: "Test"
914 version: "1.0.0"
915governing_equation:
916 latex: "x = y"
917 description: "Short"
918"#;
919 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
920 let result = emc.validate_schema();
921 assert!(result.is_err());
922 let errors = result.err().unwrap();
923 assert!(errors.iter().any(|e| e.contains("meaningful description")));
924 }
925
926 #[test]
927 fn test_emc_validation_empty_authors() {
928 let yaml = r#"
929identity:
930 name: "Test"
931 version: "1.0.0"
932governing_equation:
933 latex: "x = y"
934 description: "A valid description for the equation"
935analytical_derivation:
936 primary_citation:
937 authors: []
938 title: "Test"
939 year: 2020
940"#;
941 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
942 let result = emc.validate_schema();
943 assert!(result.is_err());
944 let errors = result.err().unwrap();
945 assert!(errors.iter().any(|e| e.contains("author")));
946 }
947
948 #[test]
949 fn test_emc_validation_invalid_year() {
950 let yaml = r#"
951identity:
952 name: "Test"
953 version: "1.0.0"
954governing_equation:
955 latex: "x = y"
956 description: "A valid description for the equation"
957analytical_derivation:
958 primary_citation:
959 authors: ["Author"]
960 title: "Test"
961 year: 0
962"#;
963 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
964 let result = emc.validate_schema();
965 assert!(result.is_err());
966 let errors = result.err().unwrap();
967 assert!(errors.iter().any(|e| e.contains("year")));
968 }
969
970 #[test]
971 fn test_emc_validation_empty_verification_tests() {
972 let yaml = r#"
973identity:
974 name: "Test"
975 version: "1.0.0"
976governing_equation:
977 latex: "x = y"
978 description: "A valid description for the equation"
979analytical_derivation:
980 primary_citation:
981 authors: ["Author"]
982 title: "Test"
983 year: 2020
984verification_tests:
985 tests: []
986"#;
987 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
988 let result = emc.validate_schema();
989 assert!(result.is_err());
990 let errors = result.err().unwrap();
991 assert!(errors.iter().any(|e| e.contains("verification test")));
992 }
993
994 #[test]
995 fn test_emc_validation_empty_falsification() {
996 let yaml = r#"
997identity:
998 name: "Test"
999 version: "1.0.0"
1000governing_equation:
1001 latex: "x = y"
1002 description: "A valid description for the equation"
1003analytical_derivation:
1004 primary_citation:
1005 authors: ["Author"]
1006 title: "Test"
1007 year: 2020
1008verification_tests:
1009 tests:
1010 - id: "T1"
1011 name: "Test"
1012falsification_criteria:
1013 criteria: []
1014"#;
1015 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
1016 let result = emc.validate_schema();
1017 assert!(result.is_err());
1018 let errors = result.err().unwrap();
1019 assert!(errors.iter().any(|e| e.contains("falsification")));
1020 }
1021
1022 #[test]
1023 fn test_emc_validation_empty_variable_symbol() {
1024 let yaml = r#"
1025identity:
1026 name: "Test"
1027 version: "1.0.0"
1028governing_equation:
1029 latex: "x = y"
1030 description: "A valid description for the equation"
1031 variables:
1032 - symbol: ""
1033 description: "Empty symbol"
1034analytical_derivation:
1035 primary_citation:
1036 authors: ["Author"]
1037 title: "Test"
1038 year: 2020
1039verification_tests:
1040 tests:
1041 - id: "T1"
1042 name: "Test"
1043"#;
1044 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
1045 let result = emc.validate_schema();
1046 assert!(result.is_err());
1047 let errors = result.err().unwrap();
1048 assert!(errors.iter().any(|e| e.contains("symbol")));
1049 }
1050
1051 #[test]
1052 fn test_experiment_validation_missing_name() {
1053 let yaml = r#"
1054metadata:
1055 name: ""
1056reproducibility:
1057 seed: 42
1058"#;
1059 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1060 let result = exp.validate_schema();
1061 assert!(result.is_err());
1062 let errors = result.err().unwrap();
1063 assert!(errors.iter().any(|e| e.contains("name")));
1064 }
1065
1066 #[test]
1067 fn test_experiment_validation_empty_emc_ref() {
1068 let yaml = r#"
1069metadata:
1070 name: "Test"
1071reproducibility:
1072 seed: 42
1073equation_model_card:
1074 emc_ref: ""
1075 emc_file: ""
1076"#;
1077 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1078 let result = exp.validate_schema();
1079 assert!(result.is_err());
1080 let errors = result.err().unwrap();
1081 assert!(errors.iter().any(|e| e.contains("EMC")));
1082 }
1083
1084 #[test]
1085 fn test_experiment_validation_short_hypothesis() {
1086 let yaml = r#"
1087metadata:
1088 name: "Test"
1089reproducibility:
1090 seed: 42
1091hypothesis:
1092 null_hypothesis: "Short"
1093"#;
1094 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1095 let result = exp.validate_schema();
1096 assert!(result.is_err());
1097 let errors = result.err().unwrap();
1098 assert!(errors
1099 .iter()
1100 .any(|e| e.contains("meaningful null hypothesis")));
1101 }
1102
1103 #[test]
1104 fn test_experiment_validation_invalid_expected_outcome() {
1105 let yaml = r#"
1106metadata:
1107 name: "Test"
1108reproducibility:
1109 seed: 42
1110hypothesis:
1111 null_hypothesis: "A valid null hypothesis for testing"
1112 expected_outcome: "invalid_outcome"
1113"#;
1114 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1115 let result = exp.validate_schema();
1116 assert!(result.is_err());
1117 let errors = result.err().unwrap();
1118 assert!(errors.iter().any(|e| e.contains("Expected outcome")));
1119 }
1120
1121 #[test]
1122 fn test_experiment_validation_empty_falsification_criteria() {
1123 let yaml = r#"
1124metadata:
1125 name: "Test"
1126reproducibility:
1127 seed: 42
1128hypothesis:
1129 null_hypothesis: "A valid null hypothesis for testing"
1130 expected_outcome: "reject"
1131falsification:
1132 import_from_emc: false
1133 criteria: []
1134"#;
1135 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1136 let result = exp.validate_schema();
1137 assert!(result.is_err());
1138 let errors = result.err().unwrap();
1139 assert!(errors.iter().any(|e| e.contains("falsification criteria")));
1140 }
1141
1142 #[test]
1143 fn test_experiment_validation_criterion_no_condition() {
1144 let yaml = r#"
1145metadata:
1146 name: "Test"
1147reproducibility:
1148 seed: 42
1149hypothesis:
1150 null_hypothesis: "A valid null hypothesis for testing"
1151 expected_outcome: "reject"
1152falsification:
1153 criteria:
1154 - id: "FC-001"
1155 name: "Test"
1156 condition: ""
1157"#;
1158 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1159 let result = exp.validate_schema();
1160 assert!(result.is_err());
1161 let errors = result.err().unwrap();
1162 assert!(errors.iter().any(|e| e.contains("condition")));
1163 }
1164
1165 #[test]
1166 fn test_experiment_validation_negative_duration() {
1167 let yaml = r#"
1168metadata:
1169 name: "Test"
1170reproducibility:
1171 seed: 42
1172hypothesis:
1173 null_hypothesis: "A valid null hypothesis for testing"
1174 expected_outcome: "reject"
1175falsification:
1176 criteria:
1177 - id: "FC-001"
1178 name: "Test"
1179 condition: "error < 0.01"
1180simulation:
1181 duration:
1182 simulation: -10.0
1183 replications: 30
1184"#;
1185 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1186 let result = exp.validate_schema();
1187 assert!(result.is_err());
1188 let errors = result.err().unwrap();
1189 assert!(errors.iter().any(|e| e.contains("positive")));
1190 }
1191
1192 #[test]
1193 fn test_experiment_validation_zero_replications() {
1194 let yaml = r#"
1195metadata:
1196 name: "Test"
1197reproducibility:
1198 seed: 42
1199hypothesis:
1200 null_hypothesis: "A valid null hypothesis for testing"
1201 expected_outcome: "reject"
1202falsification:
1203 criteria:
1204 - id: "FC-001"
1205 name: "Test"
1206 condition: "error < 0.01"
1207simulation:
1208 duration:
1209 simulation: 100.0
1210 replications: 0
1211"#;
1212 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1213 let result = exp.validate_schema();
1214 assert!(result.is_err());
1215 let errors = result.err().unwrap();
1216 assert!(errors.iter().any(|e| e.contains("Replications")));
1217 }
1218
1219 #[test]
1220 fn test_emc_to_model_card_with_doi() {
1221 let yaml = r#"
1222emc_version: "1.0"
1223identity:
1224 name: "Test EMC"
1225 version: "1.0.0"
1226governing_equation:
1227 latex: "x = y"
1228 description: "Test equation for model card"
1229 equation_type: "optimization"
1230 variables:
1231 - symbol: "x"
1232 description: "Input"
1233 units: "m"
1234 - symbol: "y"
1235 description: "Output"
1236 units: "m"
1237analytical_derivation:
1238 primary_citation:
1239 authors: ["Test Author"]
1240 title: "Test Title"
1241 year: 2020
1242 journal: "Test Journal"
1243 doi: "10.1000/test"
1244verification_tests:
1245 tests:
1246 - id: "VT-001"
1247 name: "Basic Test"
1248 parameters:
1249 x: 1.0
1250 expected:
1251 value: 1.0
1252 tolerance: 0.001
1253"#;
1254 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
1255 let model_card = emc.to_model_card();
1256 assert!(model_card.is_ok());
1257 let mc = model_card.ok().unwrap();
1258 assert_eq!(mc.name, "Test EMC");
1259 }
1260
1261 #[test]
1262 fn test_emc_to_model_card_equation_classes() {
1263 fn make_emc_yaml(name: &str, eq_type: &str) -> String {
1265 format!(
1266 r#"
1267identity:
1268 name: "{name}"
1269governing_equation:
1270 latex: "x = y"
1271 description: "Test description for {eq_type}"
1272 equation_type: "{eq_type}"
1273analytical_derivation:
1274 primary_citation:
1275 authors: ["Author"]
1276 title: "Title"
1277 year: 2020
1278verification_tests:
1279 tests:
1280 - id: "VT-001"
1281 name: "Basic Test"
1282 expected:
1283 value: 1.0
1284"#
1285 )
1286 }
1287
1288 let yaml = make_emc_yaml("Queue", "queue");
1290 let emc = EmcYaml::from_yaml(&yaml).ok().unwrap();
1291 let mc = emc.to_model_card().ok().unwrap();
1292 assert_eq!(mc.class, EquationClass::Queueing);
1293
1294 let yaml = make_emc_yaml("Stat", "statistical");
1296 let emc = EmcYaml::from_yaml(&yaml).ok().unwrap();
1297 let mc = emc.to_model_card().ok().unwrap();
1298 assert_eq!(mc.class, EquationClass::Statistical);
1299
1300 let yaml = make_emc_yaml("Inv", "inventory");
1302 let emc = EmcYaml::from_yaml(&yaml).ok().unwrap();
1303 let mc = emc.to_model_card().ok().unwrap();
1304 assert_eq!(mc.class, EquationClass::Inventory);
1305
1306 let yaml = make_emc_yaml("ML", "machine_learning");
1308 let emc = EmcYaml::from_yaml(&yaml).ok().unwrap();
1309 let mc = emc.to_model_card().ok().unwrap();
1310 assert_eq!(mc.class, EquationClass::MachineLearning);
1311 }
1312
1313 #[test]
1314 fn test_emc_to_model_card_with_publisher() {
1315 let yaml = r#"
1316identity:
1317 name: "Test"
1318governing_equation:
1319 latex: "x = y"
1320 description: "Test description that is valid"
1321analytical_derivation:
1322 primary_citation:
1323 authors: ["Author"]
1324 title: "Title"
1325 year: 2020
1326 publisher: "Publisher Name"
1327verification_tests:
1328 tests:
1329 - id: "VT-001"
1330 name: "Basic Test"
1331 expected:
1332 value: 1.0
1333"#;
1334 let emc = EmcYaml::from_yaml(yaml).ok().unwrap();
1335 let mc = emc.to_model_card().ok().unwrap();
1336 assert!(mc.citation.venue.contains("Publisher"));
1337 }
1338
1339 #[test]
1340 fn test_experiment_to_spec_with_severity_levels() {
1341 let yaml = r#"
1343metadata:
1344 name: "Test"
1345 description: "Test description"
1346reproducibility:
1347 seed: 42
1348equation_model_card:
1349 emc_ref: "test/emc"
1350hypothesis:
1351 null_hypothesis: "Test null hypothesis for validation"
1352 alternative_hypothesis: "Alternative"
1353falsification:
1354 criteria:
1355 - id: "FC-001"
1356 name: "Critical"
1357 condition: "error < 0.01"
1358 severity: "critical"
1359 - id: "FC-002"
1360 name: "Major"
1361 condition: "error < 0.05"
1362 severity: "major"
1363 - id: "FC-003"
1364 name: "Minor"
1365 condition: "error < 0.10"
1366 severity: "minor"
1367 - id: "FC-004"
1368 name: "Unknown"
1369 condition: "error < 0.20"
1370 severity: "unknown"
1371simulation:
1372 duration:
1373 warmup: 10.0
1374 simulation: 100.0
1375 replications: 10
1376"#;
1377 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1378 let spec = exp.to_experiment_spec();
1379 assert!(spec.is_ok());
1380 let spec = spec.ok().unwrap();
1381 assert_eq!(spec.falsification_criteria().len(), 4);
1382 }
1383
1384 #[test]
1385 fn test_experiment_to_spec_no_emc_ref() {
1386 let yaml = r#"
1387metadata:
1388 name: "Test"
1389reproducibility:
1390 seed: 42
1391equation_model_card:
1392 emc_ref: ""
1393"#;
1394 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1395 let spec = exp.to_experiment_spec();
1396 assert!(spec.is_ok());
1397 }
1398
1399 #[test]
1400 fn test_experiment_to_spec_no_simulation() {
1401 let yaml = r#"
1402metadata:
1403 name: "Test"
1404reproducibility:
1405 seed: 42
1406"#;
1407 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1408 let spec = exp.to_experiment_spec();
1409 assert!(spec.is_ok());
1410 }
1411
1412 #[test]
1413 fn test_experiment_validation_valid_expected_outcomes() {
1414 let yaml = r#"
1416metadata:
1417 name: "Test"
1418reproducibility:
1419 seed: 42
1420hypothesis:
1421 null_hypothesis: "A valid null hypothesis for testing"
1422 expected_outcome: "reject"
1423falsification:
1424 import_from_emc: true
1425"#;
1426 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1427 let result = exp.validate_schema();
1428 assert!(result.is_ok());
1429
1430 let yaml = r#"
1432metadata:
1433 name: "Test"
1434reproducibility:
1435 seed: 42
1436hypothesis:
1437 null_hypothesis: "A valid null hypothesis for testing"
1438 expected_outcome: "fail_to_reject"
1439falsification:
1440 import_from_emc: true
1441"#;
1442 let exp = ExperimentYaml::from_yaml(yaml).ok().unwrap();
1443 let result = exp.validate_schema();
1444 assert!(result.is_ok());
1445 }
1446
1447 #[test]
1448 fn test_author_yaml_serialization() {
1449 let author = AuthorYaml {
1450 name: "Test Author".to_string(),
1451 affiliation: "Test University".to_string(),
1452 };
1453 let json = serde_json::to_string(&author);
1454 assert!(json.is_ok());
1455 }
1456
1457 #[test]
1458 fn test_jidoka_yaml() {
1459 let jidoka = JidokaYaml {
1460 enabled: true,
1461 stop_on_severity: "critical".to_string(),
1462 };
1463 assert!(jidoka.enabled);
1464 }
1465
1466 #[test]
1467 fn test_limitation_yaml() {
1468 let limitation = LimitationYaml {
1469 description: "Test limitation".to_string(),
1470 };
1471 assert!(!limitation.description.is_empty());
1472 }
1473
1474 #[test]
1475 fn test_parameter_constraint_yaml() {
1476 let constraint = ParameterConstraintYaml {
1477 min: Some(0.0),
1478 max: Some(100.0),
1479 units: "meters".to_string(),
1480 physical_constraint: "must be positive".to_string(),
1481 };
1482 assert!(constraint.min.is_some());
1483 assert!(constraint.max.is_some());
1484 }
1485}