1use super::*;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum PackagedArtifactTargetThresholdState {
6 Draft,
8 ProductionReady,
10}
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum PackagedArtifactTargetThresholdStateValidationError {
15 Draft,
17}
18
19impl fmt::Display for PackagedArtifactTargetThresholdStateValidationError {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 Self::Draft => write!(
23 f,
24 "the packaged-artifact target-threshold state is draft; production thresholds are not yet release-ready"
25 ),
26 }
27 }
28}
29
30impl std::error::Error for PackagedArtifactTargetThresholdStateValidationError {}
31
32impl PackagedArtifactTargetThresholdState {
33 pub const fn label(self) -> &'static str {
35 match self {
36 Self::Draft => {
37 "calibrated fit envelope recorded; production thresholds not yet release-ready"
38 }
39 Self::ProductionReady => "production thresholds recorded",
40 }
41 }
42
43 pub fn summary_line(self) -> String {
45 format!("target-threshold state: {}", self)
46 }
47
48 pub const fn is_production_ready(self) -> bool {
50 matches!(self, Self::ProductionReady)
51 }
52
53 pub fn validate_production_ready(
55 self,
56 ) -> Result<(), PackagedArtifactTargetThresholdStateValidationError> {
57 if self.is_production_ready() {
58 Ok(())
59 } else {
60 Err(PackagedArtifactTargetThresholdStateValidationError::Draft)
61 }
62 }
63
64 pub fn validated_summary_line(
66 self,
67 ) -> Result<String, PackagedArtifactTargetThresholdStateValidationError> {
68 self.validate_production_ready()?;
69 Ok(self.summary_line())
70 }
71}
72
73impl fmt::Display for PackagedArtifactTargetThresholdState {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.write_str(self.label())
76 }
77}
78
79pub fn packaged_artifact_target_threshold_state_for_report() -> String {
81 match PACKAGED_ARTIFACT_TARGET_THRESHOLD_STATE.validated_summary_line() {
82 Ok(line) => line,
83 Err(error) => format!("target-threshold state: unavailable ({error})"),
84 }
85}
86
87const PACKAGED_ARTIFACT_TARGET_THRESHOLD_STATE: PackagedArtifactTargetThresholdState =
88 PackagedArtifactTargetThresholdState::ProductionReady;
89pub(crate) const PACKAGED_ARTIFACT_TARGET_THRESHOLD_SCOPES: &[&str] = &[
90 "luminaries",
91 "major planets",
92 "pluto",
93 "lunar points",
94 "selected asteroids",
95 "custom bodies",
96];
97
98#[derive(Clone, Debug, PartialEq)]
101pub struct PackagedArtifactPhase2CorpusAlignmentSummary {
102 pub reference_snapshot_source: pleiades_jpl::ReferenceSnapshotSourceSummary,
104 pub reference_snapshot: pleiades_jpl::ReferenceSnapshotBodyClassCoverageSummary,
106 pub reference_snapshot_exact_j2000: pleiades_jpl::ReferenceSnapshotExactJ2000EvidenceSummary,
108 pub comparison_snapshot_source: pleiades_jpl::ComparisonSnapshotSourceSummary,
110 pub comparison_snapshot: pleiades_jpl::ComparisonSnapshotBodyClassCoverageSummary,
112 pub independent_holdout_source: pleiades_jpl::IndependentHoldoutSourceSummary,
114 pub independent_holdout: pleiades_jpl::IndependentHoldoutSnapshotBodyClassCoverageSummary,
116 pub selected_asteroid_source: pleiades_jpl::SelectedAsteroidSourceSummary,
118 pub selected_asteroid_source_windows: pleiades_jpl::SelectedAsteroidSourceWindowSummary,
120 pub selected_asteroid_source_request_corpus:
122 pleiades_jpl::SelectedAsteroidSourceRequestCorpusSummary,
123 pub selected_asteroid_source_request_corpus_equatorial:
125 pleiades_jpl::SelectedAsteroidSourceRequestCorpusSummary,
126 pub production_generation_boundary_source: pleiades_jpl::IndependentHoldoutSourceSummary,
128 pub production_generation_body_class_coverage:
130 pleiades_jpl::ProductionGenerationSnapshotBodyClassCoverageSummary,
131 pub production_generation_source: ProductionGenerationSourceSummary,
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
137pub enum PackagedArtifactPhase2CorpusAlignmentSummaryValidationError {
138 FieldOutOfSync { field: &'static str },
140}
141
142impl fmt::Display for PackagedArtifactPhase2CorpusAlignmentSummaryValidationError {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 match self {
145 Self::FieldOutOfSync { field } => write!(
146 f,
147 "the packaged artifact phase-2 corpus alignment summary field `{field}` is out of sync with the current posture"
148 ),
149 }
150 }
151}
152
153impl std::error::Error for PackagedArtifactPhase2CorpusAlignmentSummaryValidationError {}
154
155fn phase2_corpus_alignment_validation_field_path(field: &'static str) -> &'static str {
156 match field {
157 "reference_snapshot_source" => "phase2_corpus_alignment.reference_snapshot_source",
158 "reference_snapshot" => "phase2_corpus_alignment.reference_snapshot",
159 "reference_snapshot_exact_j2000" => {
160 "phase2_corpus_alignment.reference_snapshot_exact_j2000"
161 }
162 "comparison_snapshot_source" => "phase2_corpus_alignment.comparison_snapshot_source",
163 "comparison_snapshot" => "phase2_corpus_alignment.comparison_snapshot",
164 "independent_holdout_source" => "phase2_corpus_alignment.independent_holdout_source",
165 "independent_holdout" => "phase2_corpus_alignment.independent_holdout",
166 "selected_asteroid_source" => "phase2_corpus_alignment.selected_asteroid_source",
167 "selected_asteroid_source_windows" => {
168 "phase2_corpus_alignment.selected_asteroid_source_windows"
169 }
170 "selected_asteroid_source_request_corpus" => {
171 "phase2_corpus_alignment.selected_asteroid_source_request_corpus"
172 }
173 "selected_asteroid_source_request_corpus_equatorial" => {
174 "phase2_corpus_alignment.selected_asteroid_source_request_corpus_equatorial"
175 }
176 "production_generation_boundary_source" => {
177 "phase2_corpus_alignment.production_generation_boundary_source"
178 }
179 "production_generation_body_class_coverage" => {
180 "phase2_corpus_alignment.production_generation_body_class_coverage"
181 }
182 "production_generation_source" => "phase2_corpus_alignment.production_generation_source",
183 _ => "phase2_corpus_alignment",
184 }
185}
186
187impl PackagedArtifactPhase2CorpusAlignmentSummary {
188 pub fn summary_line(&self) -> String {
190 format!(
191 "reference source={}; reference snapshot={}; reference exact J2000 evidence={}; comparison source={}; comparison snapshot={}; independent hold-out source={}; independent hold-out={}; selected asteroid source evidence={}; selected asteroid source windows={}; selected asteroid source request corpus={}; selected asteroid source request corpus equatorial={}; production generation boundary source={}; production generation body-class coverage={}; production generation source={}",
192 self.reference_snapshot_source.summary_line(),
193 self.reference_snapshot.summary_line(),
194 self.reference_snapshot_exact_j2000.summary_line(),
195 self.comparison_snapshot_source.summary_line(),
196 self.comparison_snapshot.summary_line(),
197 self.independent_holdout_source.summary_line(),
198 self.independent_holdout.summary_line(),
199 self.selected_asteroid_source.summary_line(),
200 self.selected_asteroid_source_windows.summary_line(),
201 self.selected_asteroid_source_request_corpus.summary_line(),
202 self.selected_asteroid_source_request_corpus_equatorial.summary_line(),
203 pleiades_jpl::format_production_generation_boundary_source_summary(
204 &self.production_generation_boundary_source,
205 ),
206 self.production_generation_body_class_coverage.summary_line(),
207 self.production_generation_source.summary_line(),
208 )
209 }
210
211 pub fn validate(
213 &self,
214 ) -> Result<(), PackagedArtifactPhase2CorpusAlignmentSummaryValidationError> {
215 let Some(expected) = packaged_artifact_phase2_corpus_alignment_summary_details() else {
216 return Err(
217 PackagedArtifactPhase2CorpusAlignmentSummaryValidationError::FieldOutOfSync {
218 field: "phase2_corpus_alignment",
219 },
220 );
221 };
222
223 let field_out_of_sync = |field| {
224 PackagedArtifactPhase2CorpusAlignmentSummaryValidationError::FieldOutOfSync { field }
225 };
226
227 if self.reference_snapshot_source != expected.reference_snapshot_source {
228 return Err(field_out_of_sync("reference_snapshot_source"));
229 }
230 self.reference_snapshot_source
231 .validate()
232 .map_err(|_| field_out_of_sync("reference_snapshot_source"))?;
233
234 if self.reference_snapshot != expected.reference_snapshot {
235 return Err(field_out_of_sync("reference_snapshot"));
236 }
237 self.reference_snapshot
238 .validate()
239 .map_err(|_| field_out_of_sync("reference_snapshot"))?;
240
241 if self.reference_snapshot_exact_j2000 != expected.reference_snapshot_exact_j2000 {
242 return Err(field_out_of_sync("reference_snapshot_exact_j2000"));
243 }
244 self.reference_snapshot_exact_j2000
245 .validate()
246 .map_err(|_| field_out_of_sync("reference_snapshot_exact_j2000"))?;
247
248 if self.comparison_snapshot_source != expected.comparison_snapshot_source {
249 return Err(field_out_of_sync("comparison_snapshot_source"));
250 }
251 self.comparison_snapshot_source
252 .validate()
253 .map_err(|_| field_out_of_sync("comparison_snapshot_source"))?;
254
255 if self.comparison_snapshot != expected.comparison_snapshot {
256 return Err(field_out_of_sync("comparison_snapshot"));
257 }
258 self.comparison_snapshot
259 .validate()
260 .map_err(|_| field_out_of_sync("comparison_snapshot"))?;
261
262 if self.independent_holdout_source != expected.independent_holdout_source {
263 return Err(field_out_of_sync("independent_holdout_source"));
264 }
265 self.independent_holdout_source
266 .validate()
267 .map_err(|_| field_out_of_sync("independent_holdout_source"))?;
268
269 if self.independent_holdout != expected.independent_holdout {
270 return Err(field_out_of_sync("independent_holdout"));
271 }
272 self.independent_holdout
273 .validate()
274 .map_err(|_| field_out_of_sync("independent_holdout"))?;
275
276 if self.selected_asteroid_source != expected.selected_asteroid_source {
277 return Err(field_out_of_sync("selected_asteroid_source"));
278 }
279 self.selected_asteroid_source
280 .validate()
281 .map_err(|_| field_out_of_sync("selected_asteroid_source"))?;
282
283 if self.selected_asteroid_source_windows != expected.selected_asteroid_source_windows {
284 return Err(field_out_of_sync("selected_asteroid_source_windows"));
285 }
286 self.selected_asteroid_source_windows
287 .validate()
288 .map_err(|_| field_out_of_sync("selected_asteroid_source_windows"))?;
289
290 if self.selected_asteroid_source_request_corpus
291 != expected.selected_asteroid_source_request_corpus
292 {
293 return Err(field_out_of_sync("selected_asteroid_source_request_corpus"));
294 }
295 self.selected_asteroid_source_request_corpus
296 .validate()
297 .map_err(|_| field_out_of_sync("selected_asteroid_source_request_corpus"))?;
298
299 if self.selected_asteroid_source_request_corpus_equatorial
300 != expected.selected_asteroid_source_request_corpus_equatorial
301 {
302 return Err(field_out_of_sync(
303 "selected_asteroid_source_request_corpus_equatorial",
304 ));
305 }
306 self.selected_asteroid_source_request_corpus_equatorial
307 .validate()
308 .map_err(|_| field_out_of_sync("selected_asteroid_source_request_corpus_equatorial"))?;
309
310 if self.production_generation_boundary_source
311 != expected.production_generation_boundary_source
312 {
313 return Err(field_out_of_sync("production_generation_boundary_source"));
314 }
315 self.production_generation_boundary_source
316 .validate()
317 .map_err(|_| field_out_of_sync("production_generation_boundary_source"))?;
318
319 if self.production_generation_body_class_coverage
320 != expected.production_generation_body_class_coverage
321 {
322 return Err(field_out_of_sync(
323 "production_generation_body_class_coverage",
324 ));
325 }
326 self.production_generation_body_class_coverage
327 .validate()
328 .map_err(|_| field_out_of_sync("production_generation_body_class_coverage"))?;
329
330 if self.production_generation_source != expected.production_generation_source {
331 return Err(field_out_of_sync("production_generation_source"));
332 }
333 self.production_generation_source
334 .validate()
335 .map_err(|_| field_out_of_sync("production_generation_source"))?;
336
337 Ok(())
338 }
339
340 pub fn validated_summary_line(
342 &self,
343 ) -> Result<String, PackagedArtifactPhase2CorpusAlignmentSummaryValidationError> {
344 self.validate()?;
345 Ok(self.summary_line())
346 }
347}
348
349impl fmt::Display for PackagedArtifactPhase2CorpusAlignmentSummary {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 f.write_str(&self.summary_line())
352 }
353}
354
355pub fn packaged_artifact_phase2_corpus_alignment_summary_details(
357) -> Option<PackagedArtifactPhase2CorpusAlignmentSummary> {
358 Some(PackagedArtifactPhase2CorpusAlignmentSummary {
359 reference_snapshot_source: pleiades_jpl::reference_snapshot_source_summary(),
360 reference_snapshot: pleiades_jpl::reference_snapshot_body_class_coverage_summary()?,
361 reference_snapshot_exact_j2000:
362 pleiades_jpl::reference_snapshot_exact_j2000_evidence_summary()?,
363 comparison_snapshot_source: pleiades_jpl::comparison_snapshot_source_summary(),
364 comparison_snapshot: comparison_snapshot_body_class_coverage_summary()?,
365 independent_holdout_source: pleiades_jpl::independent_holdout_source_summary(),
366 independent_holdout: independent_holdout_snapshot_body_class_coverage_summary()?,
367 selected_asteroid_source: pleiades_jpl::selected_asteroid_source_evidence_summary()?,
368 selected_asteroid_source_windows: pleiades_jpl::selected_asteroid_source_window_summary()?,
369 selected_asteroid_source_request_corpus: selected_asteroid_source_request_corpus_summary(
370 CoordinateFrame::Ecliptic,
371 )?,
372 selected_asteroid_source_request_corpus_equatorial:
373 selected_asteroid_source_request_corpus_summary(CoordinateFrame::Equatorial)?,
374 production_generation_boundary_source:
375 pleiades_jpl::production_generation_boundary_source_summary(),
376 production_generation_body_class_coverage:
377 pleiades_jpl::production_generation_snapshot_body_class_coverage_summary()?,
378 production_generation_source: production_generation_source_summary(),
379 })
380}
381
382pub fn packaged_artifact_phase2_corpus_alignment_summary_for_report() -> String {
384 static SUMMARY: OnceLock<String> = OnceLock::new();
385 SUMMARY
386 .get_or_init(|| {
387 let summary = packaged_artifact_phase2_corpus_alignment_summary_details();
388 match summary.as_ref().map(PackagedArtifactPhase2CorpusAlignmentSummary::validated_summary_line) {
389 Some(Ok(line)) => line,
390 Some(Err(error)) => format!("phase 2 corpus alignment: unavailable ({error})"),
391 None => "phase 2 corpus alignment: unavailable (phase-2 corpus evidence should be available)".to_string(),
392 }
393 })
394 .clone()
395}
396
397#[derive(Clone, Debug, PartialEq)]
399pub struct PackagedArtifactTargetThresholdSummary {
400 pub profile_id: &'static str,
402 pub state: PackagedArtifactTargetThresholdState,
404 pub scopes: &'static [&'static str],
406 pub fit_envelope: PackagedArtifactFitEnvelopeSummary,
408 pub scope_envelopes: PackagedArtifactTargetThresholdScopeEnvelopesSummary,
410 pub phase2_corpus_alignment: PackagedArtifactPhase2CorpusAlignmentSummary,
412}
413
414#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
416pub enum PackagedArtifactTargetThresholdSummaryValidationError {
417 FieldOutOfSync { field: &'static str },
419}
420
421impl fmt::Display for PackagedArtifactTargetThresholdSummaryValidationError {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 match self {
424 Self::FieldOutOfSync { field } => write!(
425 f,
426 "the packaged artifact target-threshold summary field `{field}` is out of sync with the current posture"
427 ),
428 }
429 }
430}
431
432impl std::error::Error for PackagedArtifactTargetThresholdSummaryValidationError {}
433
434impl PackagedArtifactTargetThresholdSummary {
435 pub fn summary_line(&self) -> String {
437 format!(
438 "profile id={}; target thresholds: {}; scopes={}; {}; scope envelopes={}; phase 2 corpus alignment={}",
439 self.profile_id,
440 self.state,
441 self.scopes.join(", "),
442 self.fit_envelope.summary_line(),
443 join_display(&self.scope_envelopes.scope_envelopes),
444 self.phase2_corpus_alignment.summary_line(),
445 )
446 }
447
448 pub fn validate(&self) -> Result<(), PackagedArtifactTargetThresholdSummaryValidationError> {
450 if self.profile_id != ARTIFACT_PROFILE_ID {
451 return Err(
452 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
453 field: "profile_id",
454 },
455 );
456 }
457 self.state.validate_production_ready().map_err(|_| {
458 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync { field: "state" }
459 })?;
460 if self.state != PACKAGED_ARTIFACT_TARGET_THRESHOLD_STATE {
461 return Err(
462 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
463 field: "state",
464 },
465 );
466 }
467 if self.scopes != PACKAGED_ARTIFACT_TARGET_THRESHOLD_SCOPES {
468 return Err(
469 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
470 field: "scopes",
471 },
472 );
473 }
474
475 let expected_fit_envelope = packaged_artifact_fit_envelope_summary_details();
476 if self.fit_envelope != expected_fit_envelope {
477 return Err(
478 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
479 field: "fit_envelope",
480 },
481 );
482 }
483 if self.state.is_production_ready() {
484 let thresholds = packaged_artifact_fit_threshold_summary_details();
485 self.fit_envelope
486 .validate_against_thresholds(&thresholds)
487 .map_err(|_| {
488 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
489 field: "fit_envelope",
490 }
491 })?;
492 }
493 let expected_scope_envelopes =
494 packaged_artifact_target_threshold_scope_envelopes_summary_details();
495 if self.scope_envelopes != expected_scope_envelopes {
496 return Err(
497 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
498 field: "scope_envelopes",
499 },
500 );
501 }
502 self.scope_envelopes.validate().map_err(|_| {
503 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
504 field: "scope_envelopes",
505 }
506 })?;
507
508 let expected_phase2_corpus_alignment =
509 packaged_artifact_phase2_corpus_alignment_summary_details().ok_or(
510 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
511 field: "phase2_corpus_alignment",
512 },
513 )?;
514 self.phase2_corpus_alignment
515 .validate()
516 .map_err(|error| match error {
517 PackagedArtifactPhase2CorpusAlignmentSummaryValidationError::FieldOutOfSync {
518 field,
519 } => PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
520 field: phase2_corpus_alignment_validation_field_path(field),
521 },
522 })?;
523 if self.phase2_corpus_alignment != expected_phase2_corpus_alignment {
524 return Err(
525 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
526 field: "phase2_corpus_alignment",
527 },
528 );
529 }
530
531 let thresholds = packaged_artifact_fit_threshold_summary_details();
532 for scope_envelope in &self.scope_envelopes.scope_envelopes {
533 scope_envelope
534 .fit_envelope
535 .validate_against_thresholds(&thresholds)
536 .map_err(|_| {
537 PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
538 field: "scope_envelopes",
539 }
540 })?;
541 }
542
543 Ok(())
544 }
545
546 pub fn validated_summary_line(
548 &self,
549 ) -> Result<String, PackagedArtifactTargetThresholdSummaryValidationError> {
550 self.validate()?;
551 Ok(self.summary_line())
552 }
553}
554
555impl fmt::Display for PackagedArtifactTargetThresholdSummary {
556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 f.write_str(&self.summary_line())
558 }
559}
560
561pub fn packaged_artifact_target_threshold_summary_details() -> PackagedArtifactTargetThresholdSummary
563{
564 let summary = PackagedArtifactTargetThresholdSummary {
565 profile_id: ARTIFACT_PROFILE_ID,
566 state: PACKAGED_ARTIFACT_TARGET_THRESHOLD_STATE,
567 scopes: PACKAGED_ARTIFACT_TARGET_THRESHOLD_SCOPES,
568 fit_envelope: packaged_artifact_fit_envelope_summary_details(),
569 scope_envelopes: packaged_artifact_target_threshold_scope_envelopes_summary_details(),
570 phase2_corpus_alignment: packaged_artifact_phase2_corpus_alignment_summary_details()
571 .expect("phase-2 corpus evidence should be available"),
572 };
573 debug_assert!(summary.validate().is_ok());
574 summary
575}
576
577pub fn packaged_artifact_target_threshold_summary_for_report() -> String {
579 static SUMMARY: OnceLock<String> = OnceLock::new();
580 SUMMARY
581 .get_or_init(|| {
582 let summary = packaged_artifact_target_threshold_summary_details();
583 match summary.validated_summary_line() {
584 Ok(line) => line,
585 Err(error) => format!("target thresholds: unavailable ({error})"),
586 }
587 })
588 .clone()
589}
590
591#[derive(Clone, Debug, PartialEq)]
593pub struct PackagedArtifactSourceFitHoldoutSyncSummary {
594 pub fit_thresholds: PackagedArtifactFitThresholdSummary,
596 pub target_thresholds: PackagedArtifactTargetThresholdSummary,
598 pub phase2_corpus_alignment: PackagedArtifactPhase2CorpusAlignmentSummary,
600}
601
602#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
604pub enum PackagedArtifactSourceFitHoldoutSyncSummaryValidationError {
605 FieldOutOfSync { field: &'static str },
607}
608
609impl fmt::Display for PackagedArtifactSourceFitHoldoutSyncSummaryValidationError {
610 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611 match self {
612 Self::FieldOutOfSync { field } => write!(
613 f,
614 "the packaged artifact source-fit and hold-out sync summary field `{field}` is out of sync with the current posture"
615 ),
616 }
617 }
618}
619
620impl std::error::Error for PackagedArtifactSourceFitHoldoutSyncSummaryValidationError {}
621
622impl PackagedArtifactSourceFitHoldoutSyncSummary {
623 pub fn summary_line(&self) -> String {
625 format!(
626 "source-fit and hold-out sync: fit thresholds={}; target thresholds={}; phase 2 corpus alignment={}",
627 self.fit_thresholds.summary_line(),
628 self.target_thresholds.summary_line(),
629 self.phase2_corpus_alignment.summary_line(),
630 )
631 }
632
633 pub fn validate(
635 &self,
636 ) -> Result<(), PackagedArtifactSourceFitHoldoutSyncSummaryValidationError> {
637 let expected_fit_thresholds = packaged_artifact_fit_threshold_summary_details();
638 if self.fit_thresholds != expected_fit_thresholds {
639 return Err(
640 PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
641 field: "fit_thresholds",
642 },
643 );
644 }
645 self.fit_thresholds.validate().map_err(|_| {
646 PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
647 field: "fit_thresholds",
648 }
649 })?;
650
651 let expected_target_thresholds = packaged_artifact_target_threshold_summary_details();
652 if self.target_thresholds != expected_target_thresholds {
653 return Err(
654 PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
655 field: "target_thresholds",
656 },
657 );
658 }
659 self.target_thresholds.validate().map_err(|_| {
660 PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
661 field: "target_thresholds",
662 }
663 })?;
664
665 let expected_phase2_corpus_alignment =
666 packaged_artifact_phase2_corpus_alignment_summary_details().ok_or(
667 PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
668 field: "phase2_corpus_alignment",
669 },
670 )?;
671 self.phase2_corpus_alignment
672 .validate()
673 .map_err(|error| match error {
674 PackagedArtifactPhase2CorpusAlignmentSummaryValidationError::FieldOutOfSync {
675 field,
676 } => PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
677 field: phase2_corpus_alignment_validation_field_path(field),
678 },
679 })?;
680 if self.phase2_corpus_alignment != expected_phase2_corpus_alignment {
681 return Err(
682 PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
683 field: "phase2_corpus_alignment",
684 },
685 );
686 }
687
688 Ok(())
689 }
690
691 pub fn validated_summary_line(
693 &self,
694 ) -> Result<String, PackagedArtifactSourceFitHoldoutSyncSummaryValidationError> {
695 self.validate()?;
696 Ok(self.summary_line())
697 }
698}
699
700impl fmt::Display for PackagedArtifactSourceFitHoldoutSyncSummary {
701 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
702 f.write_str(&self.summary_line())
703 }
704}
705
706pub fn packaged_artifact_source_fit_holdout_sync_summary_details(
708) -> PackagedArtifactSourceFitHoldoutSyncSummary {
709 let summary = PackagedArtifactSourceFitHoldoutSyncSummary {
710 fit_thresholds: packaged_artifact_fit_threshold_summary_details(),
711 target_thresholds: packaged_artifact_target_threshold_summary_details(),
712 phase2_corpus_alignment: packaged_artifact_phase2_corpus_alignment_summary_details()
713 .expect("phase-2 corpus evidence should be available"),
714 };
715 debug_assert!(summary.validate().is_ok());
716 summary
717}
718
719pub fn packaged_artifact_source_fit_holdout_sync_summary_for_report() -> String {
721 static SUMMARY: OnceLock<String> = OnceLock::new();
722 SUMMARY
723 .get_or_init(|| {
724 let summary = packaged_artifact_source_fit_holdout_sync_summary_details();
725 match summary.validated_summary_line() {
726 Ok(line) => line,
727 Err(error) => format!("source-fit and hold-out sync: unavailable ({error})"),
728 }
729 })
730 .clone()
731}
732
733pub fn packaged_artifact_target_threshold_scope_envelopes_for_report() -> String {
735 static SUMMARY: OnceLock<String> = OnceLock::new();
736 SUMMARY
737 .get_or_init(|| {
738 let summary = packaged_artifact_target_threshold_scope_envelopes_summary_details();
739 match summary.validated_summary_line() {
740 Ok(line) => line,
741 Err(error) => format!("scope envelopes: unavailable ({error})"),
742 }
743 })
744 .clone()
745}