diff --git a/tools/etl/fetchDeepSky.ts b/tools/etl/fetchDeepSky.ts index f2d4caa..4ba285b 100644 --- a/tools/etl/fetchDeepSky.ts +++ b/tools/etl/fetchDeepSky.ts @@ -126,7 +126,10 @@ export async function fetchDeepSky(): Promise { // Brightest first, so a consumer taking a prefix gets the most prominent objects. Objects // with no measured magnitude sort last rather than being treated as infinitely bright. - records.sort((a, b) => (a.magnitude ?? Infinity) - (b.magnitude ?? Infinity) || a.id.localeCompare(b.id)); + // The tie-break compares code points rather than localeCompare: locale collation depends on + // the ICU build of whichever Node runs the ETL, and a scheduled re-run must not reorder the + // file just because the runner's ICU disagrees with the machine that wrote it last. + records.sort((a, b) => (a.magnitude ?? Infinity) - (b.magnitude ?? Infinity) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); ensureDataDir(); writeFileSync(dataPath('deepsky.json'), JSON.stringify(records)); diff --git a/tools/etl/fetchExoplanets.ts b/tools/etl/fetchExoplanets.ts index ade5d34..dac2b21 100644 --- a/tools/etl/fetchExoplanets.ts +++ b/tools/etl/fetchExoplanets.ts @@ -25,7 +25,11 @@ const TAP_COLUMNS = [ 'st_mass', 'disc_year' ].join(','); -const TAP_QUERY = `select+${TAP_COLUMNS}+from+ps+where+default_flag=1&format=csv`; +// Ordered explicitly: without it the archive is free to return rows in any order, and a +// scheduled re-run of the ETL would then rewrite exoplanets.json — and commit a diff — when +// nothing was actually published. pl_name is unique among default_flag=1 rows, so the order +// is total and the output is a pure function of the archive's content. +const TAP_QUERY = `select+${TAP_COLUMNS}+from+ps+where+default_flag=1+order+by+pl_name&format=csv`; const TAP_URL = `${TAP_BASE_URL}?query=${TAP_QUERY}`; // A host star match must be within this many parsecs of the catalog position to be