gugen/report.rs
1use crate::composition::Composition;
2use crate::evidence::PlanningEvidence;
3use crate::literature_evidence::LiteratureRouteEvidence;
4use crate::precursor::PrecursorSelection;
5use crate::prior_experiment_evidence::PriorExperimentEvidence;
6use crate::process::{PlannedStep, RouteFamily};
7use crate::provenance::PlanningProvenance;
8use crate::reaction::BalancedReaction;
9use crate::rejection::RejectedCandidate;
10use crate::route_suitability::{RouteSuitabilityAssessment, SuitabilityFinding};
11use crate::score::{ConfidenceAssessment, PlanScoreBreakdown, PlanningAssumption};
12
13/// Bumped whenever `SynthesisPlan`'s or `SynthesisPlanningReport`'s JSON
14/// shape changes in a way a strict deserializer (e.g. one rejecting
15/// unknown fields) would need to account for. `2` as of v0.4.0: adds
16/// `SynthesisPlan.literature_evidence`, confirmed as a breaking addition
17/// by `cargo semver-checks`'s `constructible_struct_adds_field` lint --
18/// see `docs/literature_evidence_integration.md`. `3` as of Phase 26:
19/// adds `SynthesisPlan.prior_experiment_evidence`, the identical breaking
20/// class -- see `docs/prior_experiment_evidence.md`.
21pub const SCHEMA_VERSION: u32 = 3;
22
23#[derive(Debug, Clone, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct TargetSummary {
26 pub composition: Composition,
27 pub structure_present: bool,
28 pub desired_phase: Option<String>,
29}
30
31/// Whether this planner can meaningfully handle the target at all
32/// (AGENTS.md §16). Distinct from per-plan confidence: applicability is
33/// about domain fit, not about how well-evidenced any particular plan is.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum ApplicabilityLevel {
37 InDomain,
38 PartiallyInDomain,
39 OutOfDomain,
40}
41
42#[derive(Debug, Clone, PartialEq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44pub struct ApplicabilityAssessment {
45 pub level: ApplicabilityLevel,
46 pub rationale: Vec<String>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51pub enum WarningSeverity {
52 Info,
53 Caution,
54 Severe,
55}
56
57#[derive(Debug, Clone, PartialEq)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub struct PlanningWarning {
60 pub message: String,
61 pub severity: WarningSeverity,
62}
63
64#[derive(Debug, Clone, PartialEq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct UnresolvedRequirement {
67 pub description: String,
68 pub reason: String,
69}
70
71/// Deterministic plan identifier (AGENTS.md §20: "plan IDを決定的にする").
72/// Phase 5 derives this from plan contents; Phase 1 only needs the type.
73#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75pub struct PlanId(pub String);
76
77impl std::fmt::Display for PlanId {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 f.write_str(&self.0)
80 }
81}
82
83/// A candidate synthesis plan (AGENTS.md §6). `steps` is `Vec<PlannedStep>`
84/// rather than the bare `Vec<ProcessStep>` AGENTS.md §6 shows, so each step
85/// can carry the `StepRequirement` §11 mandates. `manual_review_required`
86/// isn't in §6's snippet, but §15 requires the v0.1 JSON plan to carry it
87/// (or an equivalent) regardless -- see [`crate::score_plan`] for why it's
88/// always `true` in v0.1.
89#[derive(Debug, Clone, PartialEq)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91pub struct SynthesisPlan {
92 pub plan_id: PlanId,
93 pub route_family: RouteFamily,
94 pub precursors: Vec<PrecursorSelection>,
95 pub balanced_reaction: Option<BalancedReaction>,
96 pub steps: Vec<PlannedStep>,
97 pub score: PlanScoreBreakdown,
98 pub confidence: ConfidenceAssessment,
99 pub applicability: ApplicabilityAssessment,
100 pub evidence: Vec<PlanningEvidence>,
101 pub warnings: Vec<PlanningWarning>,
102 pub assumptions: Vec<PlanningAssumption>,
103 pub unresolved: Vec<UnresolvedRequirement>,
104 pub manual_review_required: bool,
105 /// Reference-only cross-DOI literature evidence for this plan's exact
106 /// (target, precursors, route_family) (v0.4.0 Integration), from a
107 /// configured `LiteratureEvidenceProvider`. `None` whenever no
108 /// provider is configured, the provider found no matching route, or
109 /// this plan's route family isn't `ConventionalSolidState` (the only
110 /// route family the underlying corpus has evidence for). Never
111 /// derived from or fed into `score`, `confidence`, `evidence`, or
112 /// `steps` -- see `literature_evidence.rs`'s module doc comment for
113 /// why that's structural, not a convention this field happens to
114 /// follow. A field/route showing `Conflict` or
115 /// `has_multiple_operation_shapes` here is a *disclosure*, not a
116 /// planning failure -- it is never auto-resolved, and its presence
117 /// alone must never be read as "condition accuracy improved" (it
118 /// means more literature coverage was found, not that any specific
119 /// value here is correct).
120 pub literature_evidence: Option<LiteratureRouteEvidence>,
121 /// Reference-only prior-experiment evidence for this plan's exact
122 /// (target, precursors, route_family) (Phase 26), from a configured
123 /// `PriorExperimentEvidenceProvider`. `None` whenever no provider is
124 /// configured or no exact-identity match was found -- unlike
125 /// `literature_evidence`, this is **not** restricted to
126 /// `ConventionalSolidState`; `route_family` is already part of the
127 /// match key, so cross-family leakage can't happen regardless. Never
128 /// derived from or fed into `score`, `confidence`, `evidence`, or
129 /// `steps`. **Never a success rate**: the matched records' actual
130 /// process conditions, grades, and catalogs are not required to
131 /// agree with each other or with this plan's own (usually
132 /// unresolved) conditions, so grouping their outcomes together
133 /// describes what was recorded, not a probability of what a new
134 /// attempt would produce -- see `PriorExperimentEvidence::outcome_tally`'s
135 /// own doc comment.
136 pub prior_experiment_evidence: Option<PriorExperimentEvidence>,
137}
138
139/// A plan excluded from the recommended list by route-suitability findings
140/// (Phase 15B) -- `plan` is unchanged from what would otherwise have
141/// appeared in `SynthesisPlanningReport.plans` (same score/confidence/
142/// evidence, since filtering happens after scoring, not instead of it);
143/// `contradicting_findings` is just the `Contradicts` findings that
144/// triggered exclusion (not the full assessment, which may also carry
145/// `Supports`/`Unknown` findings for other purposes).
146#[derive(Debug, Clone, PartialEq)]
147#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
148pub struct NotRecommendedPlan {
149 pub plan: SynthesisPlan,
150 pub contradicting_findings: Vec<SuitabilityFinding>,
151}
152
153#[derive(Debug, Clone, PartialEq)]
154#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
155pub struct SynthesisPlanningReport {
156 pub schema_version: u32,
157 pub target: TargetSummary,
158 pub applicability: ApplicabilityAssessment,
159 /// One entry per `RouteFamily` variant a `RouteSuitabilityProvider` was
160 /// asked about (Phase 15A) -- target-level, like `applicability`, not
161 /// per-plan, since suitability doesn't depend on which precursor set a
162 /// given `SynthesisPlan` used. Correlate a specific plan to its
163 /// assessment via `SynthesisPlan.route_family`. Always empty when no
164 /// provider is configured (e.g. `Planner::offline_minimal`) -- carries
165 /// no ranking weight in this phase; nothing in `score.rs` reads it.
166 pub route_suitability: Vec<RouteSuitabilityAssessment>,
167 pub plans: Vec<SynthesisPlan>,
168 /// Plans that were built (valid chemistry, a real balanced reaction and
169 /// process template) but excluded from `plans` because
170 /// `route_suitability::derive_recommendation` returned `NotRecommended`
171 /// for that plan's route family (Phase 15B) -- kept here, with the
172 /// specific findings that triggered exclusion, rather than dropped
173 /// silently. Always empty when no `RouteSuitabilityProvider` is
174 /// configured, or when every assessed route family clears the bar.
175 pub not_recommended: Vec<NotRecommendedPlan>,
176 pub rejected_candidates: Vec<RejectedCandidate>,
177 pub unresolved: Vec<UnresolvedRequirement>,
178 pub warnings: Vec<PlanningWarning>,
179 pub provenance: PlanningProvenance,
180}