finance-dashboard/lib/companyintel-refresh.js

68 lines
2.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 公司研究同步節奏:首次進入必抓;之後等到「下次財報/公開」再更新
import { fetchEarningsEvents } from './calendar.js';
function addDaysISO(base, days) {
const d = new Date(base + 'T12:00:00Z');
d.setUTCDate(d.getUTCDate() + days);
return d.toISOString().slice(0, 10);
}
function todayISO() {
return new Date().toISOString().slice(0, 10);
}
/** 下次允許重新抓取的日期 = 下一個財報日(尚無則約一季後) */
export async function computeNextPublicRefresh(symbol) {
symbol = String(symbol || '').trim().toUpperCase();
const today = todayISO();
const end = addDaysISO(today, 200);
try {
const events = await fetchEarningsEvents(today, end, [symbol]);
const upcoming = (events || [])
.filter(e => e.date && e.date > today)
.sort((a, b) => a.date.localeCompare(b.date));
if (upcoming.length) {
const next = upcoming[0];
return {
nextRefreshAfter: next.date,
nextPublicLabel: next.title || `${symbol} 財報`,
nextPublicDate: next.date,
};
}
} catch { /* fallback */ }
const fallback = addDaysISO(today, 92);
return {
nextRefreshAfter: fallback,
nextPublicLabel: '約一季後(暫無財報日曆)',
nextPublicDate: fallback,
};
}
export function intelRefreshPolicy(enrichedRow) {
const data = enrichedRow?.data || {};
const lastSyncAt = data.lastSyncAt || enrichedRow?.updatedAt || null;
const nextRefreshAfter = data.nextRefreshAfter || null;
const today = todayISO();
const neverSynced = !lastSyncAt;
const due = nextRefreshAfter ? today >= nextRefreshAfter : false;
const needsSync = neverSynced || due;
let skipReason = null;
if (!needsSync && nextRefreshAfter) {
skipReason = `已同步;下次更新:${nextRefreshAfter}${data.nextPublicLabel || '下次財報/公開'}`;
} else if (!needsSync) {
skipReason = '已同步';
}
return {
needsSync,
lastSyncAt: lastSyncAt ? new Date(lastSyncAt).toISOString() : null,
nextRefreshAfter,
nextPublicLabel: data.nextPublicLabel || null,
skipReason,
};
}
export function shouldRunIntelSync(enrichedRow, { force = false } = {}) {
if (force) return { run: true, reason: 'force' };
const p = intelRefreshPolicy(enrichedRow);
return { run: p.needsSync, reason: p.needsSync ? 'first_or_due' : 'wait_public', ...p };
}