Implement RPC contract validation baseline
This commit is contained in:
192
apps/desktop/src/dataRefresh.ts
Normal file
192
apps/desktop/src/dataRefresh.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Background data refresh service
|
||||
* Updates prices, filings, and earnings data on a schedule
|
||||
*/
|
||||
|
||||
import type { BrowserWindow } from "electron";
|
||||
import type { Db } from "@mosaiciq/shared/db";
|
||||
import { fetchQuote, fetchFilings } from "@mosaiciq/shared/data";
|
||||
import { upsertCompany } from "@mosaiciq/shared/db";
|
||||
import { listFilings } from "@mosaiciq/shared/db";
|
||||
|
||||
export interface DataRefreshOptions {
|
||||
priceInterval?: number; // minutes
|
||||
filingInterval?: number; // minutes
|
||||
earningsInterval?: number; // minutes
|
||||
}
|
||||
|
||||
export function startDataRefresh(
|
||||
db: Db,
|
||||
mainWindow: BrowserWindow,
|
||||
options: DataRefreshOptions = {}
|
||||
): () => void {
|
||||
const {
|
||||
priceInterval = 5,
|
||||
filingInterval = 60,
|
||||
earningsInterval = 1440, // daily
|
||||
} = options;
|
||||
|
||||
let priceTimeout: NodeJS.Timeout | null = null;
|
||||
let filingTimeout: NodeJS.Timeout | null = null;
|
||||
let earningsTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
// Get all portfolio holdings
|
||||
function getPortfolioTickers(): string[] {
|
||||
const stmt = db.prepare(`
|
||||
SELECT DISTINCT ticker FROM holdings
|
||||
UNION
|
||||
SELECT DISTINCT ticker FROM companies
|
||||
`);
|
||||
const rows = stmt.all() as Array<{ ticker: string }>;
|
||||
return rows.map((r) => r.ticker);
|
||||
}
|
||||
|
||||
// Refresh prices for all tickers
|
||||
async function refreshPrices() {
|
||||
try {
|
||||
const tickers = getPortfolioTickers();
|
||||
if (tickers.length === 0) return;
|
||||
|
||||
console.log(`[DataRefresh] Refreshing prices for ${tickers.length} tickers`);
|
||||
|
||||
for (const ticker of tickers) {
|
||||
const quote = await fetchQuote(ticker);
|
||||
if (quote) {
|
||||
// Update company price in database
|
||||
const company = db.prepare("SELECT * FROM companies WHERE ticker = ?").get(ticker) as any;
|
||||
if (company) {
|
||||
db.prepare(`
|
||||
UPDATE companies
|
||||
SET price = ?, change_pct = ?, updated_at = datetime('now')
|
||||
WHERE ticker = ?
|
||||
`).run(quote.price, quote.changePercent, ticker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify UI of price updates
|
||||
mainWindow?.webContents.send("data:prices-updated", {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
console.log("[DataRefresh] Price refresh complete");
|
||||
} catch (error) {
|
||||
console.error("[DataRefresh] Error refreshing prices:", error);
|
||||
}
|
||||
|
||||
// Schedule next refresh
|
||||
priceTimeout = setTimeout(refreshPrices, priceInterval * 60 * 1000);
|
||||
}
|
||||
|
||||
// Check for new filings
|
||||
async function refreshFilings() {
|
||||
try {
|
||||
const tickers = getPortfolioTickers();
|
||||
if (tickers.length === 0) return;
|
||||
|
||||
console.log(`[DataRefresh] Checking for new filings for ${tickers.length} tickers`);
|
||||
|
||||
for (const ticker of tickers) {
|
||||
// Get most recent filing date from database
|
||||
const existingFilings = listFilings(db, ticker);
|
||||
const since = existingFilings.length > 0
|
||||
? existingFilings[0].filedDate
|
||||
: undefined;
|
||||
|
||||
const newFilings = await fetchFilings(ticker, { limit: 10, since });
|
||||
|
||||
for (const filing of newFilings) {
|
||||
// Check if filing already exists
|
||||
const exists = db.prepare(`
|
||||
SELECT id FROM filings WHERE company_id = ? AND filed_date = ? AND form_type = ?
|
||||
`).get(ticker, filing.filedDate, filing.formType);
|
||||
|
||||
if (!exists) {
|
||||
db.prepare(`
|
||||
INSERT INTO filings (id, company_id, form_type, filed_date, title)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
`${ticker}-${filing.formType}-${filing.filedDate}`,
|
||||
ticker,
|
||||
filing.formType,
|
||||
filing.filedDate,
|
||||
filing.title
|
||||
);
|
||||
|
||||
// Notify UI of new filing
|
||||
mainWindow?.webContents.send("alert:new-filing", {
|
||||
ticker,
|
||||
formType: filing.formType,
|
||||
title: filing.title,
|
||||
filedDate: filing.filedDate,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[DataRefresh] Filing refresh complete");
|
||||
} catch (error) {
|
||||
console.error("[DataRefresh] Error refreshing filings:", error);
|
||||
}
|
||||
|
||||
// Schedule next refresh
|
||||
filingTimeout = setTimeout(refreshFilings, filingInterval * 60 * 1000);
|
||||
}
|
||||
|
||||
// Refresh earnings dates
|
||||
async function refreshEarnings() {
|
||||
try {
|
||||
const tickers = getPortfolioTickers();
|
||||
if (tickers.length === 0) return;
|
||||
|
||||
console.log(`[DataRefresh] Updating earnings dates for ${tickers.length} tickers`);
|
||||
|
||||
// Import dynamically to avoid circular dependency
|
||||
const { getEarningsDate, getQuarterString } = await import("@mosaiciq/shared/data");
|
||||
|
||||
for (const ticker of tickers) {
|
||||
const earningsDate = await getEarningsDate(ticker);
|
||||
if (earningsDate) {
|
||||
// Check if earnings schedule exists
|
||||
const existing = db.prepare(`
|
||||
SELECT id FROM earnings_schedules WHERE company_id = ? AND expected_date = ?
|
||||
`).get(ticker, earningsDate.toISOString());
|
||||
|
||||
if (!existing) {
|
||||
const quarter = getQuarterString(earningsDate);
|
||||
db.prepare(`
|
||||
INSERT INTO earnings_schedules (id, company_id, quarter, expected_date)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`).run(
|
||||
`earnings-${ticker}-${Date.now()}`,
|
||||
ticker,
|
||||
quarter,
|
||||
earningsDate.toISOString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[DataRefresh] Earnings refresh complete");
|
||||
} catch (error) {
|
||||
console.error("[DataRefresh] Error refreshing earnings:", error);
|
||||
}
|
||||
|
||||
// Schedule next refresh
|
||||
earningsTimeout = setTimeout(refreshEarnings, earningsInterval * 60 * 1000);
|
||||
}
|
||||
|
||||
// Start all refresh cycles
|
||||
console.log("[DataRefresh] Starting data refresh service");
|
||||
priceTimeout = setTimeout(refreshPrices, 1000); // Start immediately
|
||||
filingTimeout = setTimeout(refreshFilings, 5000); // Start after 5s
|
||||
earningsTimeout = setTimeout(refreshEarnings, 10000); // Start after 10s
|
||||
|
||||
// Return cleanup function
|
||||
return () => {
|
||||
if (priceTimeout) clearTimeout(priceTimeout);
|
||||
if (filingTimeout) clearTimeout(filingTimeout);
|
||||
if (earningsTimeout) clearTimeout(earningsTimeout);
|
||||
console.log("[DataRefresh] Stopped data refresh service");
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user