Final completeness by time window: ranges keep their posting span, release_final() over blocks of done ranges
Test and deploy / test (push) Successful in 58s
Test and deploy / edge (push) Successful in 2m4s
Test and deploy / deploy (push) Successful in 8m5s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-16 14:19:03 +02:00
1 parent 3d24db5565
commit ce43099188
12 files changed
+259 -9

No files matched your search

+33
View File
@@ -78,6 +78,9 @@ pub struct Batch {
pub from: i64,
pub next: i64,
pub headers: u64,
/// postedLow / postedHigh: the batch's posting times without the earliest and latest 1% (postedSpan).
pub posted_low: Option<i64>,
pub posted_high: Option<i64>,
pub releases: Vec<ReleaseChunk>,
pub samples: Vec<(ReleaseChunk, &'static str, String)>,
pub candidates: Vec<Candidate>,
@@ -277,6 +280,16 @@ fn first_track(text: &str) -> Option<usize> {
None
}
/// postedSpan: posting times without the earliest and latest 1% (floor(n / 100) at each end).
pub fn posted_span(mut dates: Vec<i64>) -> (Option<i64>, Option<i64>) {
if dates.is_empty() {
return (None, None);
}
dates.sort_unstable();
let k = dates.len() / 100;
(Some(dates[k]), Some(dates[dates.len() - 1 - k]))
}
/// nameBesideQuotes (lib/headers/subject.ts): a file name elsewhere when the quoted text has no extension.
pub fn name_beside_quotes(quoted: &str, before_counter: &str) -> Option<String> {
if has_extension(quoted) {
@@ -633,6 +646,8 @@ pub struct RangeIndexer {
count_order: Vec<(&'static str, String)>,
completed: FxHashMap<String, Vec<Vec<Seg>>>,
completed_order: Vec<String>,
/// Posting times of this batch's segments (posted_span at take).
dates: Vec<i64>,
}
impl RangeIndexer {
@@ -653,6 +668,7 @@ impl RangeIndexer {
count_order: Vec::new(),
completed: FxHashMap::default(),
completed_order: Vec::new(),
dates: Vec::new(),
}
}
@@ -683,6 +699,7 @@ impl RangeIndexer {
});
}
self.batch.headers += headers;
self.dates.extend(segments.iter().filter_map(|s| s.posted_at));
for owner in crossposts {
self.count("crosspost", owner, 1, 0);
}
@@ -725,6 +742,7 @@ impl RangeIndexer {
self.scanner.files.clear();
let next = self.batch.next;
let mut out = std::mem::replace(&mut self.batch, Batch { from: next, next, ..Default::default() });
(out.posted_low, out.posted_high) = posted_span(std::mem::take(&mut self.dates));
for id in self.count_order.drain(..) {
let (articles, files) = self.counts.remove(&id).unwrap();
out.counts.push((id.0, id.1, articles, files));
@@ -1015,3 +1033,18 @@ mod beside_quotes_tests {
}
}
}
#[cfg(test)]
mod posted_span_tests {
use super::posted_span;
#[test]
fn without_the_earliest_and_latest_percent() {
assert_eq!(posted_span(vec![]), (None, None));
let mut dates: Vec<i64> = (0..200).map(|i| 1_000_000 + i).collect();
dates.push(5);
dates.push(9_999_999_999);
// 202 dates: 2 trimmed at each end, so the two wild ones go (postedSpan).
assert_eq!(posted_span(dates), (Some(1_000_001), Some(1_000_198)));
}
}
+5 -1
View File
@@ -96,7 +96,11 @@ fn dump_batch(batch: &Batch, out: &mut impl Write) {
out.write_all(line.as_bytes()).unwrap();
line.clear();
};
line.push_str(&format!("{{\"t\":\"b\",\"from\":{},\"next\":{},\"headers\":{}}}", batch.from, batch.next, batch.headers));
let opt = |t: Option<i64>| t.map_or("null".to_string(), |t| t.to_string());
line.push_str(&format!(
"{{\"t\":\"b\",\"from\":{},\"next\":{},\"headers\":{},\"postedLow\":{},\"postedHigh\":{}}}",
batch.from, batch.next, batch.headers, opt(batch.posted_low), opt(batch.posted_high)
));
emit(&mut line);
let release = |r: &indexer::ReleaseChunk, kind: &str, line: &mut String| {
line.push_str(&format!("{{\"t\":\"{kind}\",\"upload\":{},\"key\":", r.upload));
+5
View File
@@ -74,10 +74,13 @@ struct WireFragment<'a> {
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct WireBatch<'a> {
from: i64,
next: i64,
headers: u64,
posted_low: Option<i64>,
posted_high: Option<i64>,
releases: Vec<WireRelease<'a>>,
samples: Vec<WireRelease<'a>>,
candidates: Vec<WireCandidate<'a>>,
@@ -134,6 +137,8 @@ pub fn to_json(batch: &Batch) -> Vec<u8> {
from: batch.from,
next: batch.next,
headers: batch.headers,
posted_low: batch.posted_low,
posted_high: batch.posted_high,
releases: batch.releases.iter().map(|r| release(r, None)).collect(),
samples: batch.samples.iter().map(|(r, rule, key)| release(r, Some((rule, key.as_str())))).collect(),
candidates: batch.candidates.iter().map(candidate).collect(),