1#![allow(deprecated)]
11
12use crate::error::{PdfError, Result};
13use crate::verification::{IsoRequirement, VerificationLevel};
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::fs;
17
18#[derive(Debug, Clone, Deserialize, Serialize)]
20pub struct IsoMatrix {
21 pub metadata: MatrixMetadata,
22 #[serde(flatten)]
23 pub sections: HashMap<String, IsoSection>,
24 pub overall_summary: OverallSummary,
25 pub validation_tools: ValidationTools,
26}
27
28#[derive(Debug, Clone, Deserialize, Serialize)]
30pub struct VerificationStatus {
31 pub metadata: StatusMetadata,
32 pub status: HashMap<String, RequirementStatus>,
33 pub statistics: StatusStatistics,
34}
35
36#[derive(Debug, Clone)]
38pub struct ComplianceSystem {
39 pub matrix: IsoMatrix, pub status: VerificationStatus, }
42
43#[derive(Debug, Clone, Deserialize, Serialize)]
45pub struct StatusMetadata {
46 pub last_updated: String,
47 pub matrix_version: String,
48 pub total_requirements: u32,
49 pub note: String,
50 pub warning: String,
51}
52
53#[derive(Debug, Clone, Deserialize, Serialize)]
55pub struct RequirementStatus {
56 pub level: u8,
57 pub implementation: String,
58 pub test_file: String,
59 pub verified: bool,
60 pub last_checked: String,
61 pub notes: String,
62}
63
64#[derive(Debug, Clone, Deserialize, Serialize)]
66pub struct StatusStatistics {
67 pub level_0_count: u32,
68 pub level_1_count: u32,
69 pub level_2_count: u32,
70 pub level_3_count: u32,
71 pub level_4_count: u32,
72 pub average_level: f64,
73 pub compliance_percentage: f64,
74 pub last_calculated: String,
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize)]
78pub struct MatrixMetadata {
79 pub version: String,
80 pub total_features: u32,
81 pub specification: String,
82 pub methodology: String,
83}
84
85#[derive(Debug, Clone, Deserialize, Serialize)]
86pub struct IsoSection {
87 pub name: String,
88 pub iso_section: String,
89 pub total_requirements: u32,
90 pub summary: SectionSummary,
91 pub requirements: Vec<IsoRequirementData>,
92}
93
94#[derive(Debug, Clone, Deserialize, Serialize)]
95pub struct SectionSummary {
96 pub implemented: u32,
97 pub average_level: f64,
98 pub compliance_percentage: f64,
99}
100
101#[derive(Debug, Clone, Deserialize, Serialize)]
102pub struct IsoRequirementData {
103 pub id: String,
104 pub name: String,
105 pub description: String,
106 pub iso_reference: String,
107 pub requirement_type: String, pub page: u32,
109 pub original_text: String,
110}
111
112#[derive(Debug, Clone, Deserialize, Serialize)]
113pub struct OverallSummary {
114 pub total_sections: u32,
115 pub total_requirements: u32,
116 pub total_implemented: u32,
117 pub average_level: f64,
118 pub real_compliance_percentage: f64,
119 pub level_0_count: u32,
120 pub level_1_count: u32,
121 pub level_2_count: u32,
122 pub level_3_count: u32,
123 pub level_4_count: u32,
124}
125
126#[derive(Debug, Clone, Deserialize, Serialize)]
127pub struct ValidationTools {
128 pub external_validators: Vec<String>,
129 pub internal_parser: bool,
130 pub reference_pdfs: bool,
131 pub automated_testing: bool,
132}
133
134pub fn load_matrix(path: &str) -> Result<IsoMatrix> {
136 let toml_content = fs::read_to_string(path).map_err(PdfError::Io)?;
137
138 parse_compliance_matrix_with_serde(&toml_content)
140}
141
142fn parse_compliance_matrix_with_serde(toml_content: &str) -> Result<IsoMatrix> {
144 toml::from_str::<IsoMatrix>(toml_content)
146 .map_err(|e| PdfError::ParseError(format!("Failed to parse matrix TOML: {}", e)))
147}
148
149pub fn load_default_matrix() -> Result<IsoMatrix> {
151 let potential_paths = [
153 "../ISO_COMPLIANCE_MATRIX.toml",
154 "../../ISO_COMPLIANCE_MATRIX.toml",
155 "ISO_COMPLIANCE_MATRIX.toml",
156 "./ISO_COMPLIANCE_MATRIX.toml",
157 ];
158
159 for path in &potential_paths {
160 if std::path::Path::new(path).exists() {
161 return load_matrix(path);
162 }
163 }
164
165 Err(PdfError::Io(std::io::Error::new(
166 std::io::ErrorKind::NotFound,
167 "ISO_COMPLIANCE_MATRIX.toml not found in any expected location",
168 )))
169}
170
171pub fn load_verification_status(path: &str) -> Result<VerificationStatus> {
173 let toml_content = fs::read_to_string(path).map_err(PdfError::Io)?;
174 toml::from_str(&toml_content)
175 .map_err(|e| PdfError::ParseError(format!("Failed to parse verification status: {}", e)))
176}
177
178pub fn load_default_verification_status() -> Result<VerificationStatus> {
180 let potential_paths = [
181 "../ISO_VERIFICATION_STATUS.toml",
182 "../../ISO_VERIFICATION_STATUS.toml",
183 "ISO_VERIFICATION_STATUS.toml",
184 "./ISO_VERIFICATION_STATUS.toml",
185 ];
186
187 for path in &potential_paths {
188 if std::path::Path::new(path).exists() {
189 return load_verification_status(path);
190 }
191 }
192
193 Err(PdfError::Io(std::io::Error::new(
194 std::io::ErrorKind::NotFound,
195 "ISO_VERIFICATION_STATUS.toml not found in any expected location",
196 )))
197}
198
199pub fn load_compliance_system() -> Result<ComplianceSystem> {
201 let matrix = load_default_matrix()?;
202 let status = load_default_verification_status()?;
203
204 Ok(ComplianceSystem { matrix, status })
205}
206
207impl ComplianceSystem {
208 pub fn get_all_requirements(&self) -> Vec<IsoRequirement> {
210 let mut requirements = Vec::new();
211
212 for section in self.matrix.sections.values() {
213 for req_data in §ion.requirements {
214 let status = self.status.status.get(&req_data.id);
215
216 let level = status
217 .map(|s| {
218 VerificationLevel::from_u8(s.level)
219 .unwrap_or(VerificationLevel::NotImplemented)
220 })
221 .unwrap_or(VerificationLevel::NotImplemented);
222
223 requirements.push(IsoRequirement {
224 id: req_data.id.clone(),
225 name: req_data.name.clone(),
226 description: req_data.description.clone(),
227 iso_reference: req_data.iso_reference.clone(),
228 implementation: status.and_then(|s| {
229 if s.implementation.is_empty() {
230 None
231 } else {
232 Some(s.implementation.clone())
233 }
234 }),
235 test_file: status.and_then(|s| {
236 if s.test_file.is_empty() {
237 None
238 } else {
239 Some(s.test_file.clone())
240 }
241 }),
242 level,
243 verified: status.map(|s| s.verified).unwrap_or(false),
244 notes: status
245 .map(|s| s.notes.clone())
246 .unwrap_or(req_data.requirement_type.clone()),
247 });
248 }
249 }
250
251 requirements
252 }
253
254 pub fn get_requirement_info(&self, id: &str) -> Option<RequirementInfo> {
256 for section in self.matrix.sections.values() {
258 for req_data in §ion.requirements {
259 if req_data.id == id {
260 let status = self.status.status.get(id);
262
263 return Some(RequirementInfo {
264 id: req_data.id.clone(),
265 name: req_data.name.clone(),
266 description: req_data.description.clone(),
267 iso_reference: req_data.iso_reference.clone(),
268 requirement_type: req_data.requirement_type.clone(),
269 page: req_data.page,
270 level: status.map(|s| s.level).unwrap_or(0),
271 implementation: status
272 .map(|s| s.implementation.clone())
273 .unwrap_or_default(),
274 test_file: status.map(|s| s.test_file.clone()).unwrap_or_default(),
275 verified: status.map(|s| s.verified).unwrap_or(false),
276 last_checked: status
277 .map(|s| s.last_checked.clone())
278 .unwrap_or("never".to_string()),
279 notes: status.map(|s| s.notes.clone()).unwrap_or_default(),
280 });
281 }
282 }
283 }
284 None
285 }
286
287 pub fn get_section_requirements(&self, section_id: &str) -> Option<Vec<IsoRequirement>> {
289 if let Some(section) = self.matrix.sections.get(section_id) {
290 let mut requirements = Vec::new();
291
292 for req_data in §ion.requirements {
293 let status = self.status.status.get(&req_data.id);
294
295 let level = status
296 .map(|s| {
297 VerificationLevel::from_u8(s.level)
298 .unwrap_or(VerificationLevel::NotImplemented)
299 })
300 .unwrap_or(VerificationLevel::NotImplemented);
301
302 requirements.push(IsoRequirement {
303 id: req_data.id.clone(),
304 name: req_data.name.clone(),
305 description: req_data.description.clone(),
306 iso_reference: req_data.iso_reference.clone(),
307 implementation: status.and_then(|s| {
308 if s.implementation.is_empty() {
309 None
310 } else {
311 Some(s.implementation.clone())
312 }
313 }),
314 test_file: status.and_then(|s| {
315 if s.test_file.is_empty() {
316 None
317 } else {
318 Some(s.test_file.clone())
319 }
320 }),
321 level,
322 verified: status.map(|s| s.verified).unwrap_or(false),
323 notes: status
324 .map(|s| s.notes.clone())
325 .unwrap_or(req_data.requirement_type.clone()),
326 });
327 }
328
329 Some(requirements)
330 } else {
331 None
332 }
333 }
334
335 pub fn calculate_compliance_stats(&self) -> ComplianceStats {
337 ComplianceStats {
338 total_requirements: self.status.statistics.level_0_count
339 + self.status.statistics.level_1_count
340 + self.status.statistics.level_2_count
341 + self.status.statistics.level_3_count
342 + self.status.statistics.level_4_count,
343 implemented_requirements: self.status.statistics.level_1_count
344 + self.status.statistics.level_2_count
345 + self.status.statistics.level_3_count
346 + self.status.statistics.level_4_count,
347 average_compliance_percentage: self.status.statistics.compliance_percentage,
348 level_0_count: self.status.statistics.level_0_count,
349 level_1_count: self.status.statistics.level_1_count,
350 level_2_count: self.status.statistics.level_2_count,
351 level_3_count: self.status.statistics.level_3_count,
352 level_4_count: self.status.statistics.level_4_count,
353 }
354 }
355
356 pub fn get_unimplemented_requirements(&self) -> Vec<IsoRequirement> {
358 self.get_all_requirements()
359 .into_iter()
360 .filter(|req| req.level == VerificationLevel::NotImplemented)
361 .collect()
362 }
363
364 pub fn get_partially_implemented_requirements(&self) -> Vec<IsoRequirement> {
366 self.get_all_requirements()
367 .into_iter()
368 .filter(|req| {
369 matches!(
370 req.level,
371 VerificationLevel::CodeExists | VerificationLevel::GeneratesPdf
372 )
373 })
374 .collect()
375 }
376
377 pub fn get_compliant_requirements(&self) -> Vec<IsoRequirement> {
379 self.get_all_requirements()
380 .into_iter()
381 .filter(|req| req.level == VerificationLevel::IsoCompliant)
382 .collect()
383 }
384
385 pub fn update_requirement_status(
387 &mut self,
388 id: &str,
389 new_status: RequirementStatus,
390 ) -> Result<()> {
391 self.status.status.insert(id.to_string(), new_status);
392
393 self.status.metadata.last_updated = chrono::Utc::now().to_rfc3339();
395
396 self.recalculate_statistics();
398
399 Ok(())
400 }
401
402 pub fn save_status(&self, path: &str) -> Result<()> {
404 let toml_content = toml::to_string_pretty(&self.status)
405 .map_err(|e| PdfError::ParseError(format!("Failed to serialize status: {}", e)))?;
406
407 fs::write(path, toml_content).map_err(PdfError::Io)?;
408 Ok(())
409 }
410
411 fn recalculate_statistics(&mut self) {
413 let mut level_counts = [0u32; 5];
414 let mut total_level = 0u32;
415
416 for status in self.status.status.values() {
417 if status.level <= 4 {
418 level_counts[status.level as usize] += 1;
419 total_level += status.level as u32;
420 }
421 }
422
423 let total_requirements = self.status.status.len() as u32;
424 let average_level = if total_requirements > 0 {
425 total_level as f64 / total_requirements as f64
426 } else {
427 0.0
428 };
429
430 self.status.statistics = StatusStatistics {
431 level_0_count: level_counts[0],
432 level_1_count: level_counts[1],
433 level_2_count: level_counts[2],
434 level_3_count: level_counts[3],
435 level_4_count: level_counts[4],
436 average_level,
437 compliance_percentage: (average_level / 4.0) * 100.0,
438 last_calculated: chrono::Utc::now().to_rfc3339(),
439 };
440 }
441}
442
443#[derive(Debug, Clone)]
445pub struct RequirementInfo {
446 pub id: String,
447 pub name: String,
448 pub description: String,
449 pub iso_reference: String,
450 pub requirement_type: String,
451 pub page: u32,
452 pub level: u8,
453 pub implementation: String,
454 pub test_file: String,
455 pub verified: bool,
456 pub last_checked: String,
457 pub notes: String,
458}
459
460impl IsoMatrix {
461 pub fn get_all_requirement_definitions(&self) -> Vec<&IsoRequirementData> {
464 let mut requirements = Vec::new();
465
466 for section in self.sections.values() {
467 for req_data in §ion.requirements {
468 requirements.push(req_data);
469 }
470 }
471
472 requirements
473 }
474
475 #[deprecated(
477 note = "Use ComplianceSystem::get_all_requirements() for complete requirement info"
478 )]
479 pub fn get_all_requirements(&self) -> Vec<IsoRequirement> {
480 let mut requirements = Vec::new();
481
482 for section in self.sections.values() {
483 for req_data in §ion.requirements {
484 requirements.push(IsoRequirement {
486 id: req_data.id.clone(),
487 name: req_data.name.clone(),
488 description: req_data.description.clone(),
489 iso_reference: req_data.iso_reference.clone(),
490 implementation: None, test_file: None, level: VerificationLevel::NotImplemented, verified: false, notes: req_data.requirement_type.clone(), });
496 }
497 }
498
499 requirements
500 }
501
502 #[deprecated(
504 note = "Use ComplianceSystem::get_section_requirements() for complete requirement info"
505 )]
506 pub fn get_section_requirements(&self, section_id: &str) -> Option<Vec<IsoRequirement>> {
507 if let Some(section) = self.sections.get(section_id) {
508 let mut requirements = Vec::new();
509
510 for req_data in §ion.requirements {
511 requirements.push(IsoRequirement {
513 id: req_data.id.clone(),
514 name: req_data.name.clone(),
515 description: req_data.description.clone(),
516 iso_reference: req_data.iso_reference.clone(),
517 implementation: None, test_file: None, level: VerificationLevel::NotImplemented, verified: false, notes: req_data.requirement_type.clone(), });
523 }
524
525 Some(requirements)
526 } else {
527 None
528 }
529 }
530
531 #[deprecated(
533 note = "Use ComplianceSystem::get_requirement_info() for complete requirement info"
534 )]
535 pub fn get_requirement(&self, requirement_id: &str) -> Option<IsoRequirement> {
536 for section in self.sections.values() {
537 for req_data in §ion.requirements {
538 if req_data.id == requirement_id {
539 return Some(IsoRequirement {
540 id: req_data.id.clone(),
541 name: req_data.name.clone(),
542 description: req_data.description.clone(),
543 iso_reference: req_data.iso_reference.clone(),
544 implementation: None, test_file: None, level: VerificationLevel::NotImplemented, verified: false, notes: req_data.requirement_type.clone(), });
550 }
551 }
552 }
553 None
554 }
555
556 #[deprecated(note = "Use ComplianceSystem::calculate_compliance_stats() for real statistics")]
558 pub fn calculate_compliance_stats(&self) -> ComplianceStats {
559 let all_requirements = self.get_all_requirements();
560 let total_count = all_requirements.len();
561
562 let mut level_counts = [0u32; 5]; let mut total_percentage = 0.0;
564 let mut implemented_count = 0;
565
566 for req in &all_requirements {
567 let level_index = req.level as usize;
568 level_counts[level_index] += 1;
569
570 let percentage = req.level.as_percentage();
571 total_percentage += percentage;
572
573 if req.level as u8 > 0 {
574 implemented_count += 1;
575 }
576 }
577
578 let average_percentage = if total_count > 0 {
579 total_percentage / total_count as f64
580 } else {
581 0.0
582 };
583
584 ComplianceStats {
585 total_requirements: total_count as u32,
586 implemented_requirements: implemented_count,
587 average_compliance_percentage: average_percentage,
588 level_0_count: level_counts[0],
589 level_1_count: level_counts[1],
590 level_2_count: level_counts[2],
591 level_3_count: level_counts[3],
592 level_4_count: level_counts[4],
593 }
594 }
595
596 #[deprecated(note = "Use ComplianceSystem::get_unimplemented_requirements() for real status")]
598 pub fn get_unimplemented_requirements(&self) -> Vec<IsoRequirement> {
599 self.get_all_requirements()
600 .into_iter()
601 .filter(|req| req.level == VerificationLevel::NotImplemented)
602 .collect()
603 }
604
605 #[deprecated(
607 note = "Use ComplianceSystem::get_partially_implemented_requirements() for real status"
608 )]
609 pub fn get_partially_implemented_requirements(&self) -> Vec<IsoRequirement> {
610 self.get_all_requirements()
611 .into_iter()
612 .filter(|req| {
613 matches!(
614 req.level,
615 VerificationLevel::CodeExists | VerificationLevel::GeneratesPdf
616 )
617 })
618 .collect()
619 }
620
621 #[deprecated(note = "Use ComplianceSystem::get_compliant_requirements() for real status")]
623 pub fn get_compliant_requirements(&self) -> Vec<IsoRequirement> {
624 self.get_all_requirements()
625 .into_iter()
626 .filter(|req| req.level == VerificationLevel::IsoCompliant)
627 .collect()
628 }
629}
630
631#[derive(Debug, Clone)]
633pub struct ComplianceStats {
634 pub total_requirements: u32,
635 pub implemented_requirements: u32,
636 pub average_compliance_percentage: f64,
637 pub level_0_count: u32,
638 pub level_1_count: u32,
639 pub level_2_count: u32,
640 pub level_3_count: u32,
641 pub level_4_count: u32,
642}
643
644impl ComplianceStats {
645 pub fn compliance_percentage_display(&self) -> String {
647 format!("{:.1}%", self.average_compliance_percentage)
648 }
649
650 pub fn implementation_percentage(&self) -> f64 {
652 if self.total_requirements > 0 {
653 (self.implemented_requirements as f64 / self.total_requirements as f64) * 100.0
654 } else {
655 0.0
656 }
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 use tempfile::NamedTempFile;
665
666 use std::io::Write;
667
668 fn create_test_matrix_toml() -> String {
669 r#"
670[metadata]
671version = "2025-08-21"
672total_features = 3
673specification = "ISO 32000-1:2008"
674methodology = "docs/ISO_TESTING_METHODOLOGY.md"
675
676[section_7_5]
677name = "Document Structure"
678iso_section = "7.5"
679total_requirements = 2
680
681[section_7_5.summary]
682implemented = 1
683average_level = 2.0
684compliance_percentage = 50.0
685
686[[section_7_5.requirements]]
687id = "7.5.2.1"
688name = "Catalog Type Entry"
689description = "Document catalog must have /Type /Catalog"
690iso_reference = "7.5.2, Table 3.25"
691requirement_type = "mandatory"
692page = 42
693original_text = "Document catalog must have /Type /Catalog entry"
694
695[[section_7_5.requirements]]
696id = "7.5.2.2"
697name = "Catalog Version Entry"
698description = "Optional /Version entry in catalog"
699iso_reference = "7.5.2, Table 3.25"
700requirement_type = "optional"
701page = 42
702original_text = "Optional /Version entry in document catalog"
703
704[overall_summary]
705total_sections = 1
706total_requirements = 2
707total_implemented = 1
708average_level = 1.5
709real_compliance_percentage = 37.5
710level_0_count = 1
711level_1_count = 0
712level_2_count = 0
713level_3_count = 1
714level_4_count = 0
715
716[validation_tools]
717external_validators = ["qpdf"]
718internal_parser = true
719reference_pdfs = false
720automated_testing = false
721"#
722 .to_string()
723 }
724
725 #[test]
726 fn test_load_matrix() {
727 let toml_content = create_test_matrix_toml();
728 let mut temp_file = NamedTempFile::new().unwrap();
729 temp_file.write_all(toml_content.as_bytes()).unwrap();
730
731 let matrix = load_matrix(temp_file.path().to_str().unwrap()).unwrap();
732
733 assert_eq!(matrix.metadata.total_features, 3);
734 assert_eq!(matrix.sections.len(), 1);
735 assert!(matrix.sections.contains_key("section_7_5"));
736
737 let section = &matrix.sections["section_7_5"];
738 assert_eq!(section.requirements.len(), 2);
739 assert_eq!(section.requirements[0].id, "7.5.2.1");
740 assert_eq!(section.requirements[0].requirement_type, "mandatory");
741 }
742
743 #[test]
744 fn test_get_all_requirements() {
745 let toml_content = create_test_matrix_toml();
746 let matrix: IsoMatrix = toml::from_str(&toml_content).unwrap();
747
748 let requirements = matrix.get_all_requirements();
749 assert_eq!(requirements.len(), 2);
750
751 let req1 = &requirements[0];
752 assert_eq!(req1.id, "7.5.2.1");
753 assert_eq!(req1.level, VerificationLevel::NotImplemented); assert!(!req1.verified); let req2 = &requirements[1];
757 assert_eq!(req2.id, "7.5.2.2");
758 assert_eq!(req2.level, VerificationLevel::NotImplemented); assert!(!req2.verified); }
761
762 #[test]
763 fn test_get_requirement_by_id() {
764 let toml_content = create_test_matrix_toml();
765 let matrix: IsoMatrix = toml::from_str(&toml_content).unwrap();
766
767 let req = matrix.get_requirement("7.5.2.1").unwrap();
768 assert_eq!(req.name, "Catalog Type Entry");
769 assert_eq!(req.level, VerificationLevel::NotImplemented); assert!(matrix.get_requirement("nonexistent").is_none());
772 }
773
774 #[test]
775 fn test_calculate_compliance_stats() {
776 let toml_content = create_test_matrix_toml();
777 let matrix: IsoMatrix = toml::from_str(&toml_content).unwrap();
778
779 let stats = matrix.calculate_compliance_stats();
780 assert_eq!(stats.total_requirements, 2);
781 assert_eq!(stats.implemented_requirements, 0); assert_eq!(stats.level_0_count, 2); assert_eq!(stats.level_1_count, 0);
784 assert_eq!(stats.level_2_count, 0);
785 assert_eq!(stats.level_3_count, 0);
786 assert_eq!(stats.level_4_count, 0);
787 assert_eq!(stats.average_compliance_percentage, 0.0); }
789
790 #[test]
791 fn test_get_unimplemented_requirements() {
792 let toml_content = create_test_matrix_toml();
793 let matrix: IsoMatrix = toml::from_str(&toml_content).unwrap();
794
795 let unimplemented = matrix.get_unimplemented_requirements();
796 assert_eq!(unimplemented.len(), 2); let ids: Vec<String> = unimplemented.iter().map(|r| r.id.clone()).collect();
799 assert!(ids.contains(&"7.5.2.1".to_string()));
800 assert!(ids.contains(&"7.5.2.2".to_string()));
801 }
802}