Skip to content

Commit 63f264b

Browse files
authored
fix(updater): one log tag width for the whole run (#94)
Every console tag is [ + 8 characters + ] with a space on each side, the fund progress tag included, so the columns line up through a whole run (the progress tag was 10 wide next to the 12-wide [ done ] lines, request labels like [edgar ] were 9 to 11 wide).
1 parent bbbc405 commit 63f264b

1 file changed

Lines changed: 20 additions & 20 deletions

File tree

‎scripts/update-data.ts‎

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function outputFundLine(index: number, total: number, ticker: string, status: st
132132
field('sec', outputPercent(data.secYield ?? data.yields?.secYield ?? metrics.secYield)),
133133
field('wp', data.workplaceRaw),
134134
].filter(part => part !== '').join(' ');
135-
return `[ ${String(index).padStart(width)}/${String(total).padEnd(width)} ] ${outputClean(ticker).padEnd(5)} ${status.padEnd(9)}${detail ? ` ${detail}` : ''}${reason ? ` reason=${outputClean(reason)}` : ''}`;
135+
return `[ ${`${String(index).padStart(width)}/${total}`.padEnd(8)} ] ${outputClean(ticker).padEnd(5)} ${status.padEnd(9)}${detail ? ` ${detail}` : ''}${reason ? ` reason=${outputClean(reason)}` : ''}`;
136136
}
137137
function outputCreateReporter(root: URL | string, total: number) {
138138
let completed = 0;
@@ -848,7 +848,7 @@ async function fetchCatalog(config: UpdaterConfig): Promise<{ funds: CatalogFund
848848
const { text, via } = await fetchWisdomTreePage(WISDOMTREE_FUND_LIST_URL, WISDOMTREE_FUND_LIST_PROXY_URL, 'WisdomTree fund list', config, looksLikeFundList, 'application/json');
849849
return { funds: parseFundListJson(text), raw: text, rawName: 'fund-list', source: `WisdomTree official fund list${viaLabel(via)}` };
850850
} catch (error) {
851-
console.warn(`[ ${'catalog'.padEnd(9)}] ${error instanceof Error ? error.message : String(error)} - trying the product table`);
851+
console.warn(`[ ${'catalog'.padEnd(8)} ] ${error instanceof Error ? error.message : String(error)} - trying the product table`);
852852
}
853853
const { text, via } = await fetchWisdomTreePage(WISDOMTREE_CATALOG_URL, WISDOMTREE_CATALOG_PROXY_URL, 'WisdomTree product table', config, looksLikeCatalog);
854854
return { funds: parseCatalogMarkdown(text), raw: text, rawName: 'product-table', source: `WisdomTree official product table${viaLabel(via)}` };
@@ -1186,7 +1186,7 @@ export function resetSecState(): void {
11861186
/** SEC's ticker -> registrant CIK + series id table, loaded once per run (a failed load is asked again by the next fund). */
11871187
function loadFundTickerTable(config: UpdaterConfig): Promise<Map<string, NportSeriesRef>> {
11881188
if (!secState.funds) {
1189-
const loading = fetchJson(SEC_FUND_TICKERS_URL, '[edgar ] fund ticker table', config, secHeaders()).then((payload) => parseSecFundTickers(payload));
1189+
const loading = fetchJson(SEC_FUND_TICKERS_URL, '[ edgar ] fund ticker table', config, secHeaders()).then((payload) => parseSecFundTickers(payload));
11901190
secState.funds = loading;
11911191
loading.catch(() => { if (secState.funds === loading) secState.funds = null; });
11921192
}
@@ -1196,7 +1196,7 @@ function loadFundTickerTable(config: UpdaterConfig): Promise<Map<string, NportSe
11961196
/** SEC's company name -> ticker table (N-PORT rows and product-page top holdings), loaded once per run. */
11971197
function loadCompanyNames(config: UpdaterConfig): Promise<Map<string, string>> {
11981198
if (!secState.names) {
1199-
const loading = fetchJson(SEC_COMPANY_TICKERS_URL, '[edgar ] company ticker table', config, secHeaders()).then((payload) => parseSecCompanyNames(payload));
1199+
const loading = fetchJson(SEC_COMPANY_TICKERS_URL, '[ edgar ] company ticker table', config, secHeaders()).then((payload) => parseSecCompanyNames(payload));
12001200
secState.names = loading;
12011201
loading.catch(() => { if (secState.names === loading) secState.names = null; });
12021202
}
@@ -1692,7 +1692,7 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J
16921692
let productVia: 'direct' | 'proxy' | null = null;
16931693
if (!config.skipWisdomTree && fund.fundPage) {
16941694
try {
1695-
const page = await fetchWisdomTreePage(fund.fundPage, productProxyUrl(fund.fundPage), `[product ] ${fund.ticker}`, config, looksLikeProductPage);
1695+
const page = await fetchWisdomTreePage(fund.fundPage, productProxyUrl(fund.fundPage), `[ product ] ${fund.ticker}`, config, looksLikeProductPage);
16961696
productPageMarkdown = page.text;
16971697
productVia = page.via;
16981698
productSummary = parseProductPageSummary(productPageMarkdown);
@@ -1707,7 +1707,7 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J
17071707
} catch (error) {
17081708
productPageMarkdown = null;
17091709
productSummary = null;
1710-
outputNote(`[ ${'product'.padEnd(9)}] ${fund.ticker}: ${error instanceof Error ? error.message : String(error)}`);
1710+
outputNote(`[ ${'product'.padEnd(8)} ] ${fund.ticker}: ${error instanceof Error ? error.message : String(error)}`);
17111711
}
17121712
}
17131713
const productFetched = productSummary !== null;
@@ -1719,7 +1719,7 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J
17191719
const distributionsFailed = !productFetched || (partialPage && !productSummary!.sections.distributions);
17201720
if (partialPage) {
17211721
const lacking = [pricingFailed && 'NAV/price/premium', returnsFailed && 'returns', distributionsFailed && 'distributions'].filter(Boolean).join(', ');
1722-
outputNote(`[ ${'kept'.padEnd(9)}] ${fund.ticker}: product page came back partial, kept the published ${lacking}`);
1722+
outputNote(`[ ${'kept'.padEnd(8)} ] ${fund.ticker}: product page came back partial, kept the published ${lacking}`);
17231723
}
17241724
if (!productFetched || (partialPage && fund.secYield === null)) fund.secYield = fund.secYield ?? numberOrNull(previous.metrics?.secYield);
17251725
fund.cusip = fund.cusip || String(previous.cusip || '');
@@ -1742,11 +1742,11 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J
17421742
const published = { asOf: calendarIso(previousMeta?.holdings?.asOfDate), rows: topList ? 0 : numberOrNull(previousMeta?.holdings?.totalRows) ?? 0 };
17431743
const ref = (await loadFundTickerTable(config)).get(fund.ticker);
17441744
const read = ref
1745-
? await readNportHoldings({ ticker: fund.ticker, cik: ref.cik, seriesId: ref.seriesId }, published, (url, label) => fetchText(url, `[nport ] ${label}`, config, secHeaders()), { cache: secState.reader })
1745+
? await readNportHoldings({ ticker: fund.ticker, cik: ref.cik, seriesId: ref.seriesId }, published, (url, label) => fetchText(url, `[ nport ] ${label}`, config, secHeaders()), { cache: secState.reader })
17461746
: { problem: `${fund.ticker}: not in the SEC fund ticker table` };
1747-
if ('problem' in read) outputNote(`[ ${'nport'.padEnd(9)}] ${read.problem}`);
1747+
if ('problem' in read) outputNote(`[ ${'nport'.padEnd(8)} ] ${read.problem}`);
17481748
else {
1749-
const sheet = nportHoldingsSheet(read.filing, nportTickers(read.filing.positions, await loadCompanyNames(config).catch(() => null), await loadUsListings((url, label) => fetchText(url, `[nport ] ${label}`, config, secHeaders()))));
1749+
const sheet = nportHoldingsSheet(read.filing, nportTickers(read.filing.positions, await loadCompanyNames(config).catch(() => null), await loadUsListings((url, label) => fetchText(url, `[ nport ] ${label}`, config, secHeaders()))));
17501750
holdingsRows = sheet.rows;
17511751
holdingsHeaders = sheet.headers;
17521752
holdingsFresh = true;
@@ -1756,13 +1756,13 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J
17561756
holdingsSource = `SEC EDGAR Form N-PORT-P (accession ${read.candidate.accession}, report period ${read.filing.repPdDate || 'n/a'})`;
17571757
}
17581758
} catch (error) {
1759-
outputNote(`[ ${'nport'.padEnd(9)}] ${fund.ticker}: ${error instanceof Error ? error.message : String(error)}`);
1759+
outputNote(`[ ${'nport'.padEnd(8)} ] ${fund.ticker}: ${error instanceof Error ? error.message : String(error)}`);
17601760
}
17611761
}
17621762

17631763
if (!holdingsRows.length && config.edgarFallback && productPageMarkdown) {
17641764
const top = parseTopHoldingsMarkdown(productPageMarkdown, fund.ticker, (await loadCompanyNames(config).catch(() => null)) || new Map());
1765-
if (top.rejected) outputNote(`[ ${'holdings'.padEnd(9)}] ${fund.ticker}: product page top holdings rejected: ${top.rejected}`);
1765+
if (top.rejected) outputNote(`[ ${'holdings'.padEnd(8)} ] ${fund.ticker}: product page top holdings rejected: ${top.rejected}`);
17661766
if (top.rows.length && (!previousMeta?.holdings?.totalRows || /^WisdomTree product page/.test(String(previousMeta.holdings.source || '')))) {
17671767
holdingsRows = top.rows;
17681768
holdingsFresh = true;
@@ -1779,7 +1779,7 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J
17791779
// summary, weights adding up to 581 and 600): it is not republished, the fund has no holdings until a valid source exists.
17801780
const junk = previousSheet.length ? holdingsGuard(previousHeaders, previousSheet, null, HOLDINGS_GUARD_RELAX[fund.ticker] ?? HOLDINGS_GUARD_RELAX['*'] ?? [], holdingsWeightCount(previousHeaders, previousSheet)) : null;
17811781
if (junk) {
1782-
console.warn(`[ ${'holdings'.padEnd(9)}] ${fund.ticker}: published holdings dropped (${junk}); none until a valid source exists`);
1782+
console.warn(`[ ${'holdings'.padEnd(8)} ] ${fund.ticker}: published holdings dropped (${junk}); none until a valid source exists`);
17831783
} else {
17841784
holdingsRows = previousSheet;
17851785
holdingsHeaders = previousHeaders;
@@ -1795,7 +1795,7 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J
17951795
if (!config.skipYahoo) {
17961796
try {
17971797
const query = yahooChartQuery(config.historyRange);
1798-
const payload = await fetchJson(`${YAHOO_CHART_URL}/${encodeURIComponent(fund.ticker)}?${query.toString()}`, `[chart ] ${fund.ticker}`, config, { 'User-Agent': 'Mozilla/5.0' });
1798+
const payload = await fetchJson(`${YAHOO_CHART_URL}/${encodeURIComponent(fund.ticker)}?${query.toString()}`, `[ chart ] ${fund.ticker}`, config, { 'User-Agent': 'Mozilla/5.0' });
17991799
const fetched = parseChart(payload);
18001800
// A HISTORY_RANGE window never shrinks the published history (and with it 3y, 5y, 10y and since inception); the published
18011801
// distributions table is [Ex-Date, ..., Total Distribution], the window merge reads [date, amount]
@@ -1805,7 +1805,7 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J
18051805
days = chart.days;
18061806
fresh = fresh || days.length > 0;
18071807
} catch (error) {
1808-
outputNote(`[ ${'chart'.padEnd(9)}] ${fund.ticker}: ${error instanceof Error ? error.message : String(error)}`);
1808+
outputNote(`[ ${'chart'.padEnd(8)} ] ${fund.ticker}: ${error instanceof Error ? error.message : String(error)}`);
18091809
}
18101810
}
18111811
// A new fund that no source answered publishes nothing (never an empty meta.json reported updated): it fails and keeps a catalog-only row.
@@ -2377,7 +2377,7 @@ export async function runUpdate(config: UpdaterConfig, deadlineMs: number = SOFT
23772377
await writeFile(new URL(`${fetched.rawName}-${new Date().toISOString().slice(0, 10)}.${fetched.rawName === 'fund-list' ? 'json' : 'md'}`, raw), fetched.raw, 'utf8');
23782378
}
23792379
} catch (error) {
2380-
console.warn(`[ ${'catalog'.padEnd(9)}] ${error instanceof Error ? error.message : String(error)} - keeping the published feed`);
2380+
console.warn(`[ ${'catalog'.padEnd(8)} ] ${error instanceof Error ? error.message : String(error)} - keeping the published feed`);
23812381
}
23822382
}
23832383
// The live catalog (null when only the published feed is known): a catalog-only row it no longer lists is dropped, never retried.
@@ -2387,15 +2387,15 @@ export async function runUpdate(config: UpdaterConfig, deadlineMs: number = SOFT
23872387

23882388
const universe = [...catalog.values()].sort((a, b) => a.ticker.localeCompare(b.ticker));
23892389
if (!universe.length) throw new Error('No catalog rows available. Run this where www.wisdomtree.com is reachable or seed api/wisdomtree/index.json first.');
2390-
console.log(`[ ${'catalog'.padEnd(9)}] ${universe.length} WisdomTree ETFs (${catalogSource})`);
2390+
console.log(`[ ${'catalog'.padEnd(8)} ] ${universe.length} WisdomTree ETFs (${catalogSource})`);
23912391
const noteLines: string[] = [];
23922392
if (live && previous.size) {
23932393
const listed = new Set(live);
23942394
// A catalog that lost more than half of the known funds is treated as truncated: nothing is reported then.
23952395
const dropped = Array.from(previous.keys()).filter((ticker) => !listed.has(ticker) && previous.get(ticker)?.dataFile).sort();
23962396
if (dropped.length && listed.size * 2 >= previous.size) noteLines.push(`DROPPED FUNDS: ${dropped.join(', ')} (no longer in the live catalog; their published data is kept)`);
23972397
}
2398-
for (const line of noteLines) console.log(`[ ${'catalog'.padEnd(9)}] ${line}`);
2398+
for (const line of noteLines) console.log(`[ ${'catalog'.padEnd(8)} ] ${line}`);
23992399
// Q27: a TICKERS entry outside the live catalog and the published funds stops the run before any fund request
24002400
assertKnownTickers(config.tickers ? Array.from(config.tickers) : null, knownTickers(live, Array.from(previous.values())), 'the WisdomTree catalog or published feed');
24012401

@@ -2464,14 +2464,14 @@ export async function runUpdate(config: UpdaterConfig, deadlineMs: number = SOFT
24642464
for (const line of newFunds) console.log(line);
24652465
// A TICKERS run never touches the cursor; a full run resets it.
24662466
if (!config.tickers) await writeIfChanged(STATE_FILE, { cursor: config.maxFetches > 0 ? lastDispatched ?? (cursor || null) : null, savedAt: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z') });
2467-
if (stoppedAtDeadline) console.warn(`[ ${'deadline'.padEnd(9)}] soft deadline reached; ${queue.length} funds were not taken this run (the index was still written)`);
2467+
if (stoppedAtDeadline) console.warn(`[ ${'deadline'.padEnd(8)} ] soft deadline reached; ${queue.length} funds were not taken this run (the index was still written)`);
24682468
console.log(output.summary(startedAt));
24692469
console.log(outputCounts(counts.funds, counts.holdings, counts.history));
24702470
if (process.env.GITHUB_STEP_SUMMARY) await appendFile(process.env.GITHUB_STEP_SUMMARY, `### WisdomTree data update\n\n- updated: ${results.length}\n- failed: ${failures}\n- counts: ${counts.funds} funds / ${counts.holdings.toLocaleString('en-US')} holdings rows / ${counts.history.toLocaleString('en-US')} history rows\n${noteLines.map((line) => `- ${line}\n`).join('')}${stoppedAtDeadline ? '- soft deadline reached before every fund was taken\n' : ''}${newFunds.length ? `\n### NEW FUNDS\n\n${newFunds.join('\n')}\n` : ''}`, 'utf8');
24712471
// Every fund failed (or no fund got fresh data from any enabled source): the run is red.
24722472
const sourcesEnabled = !config.skipWisdomTree || !config.skipYahoo || config.edgarFallback;
24732473
if ((processed > 0 && failures === processed) || (sourcesEnabled && results.length > 0 && fundsWithFreshData === 0)) {
2474-
console.error(`[ ${'failed'.padEnd(9)}] no fund was updated from a live source`);
2474+
console.error(`[ ${'failed'.padEnd(8)} ] no fund was updated from a live source`);
24752475
process.exitCode = 1;
24762476
}
24772477
}

0 commit comments

Comments
 (0)