Place an archive host by its parallax where the archive gives no distance, so mu2 Sco b has its star

The archive leaves sy_dist blank for mu2 Sco and publishes sy_plx 6.31 +- 0.86 mas. The matcher
only reads the parallax as a second chance beside a finite sy_dist, and the archive-star fallback
needs sy_dist too, so mu2 Sco b had no host while Pipirima (HIP 82545, V 3.56, B2 IV) sat on the
map 0.4" from the archive's direction at 145.3 pc. The build.ts comment and the step report said
the 27 hostless planets had "no distance in either archive table"; mu2 Sco was the one whose row
has a parallax.

archiveDistancePc takes sy_dist, or 1000 / sy_plx where it is blank (158.5 pc here, within the
ratio test of Pipirima's 145.3). An ETL run from cache changes exoplanets.json alone: mu2 Sco b
now has host 82294, Pipirima, and 6 328 of 6 354 planets have a host (26 without, none of whose
rows has a distance or a parallax). hostDistancePc stays the archive's own blank.

Control: ignoring the parallax fails "takes the archive's parallax where it gives no distance,
and nothing from a parallax that is none" alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 16:37:30 +02:00
co-authored by Claude Opus 5.5
parent 734b048c9e
commit 5fb0d45623
4 changed files with 21 additions and 4 deletions
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { ARCHIVE_EPOCH, ARCHIVE_ID_BASE, archiveStarId, buildStarNameIndex, CATALOGUE_EPOCH, normalizeStarName, resolveHostStarId } from './host-star-matching'; import { ARCHIVE_EPOCH, ARCHIVE_ID_BASE, archiveDistancePc, archiveStarId, buildStarNameIndex, CATALOGUE_EPOCH, normalizeStarName, resolveHostStarId } from './host-star-matching';
import { propagateProperMotion, raDegDecDistanceToXyz } from './coordinates'; import { propagateProperMotion, raDegDecDistanceToXyz } from './coordinates';
import { StarRecord } from '../models/star.model'; import { StarRecord } from '../models/star.model';
@@ -207,6 +207,14 @@ describe('resolveHostStarId', () => {
expect(id).toBe(1); expect(id).toBe(1);
}); });
it("takes the archive's parallax where it gives no distance, and nothing from a parallax that is none", () => {
// mu2 Sco: sy_dist blank, sy_plx 6.31 mas; the catalogue has Pipirima at 145.3 pc.
expect(archiveDistancePc(undefined, 6.31)).toBeCloseTo(158.48, 2);
expect(archiveDistancePc(145.35, 6.31)).toBe(145.35);
expect(archiveDistancePc(undefined, 0)).toBeNaN();
expect(archiveDistancePc(undefined, undefined)).toBeNaN();
});
it('lets a named host resolve even with no usable distance', () => { it('lets a named host resolve even with no usable distance', () => {
const id = resolveHostStarId({ hostname: 'Sirius', raDeg: 101.3, decDeg: -16.7, distancePc: 0 }, FIXTURE_STARS); const id = resolveHostStarId({ hostname: 'Sirius', raDeg: 101.3, decDeg: -16.7, distancePc: 0 }, FIXTURE_STARS);
@@ -27,6 +27,15 @@ export interface HostStarQuery {
parallaxMas?: number; parallaxMas?: number;
} }
/**
* A host's distance as the archive gives it: `sy_dist`, or where that is blank, the inverse of its
* parallax `sy_plx`; `NaN` with neither. mu2 Sco has no `sy_dist` and a 6.31 mas parallax, which
* finds Pipirima (HIP 82545) 0.4″ from the archive's direction; left blank, its planet had no star.
*/
export function archiveDistancePc(distancePc: number | undefined, parallaxMas: number | undefined): number {
return distancePc ?? (parallaxMas !== undefined && parallaxMas > 0 ? 1000 / parallaxMas : Number.NaN);
}
function distancesAgree(a: number, b: number): boolean { function distancesAgree(a: number, b: number): boolean {
const [near, far] = a < b ? [a, b] : [b, a]; const [near, far] = a < b ? [a, b] : [b, a];
return (far - near) / near <= MERGE_DISTANCE_RATIO_TOLERANCE; return (far - near) / near <= MERGE_DISTANCE_RATIO_TOLERANCE;
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
import { writeFileSync } from 'node:fs'; import { writeFileSync } from 'node:fs';
import { propagateProperMotion, raDegDecDistanceToXyz } from '../../src/app/shared/astro/coordinates'; import { propagateProperMotion, raDegDecDistanceToXyz } from '../../src/app/shared/astro/coordinates';
import { ARCHIVE_EPOCH, archiveStarId, buildStarNameIndex, CATALOGUE_EPOCH, resolveHostStarId } from '../../src/app/shared/astro/host-star-matching'; import { ARCHIVE_EPOCH, archiveDistancePc, archiveStarId, buildStarNameIndex, CATALOGUE_EPOCH, resolveHostStarId } from '../../src/app/shared/astro/host-star-matching';
import { temperatureToColorIndex } from '../../src/app/shared/astro/spectral'; import { temperatureToColorIndex } from '../../src/app/shared/astro/spectral';
import { ExoplanetRecord } from '../../src/app/shared/models/exoplanet.model'; import { ExoplanetRecord } from '../../src/app/shared/models/exoplanet.model';
import { isDesignation } from '../../src/app/shared/models/star-catalog'; import { isDesignation } from '../../src/app/shared/models/star-catalog';
@@ -127,7 +127,7 @@ export async function fetchExoplanets(stars?: StarRecord[]): Promise<{ exoplanet
const host = (column: string) => parseOptionalNumber(row[column] || compositeRow?.[column]); const host = (column: string) => parseOptionalNumber(row[column] || compositeRow?.[column]);
const raDeg = host('ra') ?? Number.NaN; const raDeg = host('ra') ?? Number.NaN;
const decDeg = host('dec') ?? Number.NaN; const decDeg = host('dec') ?? Number.NaN;
const distancePc = host('sy_dist') ?? Number.NaN; const distancePc = archiveDistancePc(host('sy_dist'), host('sy_plx'));
const pmRaMasPerYear = host('sy_pmra'); const pmRaMasPerYear = host('sy_pmra');
const pmDecMasPerYear = host('sy_pmdec'); const pmDecMasPerYear = host('sy_pmdec');