Carry the archive's positions back from Gaia DR2's J2015.5, where it publishes them, not J2016

fetchExoplanets placed each star it adds from the Exoplanet Archive by carrying the archive's
position back sixteen years, and 4c8e4a0 said the archive publishes at Gaia's J2016. It does not:
its positions are Gaia DR2's at J2015.5 although it names the DR3 source. Barnard's star in the
archive, 269.4486144, 4.7379808, equals DR2 4472832130942575872 at J2015.5 to 1e-7°, and sits 5.2″
from DR3's J2016 place; the review found 66 of the 67 archive-placed stars moving over 100 mas a
year within 0.21 mas of their DR2 position and none within 1 mas of DR3's. The matcher's comment
made the same claim of HD 133131 and TOI-2459, whose positions are DR2's too.

host-star-matching.ts now exports CATALOGUE_EPOCH and ARCHIVE_EPOCH = 2015.5, the matcher carries a
query back by their difference, and fetchExoplanets uses the same two, so the epoch is written
once. From cache: only the 3 277 archive-placed stars move, 2 287 of them by more than a
milliarcsecond, 74 by more than 50 and TOI-2406 most, by 203 mas, half a year of its 405 mas/yr;
no planet changes host. Invisible at map scale; the point is that the constant and its comments
now say what the archive does.

Control: ARCHIVE_EPOCH back to 2016.0 fails "carries Barnard's star from the archive's position to
where Gaia DR3's goes, to a few milliarcseconds" (1 of 802), which puts the two 5.2″ apart. The
GJ 15 A matching fixture is now built at J2015.5 too. fetchExoplanets' use of the constant has no
test of its own; the ETL has no test harness.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 21:20:26 +02:00
co-authored by Claude Opus 5.5
parent 62f81f2c43
commit 6d81c45f45
4 changed files with 33 additions and 20 deletions
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { ARCHIVE_ID_BASE, archiveStarId, buildStarNameIndex, normalizeStarName, resolveHostStarId } from './host-star-matching';
import { ARCHIVE_EPOCH, ARCHIVE_ID_BASE, archiveStarId, buildStarNameIndex, CATALOGUE_EPOCH, normalizeStarName, resolveHostStarId } from './host-star-matching';
import { propagateProperMotion, raDegDecDistanceToXyz } from './coordinates';
import { StarRecord } from '../models/star.model';
@@ -81,13 +81,13 @@ describe('resolveHostStarId', () => {
expect(id).toBe(80);
});
// GJ 15 A's archive row sits at J2016, 46″ along its proper motion from Groombridge 34's
// GJ 15 A's archive row sits at J2015.5, 45″ along its proper motion from Groombridge 34's
// J2000 place — and only 16″ from an unrelated Gaia entry. Nearest-to-the-published-point
// picks the interloper; carrying the query back the sixteen years must put the planets on
// the star that actually moved there.
// picks the interloper; carrying the query back the fifteen and a half years must put the
// planets on the star that actually moved there.
it('picks the star the proper motion says the query is, not the entry nearest the published point', () => {
const primary = star(90, 'Groombridge 34', 4.595364, 44.022955, 3.562);
const published = propagateProperMotion(4.595364, 44.022955, 2891.5, 411.9, 16);
const published = propagateProperMotion(4.595364, 44.022955, 2891.5, 411.9, 15.5);
const interloper = star(91, 'Gaia DR3 385334196532776576', published.raDeg, published.decDeg + 16 / 3600, 3.563);
const id = resolveHostStarId(
@@ -215,6 +215,17 @@ describe('resolveHostStarId', () => {
});
});
describe('the archive epoch', () => {
it("carries Barnard's star from the archive's position to where Gaia DR3's goes, to a few milliarcseconds", () => {
// The archive publishes Gaia DR2's J2015.5 position and motion; DR3's is at J2016.
const archive = propagateProperMotion(269.4486144, 4.7379808, -802.803, 10362.5, CATALOGUE_EPOCH - ARCHIVE_EPOCH);
const dr3 = propagateProperMotion(269.44850252543836, 4.739420051112412, -801.5509783684709, 10362.394206546573, CATALOGUE_EPOCH - 2016);
const [a, b] = [archive, dr3].map(({ raDeg, decDeg }) => raDegDecDistanceToXyz(raDeg, decDeg, 1));
const separationArcsec = (Math.acos(Math.min(1, a.x * b.x + a.y * b.y + a.z * b.z)) * 180 * 3600) / Math.PI;
expect(separationArcsec).toBeLessThan(0.02);
});
});
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());
+11 -9
View File
@@ -80,15 +80,17 @@ export const HOST_TRANSVERSE_TOLERANCE_PC = 0.01;
/**
* The archive does not say which epoch a row's position is for, and they are demonstrably
* mixed: alf Tau and GJ 273 publish J2000 (the raw position sits under an arcsecond from our
* star, and carrying it back doubles the error), HD 133131 and TOI-2459 publish Gaia's J2016
* (the carried-back position lands to 0.1″). So every query is tried at both ends — as
* published, and carried back sixteen years with the archive's own proper motion — and a star
* is judged on whichever is closer. Guessing one epoch picks companions: assume J2016 and
* Aldebaran's planet lands on Gl 171.1B, assume J2000 and GJ 15 A's land on a Gaia entry
* 15.9″ out.
* star, and carrying it back doubles the error), HD 133131 and TOI-2459 publish Gaia DR2's J2015.5
* (the carried-back position lands to 0.1″). DR2's, not DR3's J2016, although the archive names
* the DR3 source: Barnard's star, Teegarden's Star, TRAPPIST-1 and 66 of the 67 archive-placed
* stars moving over 100 mas a year equal their DR2 position to a milliarcsecond and none their
* DR3 one. So every query is tried at both ends — as published, and carried back fifteen and a
* half years with the archive's own proper motion — and a star is judged on whichever is closer.
* Guessing one epoch picks companions: assume the later one and Aldebaran's planet lands on Gl
* 171.1B, assume J2000 and GJ 15 A's land on a Gaia entry 15.9″ out.
*/
const CATALOGUE_EPOCH = 2000.0;
const ARCHIVE_LATEST_EPOCH = 2016.0;
export const CATALOGUE_EPOCH = 2000.0;
export const ARCHIVE_EPOCH = 2015.5;
function knownMotion(masPerYear: number | undefined): number {
return Number.isFinite(masPerYear) ? (masPerYear as number) : 0;
@@ -140,7 +142,7 @@ export function resolveHostStarId(
// star would pass the direction test and the last one in array order would win.
knownMotion(query.pmRaMasPerYear),
knownMotion(query.pmDecMasPerYear),
CATALOGUE_EPOCH - ARCHIVE_LATEST_EPOCH
CATALOGUE_EPOCH - ARCHIVE_EPOCH
);
const carried = raDegDecDistanceToXyz(carriedBack.raDeg, carriedBack.decDeg, 1);
Binary file not shown.