mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 22:02:21 +00:00
Merge pull request #1531 from quanticsoul4772/upstream/firms-age-rounding
fix(fires): show a 47.6-hour-old detection as 2d, not 48h
This commit is contained in:
4 files changed
+74
-11
No files matched your search
@@ -0,0 +1,5 @@
|
||||
- FIRMS fire ages round to whole hours before choosing a unit, so an age of
|
||||
47.5–48 hours reads "2d", as exactly 48 hours does, not "48h". Detections
|
||||
are filtered to the trailing 24 hours, so this shows mostly in the
|
||||
`STALE · cached {age}` stats line after about two days without a successful
|
||||
refresh (@quanticsoul4772, #1531).
|
||||
@@ -3053,6 +3053,7 @@ to a set time, naming its end as `at` (epoch ms): the row then reads
|
||||
`window_end`. The dev proxy never sets `at`.
|
||||
|
||||
The FIRMS CSV parser rejects impossible acquisition calendar dates before the proxy filters records to the trailing 24 hours, while retaining valid UTC acquisition times.
|
||||
FIRMS ages round to whole hours before choosing a unit, so a 47.6-hour age reads "2d", not "48h"; with detections filtered to 24 hours, this shows mostly in the `STALE · cached {age}` stats line.
|
||||
The `get_active_fires` tool reports acquisition times as UTC ISO timestamps,
|
||||
including zero-padded hours and minutes; missing or invalid dates and times
|
||||
remain `null`, including impossible calendar dates from a custom source.
|
||||
|
||||
+23
-11
@@ -535,14 +535,26 @@ export function createModel({
|
||||
return `${isolate(latPart)} ${isolate(lonPart)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Millisecond delta → `{ unit: '<1h' | 'h' | 'd', count }`, or null for
|
||||
* invalid input. Both age formatters read it, so they pick the same unit.
|
||||
*/
|
||||
|
||||
function ageParts(deltaMs) {
|
||||
if (!Number.isFinite(deltaMs) || deltaMs < 0) return null;
|
||||
const hours = deltaMs / 3600000;
|
||||
if (hours < 1) return { unit: '<1h', count: 0 };
|
||||
const wholeHours = Math.round(hours);
|
||||
if (wholeHours < 48) return { unit: 'h', count: wholeHours };
|
||||
return { unit: 'd', count: Math.round(hours / 24) };
|
||||
}
|
||||
|
||||
/** Millisecond delta → "<1h" / "Xh" / "Xd", or '' for invalid input. */
|
||||
|
||||
function formatAge(deltaMs) {
|
||||
if (!Number.isFinite(deltaMs) || deltaMs < 0) return '';
|
||||
const hours = deltaMs / 3600000;
|
||||
if (hours < 1) return '<1h';
|
||||
if (hours < 48) return `${Math.round(hours)}h`;
|
||||
return `${Math.round(hours / 24)}d`;
|
||||
const age = ageParts(deltaMs);
|
||||
if (!age) return '';
|
||||
return age.unit === '<1h' ? '<1h' : `${age.count}${age.unit}`;
|
||||
}
|
||||
|
||||
/** Millisecond delta → "<1m ago" / "Xm ago" / "Xh ago" (fresh-feed readout). */
|
||||
@@ -573,12 +585,12 @@ export function createModel({
|
||||
/** formatAge for a card: "<1h", "24h", "3d" in the UI language ("24 ч"). */
|
||||
|
||||
function formatAgeLabel(deltaMs) {
|
||||
if (!Number.isFinite(deltaMs) || deltaMs < 0) return '';
|
||||
const hours = deltaMs / 3600000;
|
||||
if (hours < 1) return t('<1h', { context: 'age' });
|
||||
if (hours < 48)
|
||||
return t('{count}h', { context: 'age', count: Math.round(hours) });
|
||||
return t('{count}d', { context: 'age', count: Math.round(hours / 24) });
|
||||
const age = ageParts(deltaMs);
|
||||
if (!age) return '';
|
||||
if (age.unit === '<1h') return t('<1h', { context: 'age' });
|
||||
return age.unit === 'h'
|
||||
? t('{count}h', { context: 'age', count: age.count })
|
||||
: t('{count}d', { context: 'age', count: age.count });
|
||||
}
|
||||
|
||||
/** Normalized 0..1 confidence → low/nominal/high display bucket. */
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { getLocale, setLocale } from '../../i18n/index.js';
|
||||
import { createModel } from './model.js';
|
||||
|
||||
const HOUR_MS = 3600000;
|
||||
|
||||
test('a fire age that rounds to 48 hours reads in days, as 48 hours does', () => {
|
||||
const previousLocale = getLocale();
|
||||
const { formatAge, formatAgeLabel } = createModel({
|
||||
services: { anchors: {} },
|
||||
});
|
||||
try {
|
||||
setLocale('en');
|
||||
assert.equal(formatAge(47.4 * HOUR_MS), '47h');
|
||||
assert.equal(formatAge(47.5 * HOUR_MS), '2d');
|
||||
assert.equal(formatAge(47.6 * HOUR_MS), '2d');
|
||||
assert.equal(formatAge(48 * HOUR_MS), '2d');
|
||||
assert.equal(formatAgeLabel(47.4 * HOUR_MS), '47h');
|
||||
assert.equal(formatAgeLabel(47.5 * HOUR_MS), '2d');
|
||||
assert.equal(formatAgeLabel(47.6 * HOUR_MS), formatAgeLabel(48 * HOUR_MS));
|
||||
assert.equal(formatAgeLabel(48 * HOUR_MS), '2d');
|
||||
} finally {
|
||||
setLocale(previousLocale);
|
||||
}
|
||||
});
|
||||
|
||||
test('both fire age formatters agree on invalid, sub-hour and multi-day ages', () => {
|
||||
const previousLocale = getLocale();
|
||||
const { formatAge, formatAgeLabel } = createModel({
|
||||
services: { anchors: {} },
|
||||
});
|
||||
try {
|
||||
setLocale('en');
|
||||
for (const format of [formatAge, formatAgeLabel]) {
|
||||
assert.equal(format(-1), '');
|
||||
assert.equal(format(Number.NaN), '');
|
||||
assert.equal(format(0.5 * HOUR_MS), '<1h');
|
||||
assert.equal(format(1 * HOUR_MS), '1h');
|
||||
assert.equal(format(72 * HOUR_MS), '3d');
|
||||
}
|
||||
} finally {
|
||||
setLocale(previousLocale);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user