Number the stars the archive places after their host's name, so a refresh keeps each one's id
4c8e4a0 numbered each star it adds from the Exoplanet Archive by its place among the unmatched
hosts, in pl_name order, and the refresh workflow re-queries the archive every Monday. One host
added or dropped ahead of another renumbers it: in the review, removing a single planet row
renamed 3 276 of the 3 277 ids, and a bookmark kept on Kepler-186 (1070001620) opened Kepler-1860
under Kepler-186's stored name. HYG's and Gaia's ids do not move between refreshes; the merge's own
comment says ids are meant to hold.
archiveStarId (host-star-matching.ts, beside the matcher the ETL already imports from there) hashes
the host name with FNV-1a into the 3.7 million ids between 1 070 000 000 and 2^30, and moves a name
whose id is taken to the next free one. The added stars are sorted by id before they are appended,
so the published list stays in id order. Measured: all 4 237 archive-hosted planets change host id
once (Kepler-186 is now 1073671518), none of the others; one of the 3 277 names was probed past a
collision, and one new id falls in the old 1070000000-1070003276 range, so a bookmark saved on that
old id would open the wrong star once. Dropping the same row from a copy of the cached answer and
rerunning the exoplanet step in a scratch copy of the ETL now leaves 3 276 of 3 276 ids unchanged.
The validators pass: no duplicate id, all under 2^30.
Controls, each failing its named test: the id taken from the order of arrival, and no probing past
a taken id (1 of 801 each).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildStarNameIndex, normalizeStarName, resolveHostStarId } from './host-star-matching';
|
||||
import { ARCHIVE_ID_BASE, archiveStarId, buildStarNameIndex, normalizeStarName, resolveHostStarId } from './host-star-matching';
|
||||
import { propagateProperMotion, raDegDecDistanceToXyz } from './coordinates';
|
||||
import { StarRecord } from '../models/star.model';
|
||||
|
||||
@@ -214,3 +214,19 @@ describe('resolveHostStarId', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('archiveStarId', () => {
|
||||
it('gives a host the same id whatever else the archive holds, inside the range left for it', () => {
|
||||
const kepler186 = archiveStarId('Kepler-186', new Set());
|
||||
const others = new Set(['1RXS J160929.1-210524', 'Kepler-1860', 'Kepler-452', 'TOI-700'].map((name) => archiveStarId(name, new Set())));
|
||||
expect(archiveStarId('Kepler-186', others)).toBe(kepler186);
|
||||
expect(others.has(kepler186)).toBe(false);
|
||||
expect(kepler186).toBeGreaterThanOrEqual(ARCHIVE_ID_BASE);
|
||||
expect(kepler186).toBeLessThan(2 ** 30);
|
||||
});
|
||||
|
||||
it('moves a host whose id is taken to the next free one', () => {
|
||||
const kepler186 = archiveStarId('Kepler-186', new Set());
|
||||
expect(archiveStarId('Kepler-186', new Set([kepler186, kepler186 + 1]))).toBe(kepler186 + 2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -171,3 +171,30 @@ export function resolveHostStarId(
|
||||
|
||||
return best ? best.id : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the ids of the stars only the archive places begin: past Gaia's two ranges, and under
|
||||
* the 2^30 `validateStars` holds every id to — which leaves 3.7 million.
|
||||
*/
|
||||
export const ARCHIVE_ID_BASE = 1_070_000_000;
|
||||
const ARCHIVE_ID_RANGE = 2 ** 30 - ARCHIVE_ID_BASE;
|
||||
|
||||
/**
|
||||
* The id of a star the ETL places from the archive, from its host's name rather than from its
|
||||
* place in the answer. Numbered in pl_name order, a refresh that added or dropped one host
|
||||
* renumbered every host after it — one row dropped renamed 3 276 of 3 277 ids, and a bookmark kept
|
||||
* on Kepler-186 opened Kepler-1860 — while HYG's and Gaia's ids hold. FNV-1a over the name, into
|
||||
* the range above; a name whose id is taken takes the next free one, the one case a refresh can
|
||||
* still move, and only between the two names that collided.
|
||||
*/
|
||||
export function archiveStarId(hostname: string, taken: ReadonlySet<number>): number {
|
||||
let hash = 0x811c9dc5;
|
||||
for (let i = 0; i < hostname.length; i++) {
|
||||
hash = Math.imul(hash ^ hostname.charCodeAt(i), 0x01000193) >>> 0;
|
||||
}
|
||||
let offset = hash % ARCHIVE_ID_RANGE;
|
||||
while (taken.has(ARCHIVE_ID_BASE + offset)) {
|
||||
offset = (offset + 1) % ARCHIVE_ID_RANGE;
|
||||
}
|
||||
return ARCHIVE_ID_BASE + offset;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user