fix(rid): refetch's redo hint only for changed levels and deleted spikes
CI / Format & lint (push) Successful in 18s
CI / Test suite (push) Successful in 23s
Docs / Validate documentation (push) Successful in 28s
Rust / fmt, clippy, test, build (push) Successful in 3m32s

New hours are forecast by the catch-up anyway, and a forecast issued without
the discharge RID added later is still what was issued; only a corrected or
deleted level makes the issued forecasts wrong.
This commit is contained in:
grabowski committed 2026-10-26 15:08:36 +07:00
1 parent 9bf904b23c
commit a40faf3102
4 files changed
+31 -9

No files matched your search

+1 -1
View File
@@ -25,7 +25,7 @@ ping-collector fill-gaps <days|all> # re-fetch RID reports with holes
ping-collector refetch FROM [TO] # re-fetch the RID reports of FROM..=TO (YYYY-MM-DD) even
# without holes, through the spike screening: corrects
# readings RID revised, deletes stored spikes; prints the
# catch-up-forecasts command when readings changed
# catch-up-forecasts command when stored levels changed
ping-collector check-db # connect, create missing tables, row counts
ping-collector forecast [--json] [--save] [--now T] [--rain-file F]
# forecast from the database and print it
+13 -4
View File
@@ -272,8 +272,8 @@ async fn spikes_are_screened_and_refetch_repairs_the_store() {
let s = rid::refetch(&src, &db.pool, &stations, day, day)
.await
.unwrap();
assert_eq!((s.saved, s.spikes, s.deleted), (1, 1, 1));
assert_eq!(s.first_change, Some(t(2026, 10, 24, 23, 0)));
assert_eq!((s.saved, s.spikes, s.deleted, s.changed), (1, 1, 1, 2));
assert_eq!(s.first_change, Some(t(2026, 10, 25, 0, 0)));
assert_eq!(
p1_levels(&db.pool).await,
vec![
@@ -291,8 +291,17 @@ async fn spikes_are_screened_and_refetch_repairs_the_store() {
.await
.unwrap();
assert_eq!((s.saved, s.spikes, s.deleted, s.changed), (2, 0, 0, 1));
assert_eq!(s.first_change, Some(t(2026, 10, 25, 0, 0)));
assert_eq!(s.first_change, None); // a new hour, nothing to redo
assert_eq!(p1_levels(&db.pool).await.len(), 3);
// RID revises a level: the forecasts from that hour on need a redo
src.0
.lock()
.unwrap()
.insert(day, vec![(23, 1.05), (24, 1.06)]);
let s = rid::refetch(&src, &db.pool, &stations, day, day)
.await
.unwrap();
assert_eq!(s.first_change, Some(t(2026, 10, 24, 23, 0)));
// a live cycle: the newest hour jumps and waits for the next one
let today = d(2026, 10, 25);
@@ -315,7 +324,7 @@ async fn spikes_are_screened_and_refetch_repairs_the_store() {
let o = rid::run_cycle(&src, &db.pool, &stations, t(2026, 10, 25, 3, 10)).await;
assert_eq!((o.fetched, o.saved), (5, 4));
let levels: Vec<f64> = p1_levels(&db.pool).await.iter().map(|r| r.1).collect();
assert_eq!(levels, vec![1.04, 1.06, 1.07, 1.1]);
assert_eq!(levels, vec![1.05, 1.06, 1.07, 1.1]);
// the forecasts issued from the changed hours can be redone
let mut f = forecast_row("P.1", 24, 0.9);
+1 -1
View File
@@ -255,7 +255,7 @@ async fn run(cli: Cli) -> Result<()> {
let hours = (util::bangkok_now() - t).num_hours() + 2;
if hours <= 24 * 60 {
println!(
"forecasts issued since {t} came from other readings; recompute them with\n ping-collector catch-up-forecasts --hours {hours} --redo-from {}",
"stored levels changed from {t} on: the forecasts issued since came from wrong levels; recompute them with\n ping-collector catch-up-forecasts --hours {hours} --redo-from {}",
t.format("%Y-%m-%dT%H:%M")
);
}
+16 -3
View File
@@ -251,8 +251,10 @@ pub struct StoreOutcome {
pub deleted: usize,
/// Saved readings that were new or differ from the stored ones, plus deletions.
pub changed: usize,
/// The earliest hour whose stored data changed (forecasts from then on
/// were computed from other data).
/// The earliest hour whose stored level changed or was deleted: the
/// forecasts issued from then on came from a wrong level. (New hours and
/// added discharges do not count: the catch-up forecasts new hours, and
/// a forecast issued without the discharge is still what was issued.)
pub first_change: Option<NaiveDateTime>,
}
@@ -322,12 +324,23 @@ pub async fn store(
.map(|r| r.timestamp)
.collect();
changed.extend(s.delete.iter().map(|(t, _)| *t));
let level_changed = s
.keep
.iter()
.filter(|r| {
before
.get(&(r.timestamp, r.station_num))
.is_some_and(|(l, _)| differs(*l, r.water_level, 3))
})
.map(|r| r.timestamp)
.chain(s.delete.iter().map(|(t, _)| *t))
.min();
out.saved = db::save_rid(pool, &s.keep, stations).await?;
out.deleted = db::delete_rid_readings(pool, &s.delete).await?;
out.held = s.held.len();
out.spikes = s.spikes.len();
out.changed = changed.len();
out.first_change = changed.into_iter().min();
out.first_change = level_changed;
Ok(out)
}