rustev_core/ranking/
select.rs1use rustev_contract::judgment::{Derivation, Lineage, Unresolved};
3use rustev_contract::ranking::{BackendFailure, Backfill, Select};
4use rustev_contract::ranking_result::*;
5use std::collections::BTreeSet;
6
7pub struct Source<'a> {
10 pub ids: Result<&'a [String], Unresolved>,
11 pub partial: bool,
12 pub derivation: Derivation,
13 pub lineage: &'a Lineage,
14}
15
16pub struct ExactOrdering<'a> {
18 pub from: &'a str,
19 pub static_class: Derivation,
20 pub source: Source<'a>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct SelectionResult {
25 pub value: Result<Selection, Unresolved>,
26 pub derivation: Derivation,
27 pub lineage: Lineage,
28 pub fallback: Option<Unresolved>,
30 pub backfill: SelectionBackfill,
32}
33
34pub fn backend_failure(reason: &Unresolved) -> bool {
35 matches!(
36 reason,
37 Unresolved::BackendUnavailable { .. }
38 | Unresolved::InvalidBackendOutput { .. }
39 | Unresolved::BudgetExhausted { .. }
40 | Unresolved::DeadlineExceeded
41 | Unresolved::Unsupported { .. }
42 )
43}
44
45fn invalid(detail: &str) -> Unresolved {
46 Unresolved::InvalidInput {
47 fields: vec![],
48 detail: detail.into(),
49 }
50}
51
52pub fn select(
56 declaration: &Select,
57 pool: &[String],
58 model: &Source<'_>,
59 backfill: Option<&ExactOrdering<'_>>,
60) -> SelectionResult {
61 let mut lineage = model.lineage.clone();
62 if let Some(b) = backfill {
63 lineage.inputs.extend(b.source.lineage.inputs.clone());
64 lineage.steps.extend(b.source.lineage.steps.clone());
65 if let Some(producers) = &b.source.lineage.producers {
66 lineage
67 .producers
68 .get_or_insert_with(Default::default)
69 .extend(producers.clone());
70 }
71 }
72 lineage.inputs.sort();
73 lineage.inputs.dedup();
74 lineage.steps.sort();
75 lineage.steps.dedup();
76 let mut result = SelectionResult {
77 value: Err(invalid("selection not evaluated")),
78 derivation: Derivation::MixedDerived,
79 lineage,
80 fallback: None,
81 backfill: SelectionBackfill::None,
82 };
83 result.value = assemble(
84 declaration,
85 pool,
86 model,
87 backfill,
88 &mut result.fallback,
89 &mut result.backfill,
90 );
91 result
92}
93
94fn assemble(
95 d: &Select,
96 pool: &[String],
97 model: &Source<'_>,
98 backfill: Option<&ExactOrdering<'_>>,
99 fallback: &mut Option<Unresolved>,
100 state: &mut SelectionBackfill,
101) -> Result<Selection, Unresolved> {
102 if !(1..=1024).contains(&d.k)
103 || pool.len() > 1024
104 || pool.iter().collect::<BTreeSet<_>>().len() != pool.len()
105 {
106 return Err(invalid("invalid selection bound or pool"));
107 }
108 match (&d.backfill, backfill) {
109 (Backfill::None, None) if d.on_backend_failure != BackendFailure::BackfillAll => {}
110 (
111 Backfill::From {
112 from,
113 exclude_selected: true,
114 },
115 Some(b),
116 ) if from == b.from
117 && b.static_class == Derivation::ExactDerived
118 && b.source.derivation == Derivation::ExactDerived => {}
119 _ => {
120 return Err(invalid(
121 "backfill must match its declared exact-derived ordering",
122 ));
123 }
124 }
125 let mut entries = Vec::new();
126 let mut selected = BTreeSet::new();
127 let status = match &model.ids {
128 Ok(ids) => {
129 if ids.len() > pool.len()
132 || ids.iter().any(|id| !pool.contains(id))
133 || ids.iter().collect::<BTreeSet<_>>().len() != ids.len()
134 {
135 return Err(invalid("invalid ranking source ids"));
136 }
137 for (p, id) in ids.iter().take(d.k as usize).enumerate() {
138 selected.insert(id.clone());
139 entries.push(SelectionEntry {
140 position: p as u64 + 1,
141 id: id.clone(),
142 origin: SelectionOrigin::Model,
143 source_position: p as u64 + 1,
144 derivation: model.derivation,
145 lineage: model.lineage.clone(),
146 });
147 }
148 SelectionModel::Ranked {
149 entries: ids.len() as u64,
150 partial: model.partial,
151 }
152 }
153 Err(reason) => {
154 if !backend_failure(reason) || d.on_backend_failure == BackendFailure::Propagate {
155 return Err(reason.clone());
156 }
157 *fallback = Some(reason.clone());
158 SelectionModel::Failed {
159 reason: reason.clone(),
160 }
161 }
162 };
163 if let Some(b) = backfill {
164 if entries.len() as u64 == d.k {
165 *state = SelectionBackfill::NotNeeded;
166 } else {
167 let ids = b.source.ids.as_ref().map_err(|reason| {
168 *state = SelectionBackfill::Unresolved {
169 reason: reason.clone(),
170 };
171 reason.clone()
172 })?;
173 let (mut taken, mut skipped_selected, mut skipped_outside_pool) = (0, 0, 0);
174 for (p, id) in ids.iter().enumerate() {
175 if entries.len() as u64 == d.k {
176 break;
177 }
178 if selected.contains(id) {
179 skipped_selected += 1;
180 } else if !pool.contains(id) {
181 skipped_outside_pool += 1;
182 } else {
183 selected.insert(id.clone());
184 taken += 1;
185 entries.push(SelectionEntry {
186 position: entries.len() as u64 + 1,
187 id: id.clone(),
188 origin: SelectionOrigin::Backfill,
189 source_position: p as u64 + 1,
190 derivation: b.source.derivation,
191 lineage: b.source.lineage.clone(),
192 });
193 }
194 }
195 *state = SelectionBackfill::Used {
196 taken,
197 skipped_selected,
198 skipped_outside_pool,
199 };
200 }
201 }
202 Ok(Selection {
203 k: d.k,
204 shortfall: d.k - entries.len() as u64,
205 entries,
206 model: status,
207 backfill: state.clone(),
208 })
209}