Merge upstream/main into PR #1042

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-15 15:56:27 +01:00
commit 25f5812e10
4 files changed
+60 -6

No files matched your search

+1 -1
View File
@@ -38,7 +38,7 @@
- Recent Imagery continues past malformed records in full catalog pages, respects the requested record limit, and reports results as truncated when that limit drops valid records. - Recent Imagery continues past malformed records in full catalog pages, respects the requested record limit, and reports results as truncated when that limit drops valid records.
- Reject impossible FIRMS acquisition calendar days in the CSV parser before trailing-24-hour filtering. - Regional articles retain unknown publication times when compact source dates do not identify a real calendar instant.
- Treat explicitly current, complete empty vessel coverage as a successful update, - Treat explicitly current, complete empty vessel coverage as a successful update,
clearing obsolete contacts while retaining stale or incomplete snapshots. clearing obsolete contacts while retaining stale or incomplete snapshots.
+3 -1
View File
@@ -1177,7 +1177,9 @@ stalled downloads are cancelled. Development and preview use the same handler.
Overpass query validation, geometry simplification, disk caching and upstream Overpass query validation, geometry simplification, disk caching and upstream
transport now have separate modules; military-installation search reuses that transport now have separate modules; military-installation search reuses that
transport. Regional briefing combines separate place, news and weather sources, transport. Regional briefing combines separate place, news and weather sources,
while weather effects uses only the weather source. Existing process-scoped while weather effects uses only the weather source.
GDELT compact dates that do not name a real calendar instant keep the article
with an unknown publication time. Existing process-scoped
caches, rate limits, stale fallbacks and route ordering are preserved. caches, rate limits, stale fallbacks and route ordering are preserved.
Local voice has separate HUD-summary, debug-log and Realtime-token handlers, Local voice has separate HUD-summary, debug-log and Realtime-token handlers,
@@ -0,0 +1,43 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { normalizeRegionalArticles } from './regionalModel.js';
test('compact source dates must denote an actual calendar instant', () => {
for (const date of [
'20260230T120000Z',
'20260431T120000Z',
'20261301T120000Z',
'20260716T240000Z',
'20260716T126000Z',
'20260716T120060Z',
]) {
const [article] = normalizeRegionalArticles({
articles: [
{
title: 'Fixture',
url: 'https://fixture.example/news',
seendate: date,
},
],
});
assert.equal(article.publishedAt, null, date);
assert.equal(article.title, 'Fixture');
}
for (const [input, expected] of [
['20240229T004500Z', '2024-02-29T00:45:00Z'],
['20260716T000000Z', '2026-07-16T00:00:00Z'],
['2026-07-16T12:00:00Z', '2026-07-16T12:00:00.000Z'],
]) {
assert.equal(
normalizeRegionalArticles({
articles: [
{
title: 'Fixture',
url: 'https://fixture.example/news',
seendate: input,
},
],
})[0].publishedAt,
expected,
);
}
});
+13 -4
View File
@@ -67,11 +67,20 @@ export function normalizeRegionalArticles(payload, limit = MAX_ARTICLES) {
seen.add(signature); seen.add(signature);
const rawDate = cleanText(row?.seendate, 32); const rawDate = cleanText(row?.seendate, 32);
const compactDate = /^(\d{8})T(\d{6})Z$/.exec(rawDate); const compactDate = /^(\d{8})T(\d{6})Z$/.exec(rawDate);
const publishedAt = compactDate const candidateDate = compactDate
? `${compactDate[1].slice(0, 4)}-${compactDate[1].slice(4, 6)}-${compactDate[1].slice(6, 8)}T${compactDate[2].slice(0, 2)}:${compactDate[2].slice(2, 4)}:${compactDate[2].slice(4, 6)}Z` ? `${compactDate[1].slice(0, 4)}-${compactDate[1].slice(4, 6)}-${compactDate[1].slice(6, 8)}T${compactDate[2].slice(0, 2)}:${compactDate[2].slice(2, 4)}:${compactDate[2].slice(4, 6)}Z`
: Number.isNaN(Date.parse(rawDate)) : rawDate;
? null const parsedMs = Date.parse(candidateDate);
: new Date(rawDate).toISOString(); const validDate =
Number.isFinite(parsedMs) &&
(!compactDate ||
new Date(parsedMs).toISOString().slice(0, 19) ===
candidateDate.slice(0, 19));
const publishedAt = validDate
? compactDate
? candidateDate
: new Date(parsedMs).toISOString()
: null;
articles.push({ articles.push({
title, title,
url, url,