The Gliese fold (030447b) kept HYG's magnitude, band and colour over the Gaia entry's, putting CNS3's V at Gaia's distance. Of the 49 folded, 15 lost a measured BP-RP and 6 their temperature and radius (Gl 700.1C, Gl 632.2B, GJ 9800B, Gl 734B, Gl 323B, GJ 3605), and GJ 4285, V 11.45 where SIMBAD has G 13.05, was drawn five times too luminous. A folded star now keeps the photometry of the entry already there; HYG's name, id and type still come with the fold. Published catalogue, before -> after: GJ 4285 V 11.45, 3 850 K, 0.491 R☉, 4.76e-2 L☉ -> G 13.05, BP-RP 2.74, 3 291 K, 0.297 R☉, 9.29e-3 L☉; GJ 3207 0.574 -> 0.278 R☉; Gl 700.1C no temperature, 4.54 L☉ -> 5 155 K, 1.214 R☉, 0.937 L☉; Gl 632.2B -> 9 044 K, 0.018 R☉; Gl 734B -> 3 360 K, 0.381 R☉. The fold also looked its target up by name, so it saw only Gaia entries still bare. Where a Hipparcos row of HYG's had already taken the source, the Gliese-only row of the same star stayed beside it and the check counted none: Gl 251 at 5.76 pc beside HD 265866 (the host of GJ 251 b and c) at 5.58, Gl 422 beside HD 304043. Gaia entries now carry their DR3 designation through the merge (ETL only; the assets do not store it) and the fold looks it up; into an entry a HYG star already describes it folds only where the two V agree within 0.5, so a companion SIMBAD gives its primary's source stays. 14 more rows fold, 63 in all, each within 0.47 of the star it joins: Gl 251, Gl 422, Gl 162, Gl 794, Gl 225.2C, Gl 905.2B, GJ 3232, GJ 4046, GJ 9490C, GJ 9608, 69 Tau Oph (Gl 700.1A) and the second rows of HD 65277, HD 120237 and HD 336196. Stars within 10 pc 367 -> 366, within 25 pc 5 479 -> 5 468; distances without an error 346 -> 332; HYG survivors 11 478 -> 11 465. validateMerge's count of Gliese rows beside their source took SIMBAD's map from the function the fold takes it from, so a slip in the map turned both off: with it empty, GJ 2097 and GJ 4285 were back inside 10 pc and the ETL passed. It now also asks for GJ 2097 and GJ 4285 beyond 20 pc and for no Gl 251 or Gl 422 by name, and glieseGaiaDesignations refuses fewer than 3 200 HYG rows matched to SIMBAD (3 352 of HYG's 3 801 measured).4d47896left 22 naked-eye HYG rows out as having no distance anywhere; two had one. HD 45951 (K0 III, V 6.2) was on the map only as "Gaia DR3 3369454521490604416", HYG's declination being 31.7' out, and θ¹ Ori A (HD 37020, HIP 26220) sits 0.1" from Gaia DR3 3017364132050194688, 2.643 ± 0.072 mas, which the magnitude test refused because HYG gives V 4.98 against G 6.63. One cached SIMBAD query now names the Gaia DR3 source of each naked-eye HYG row with no distance (199 of 206 HD numbers), and a row nothing else places takes that source from the bright list, at its position: HD 45951 at 112.0 pc, under its name, and θ¹ Ori A at 378.4 pc. HD 45951 joins the required names. The twenty still left out are named in build.ts; none has a bright source with a parallax five times its error within a minute of arc nor under its SIMBAD name. Naked-eye stars 8 898 -> 8 899; 455 532 -> 455 519 stars. The fold and the identity lookup are tested in star-merge.spec.ts. Guarded mutants (1 of 833 failed unless noted): - the Gliese row's photometry kept: caught by "...in Gaia's photometry" (and the Hipparcos-row fold test, 2 failed) - combine dropping the designation: caught by "folds one into a Gaia entry a HYG star of the same brightness..." - folding whatever the brightness; tolerance 2 magnitudes: caught by "leaves a star with a Hipparcos error alone..." - folding into bare entries only: caught by the Hipparcos-row fold test (and the leave-alone test, 2 failed) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
82 lines
3.9 KiB
TypeScript
82 lines
3.9 KiB
TypeScript
import { createHash } from 'node:crypto';
|
||
|
||
import { parseCsvObjects } from '../lib/csv';
|
||
import { fetchTextCached } from '../lib/http';
|
||
|
||
/**
|
||
* SIMBAD, via its TAP service, for one thing: which Gaia DR3 source a star is.
|
||
*
|
||
* The merge decides identity on the sky, and for the HYG rows Gliese places without Hipparcos
|
||
* astrometry the sky is not enough: their positions are off by up to minutes of arc, their
|
||
* distances are photometric and their proper motions sometimes wrong. SIMBAD's cross-identifications
|
||
* were made star by star, and name the answer.
|
||
*/
|
||
|
||
const SIMBAD_TAP_URL = 'https://simbad.cds.unistra.fr/simbad/sim-tap/sync';
|
||
|
||
/** SIMBAD gave 4 868 GJ designations a Gaia DR3 one on 2026-09-30. */
|
||
const MIN_GJ_DESIGNATIONS = 4_000;
|
||
|
||
/**
|
||
* SIMBAD's Gaia DR3 designation for every object it knows by a Gliese-Jahreiss number, keyed by
|
||
* that number with its spaces collapsed, as SIMBAD writes it: "GJ 734 B", not HYG's "Gl 734B".
|
||
* One query for the whole catalogue rather than for the rows that need it, so the answer, and its
|
||
* cache, do not change when those rows do.
|
||
*/
|
||
export async function fetchGaiaDesignationsByGj(): Promise<Map<string, string>> {
|
||
const designations = await fetchDesignations(
|
||
"select i1.id as gj, i2.id as gaia from ident as i1 join ident as i2 on i1.oidref = i2.oidref where i1.id like 'GJ %' and i2.id like 'Gaia DR3 %' order by gj, gaia",
|
||
'gj',
|
||
'simbad-gj-gaia'
|
||
);
|
||
if (designations.size < MIN_GJ_DESIGNATIONS) {
|
||
throw new Error(`SIMBAD gave ${designations.size} GJ stars a Gaia DR3 designation, not the ~4 868 it holds; delete tools/etl/.cache/simbad-gj-gaia-*.csv once it answers properly`);
|
||
}
|
||
return designations;
|
||
}
|
||
|
||
/** Of the 206 HD numbers asked for below, SIMBAD gave 199 a Gaia DR3 designation on 2026-09-30. */
|
||
const MIN_HD_DESIGNATION_SHARE = 0.9;
|
||
|
||
/**
|
||
* SIMBAD's Gaia DR3 designation for each of `hdNumbers`, keyed "HD 45951": for the naked-eye HYG
|
||
* rows with no distance, which HYG's own position does not always lead to — HD 45951's declination
|
||
* there is 31.7′ off SIMBAD's. Asked by number, since the HD catalogue whole would be 360 000 rows;
|
||
* the numbers are HYG's, so the answer and its cache change only when HYG does.
|
||
*/
|
||
export async function fetchGaiaDesignationsByHd(hdNumbers: readonly string[]): Promise<Map<string, string>> {
|
||
const list = [...hdNumbers].sort().map((hd) => `'HD ${hd}'`).join(', ');
|
||
const designations = await fetchDesignations(
|
||
`select i1.id as hd, i2.id as gaia from ident as i1 join ident as i2 on i1.oidref = i2.oidref where i1.id in (${list}) and i2.id like 'Gaia DR3 %' order by hd, gaia`,
|
||
'hd',
|
||
'simbad-hd-gaia'
|
||
);
|
||
if (designations.size < hdNumbers.length * MIN_HD_DESIGNATION_SHARE) {
|
||
throw new Error(`SIMBAD gave ${designations.size} of ${hdNumbers.length} HD stars a Gaia DR3 designation; delete tools/etl/.cache/simbad-hd-gaia-*.csv once it answers properly`);
|
||
}
|
||
return designations;
|
||
}
|
||
|
||
/** The identifier in `column` → Gaia DR3 designation pairs a query answers, SIMBAD's padding collapsed ("HD 45951"). */
|
||
async function fetchDesignations(query: string, column: string, cachePrefix: string): Promise<Map<string, string>> {
|
||
const url = `${SIMBAD_TAP_URL}?REQUEST=doQuery&LANG=ADQL&FORMAT=csv&QUERY=${encodeURIComponent(query)}`;
|
||
const rows = parseCsvObjects(await fetchTextCached(url, `${cachePrefix}-${createHash('sha1').update(url).digest('hex').slice(0, 8)}.csv`));
|
||
|
||
const designations = new Map<string, string>();
|
||
for (const row of rows) {
|
||
const identifier = collapse(row[column] ?? '');
|
||
// An object SIMBAD gives two Gaia DR3 sources is one it has not resolved; neither is the star.
|
||
designations.set(identifier, designations.has(identifier) ? '' : collapse(row['gaia'] ?? ''));
|
||
}
|
||
for (const [identifier, designation] of designations) {
|
||
if (!designation) {
|
||
designations.delete(identifier);
|
||
}
|
||
}
|
||
return designations;
|
||
}
|
||
|
||
function collapse(identifier: string): string {
|
||
return identifier.replace(/\s+/g, ' ').trim();
|
||
}
|