1use crate::solve_report::SolveReport;
17use pounce_studio_core::glossary::{citation_by_key, solve_feature_keys, Citation};
18
19pub struct Selected {
22 pub citation: &'static Citation,
23 pub reason: &'static str,
24}
25
26pub fn select(report: Option<&SolveReport>) -> Vec<Selected> {
32 let mut out: Vec<Selected> = Vec::new();
33 let mut seen: Vec<&'static str> = Vec::new();
34
35 let push = |feature: &str,
36 reason: &'static str,
37 out: &mut Vec<Selected>,
38 seen: &mut Vec<&'static str>| {
39 let Some(keys) = solve_feature_keys(feature) else {
40 return;
41 };
42 for &k in keys {
43 if seen.contains(&k) {
44 continue;
45 }
46 if let Some(citation) = citation_by_key(k) {
47 seen.push(k);
48 out.push(Selected { citation, reason });
49 }
50 }
51 };
52
53 push(
55 "core",
56 "core solver (cite for any pounce result)",
57 &mut out,
58 &mut seen,
59 );
60
61 if let Some(r) = report {
63 if r.statistics.restoration_calls > 0 {
64 push(
65 "restoration",
66 "the restoration phase was entered during this solve",
67 &mut out,
68 &mut seen,
69 );
70 }
71 }
72
73 out
74}
75
76pub fn render_human(selected: &[Selected]) -> String {
79 let mut s = String::new();
80 s.push_str("Please cite the following when publishing results obtained with pounce:\n");
81 for (i, sel) in selected.iter().enumerate() {
82 let c = sel.citation;
83 s.push_str(&format!("\n[{}] {}\n", i + 1, c.title));
84 s.push_str(&format!(" {} ({})\n", c.author, c.year));
85 if !c.venue.is_empty() {
86 s.push_str(&format!(" {}\n", c.venue));
87 }
88 if !c.doi.is_empty() {
89 s.push_str(&format!(" doi:{}\n", c.doi));
90 }
91 s.push_str(&format!(" — {}\n", sel.reason));
92 }
93 s
94}
95
96pub fn render_bibtex(selected: &[Selected]) -> String {
99 let mut s = String::new();
100 for (i, sel) in selected.iter().enumerate() {
101 let c = sel.citation;
102 if i > 0 {
103 s.push('\n');
104 }
105 s.push_str(&format!("@{}{{{},\n", c.entry_type, c.key));
106 s.push_str(&format!(" title = {{{}}},\n", c.title));
107 if !c.author.is_empty() {
108 s.push_str(&format!(" author = {{{}}},\n", c.author));
109 }
110 if !c.year.is_empty() {
111 s.push_str(&format!(" year = {{{}}},\n", c.year));
112 }
113 if !c.venue.is_empty() {
114 let venue_field = if c.entry_type == "article" {
117 "journal"
118 } else {
119 "howpublished"
120 };
121 s.push_str(&format!(" {venue_field} = {{{}}},\n", c.venue));
122 }
123 if !c.doi.is_empty() {
124 s.push_str(&format!(" doi = {{{}}},\n", c.doi));
125 }
126 s.push_str("}\n");
127 }
128 s
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::solve_report::{InputDescriptor, ReportBuilder, ReportDetail};
135
136 fn report(restoration_calls: i32) -> SolveReport {
137 let mut r = ReportBuilder::new(
138 ReportDetail::Summary,
139 InputDescriptor::Builtin {
140 name: "test".into(),
141 },
142 )
143 .finish();
144 r.statistics.restoration_calls = restoration_calls;
145 r
146 }
147
148 #[test]
149 fn core_always_present_without_report() {
150 let sel = select(None);
151 let keys: Vec<_> = sel.iter().map(|s| s.citation.key).collect();
152 assert!(keys.contains(&"pounce2026"));
153 assert!(keys.contains(&"wachter2006"));
154 assert!(!keys.contains(&"byrd2010"));
156 }
157
158 #[test]
159 fn restoration_adds_byrd_when_report_shows_it() {
160 let r = report(3);
161 let sel = select(Some(&r));
162 let keys: Vec<_> = sel.iter().map(|s| s.citation.key).collect();
163 assert!(keys.contains(&"pounce2026"));
164 assert!(keys.contains(&"byrd2010"));
165 }
166
167 #[test]
168 fn no_restoration_no_byrd() {
169 let r = report(0);
170 let sel = select(Some(&r));
171 let keys: Vec<_> = sel.iter().map(|s| s.citation.key).collect();
172 assert!(!keys.contains(&"byrd2010"));
173 }
174
175 #[test]
176 fn human_render_mentions_pounce_and_doi() {
177 let out = render_human(&select(None));
178 assert!(out.contains("POUNCE"));
179 assert!(out.contains("10.5281/zenodo.20387011"));
180 }
181
182 #[test]
183 fn bibtex_render_is_pasteable() {
184 let out = render_bibtex(&select(None));
185 assert!(out.contains("@software{pounce2026,"));
186 assert!(out.contains("@article{wachter2006,"));
187 assert!(out.contains("doi = {10.5281/zenodo.20387011}"));
188 }
189}