finance-dashboard/lib/companyintel-refresh.js

68 lines
2.3 KiB
JavaScript
Raw Normal View History

2026-06-04 09:32:28 +00:00
// 公司研究同步節奏:首次進入必抓;之後等到「下次財報/公開」再更新
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 };
}