// Cost Breakdown — drill-down for a single run.
const { useState: useStateC, useEffect: useEffectC } = React;
// ── Phase grouping ─────────────────────────────────────────────────
// Phases that share a common prefix are collapsed into a group row.
const PHASE_GROUP_PREFIXES = [
"Bullets Generation",
"Novelty Embedding Evaluation",
"Novelty Search",
"Entity Grounding Check",
"Relevance Score",
"Concept Search",
];
function getGroupKey(label) {
for (const prefix of PHASE_GROUP_PREFIXES) {
if (label.startsWith(prefix) && label.length > prefix.length) return prefix;
}
return null;
}
function buildPhaseGroups(phases) {
const order = [];
const byKey = {};
phases.forEach(p => {
const key = getGroupKey(p.label);
if (key) {
if (!byKey[key]) {
byKey[key] = { type: "group", key, label: key, items: [], total: 0, llm: 0, embed: 0, api: 0, calls: 0, chunks: 0, requests: 0 };
order.push(byKey[key]);
}
const g = byKey[key];
g.items.push(p);
g.total += p.total;
g.llm += p.llm;
g.embed += p.embed;
g.api += p.api;
g.calls += p.calls;
g.chunks += p.chunks;
g.requests += p.requests;
} else {
order.push({ type: "single", data: p });
}
});
const grand = order.reduce((s, x) => s + (x.type === "group" ? x.total : x.data.total), 0);
order.forEach(x => {
if (x.type === "group") x.percent = grand > 0 ? (x.total / grand) * 100 : 0;
});
return { rows: order, grand };
}
function PhaseRow({ p, phaseMax, indent = false }) {
return (
{p.label}
${p.total.toFixed(4)}
{p.percent.toFixed(1)}%
{p.calls > 0 && {p.calls} LLM calls}
{p.requests > 0 && {p.requests} API requests}
{p.chunks > 0 && {p.chunks} chunks retrieved}
);
}
// Extract the sub-operation name from a phase label given its group prefix.
// e.g. prefix="Novelty Search", label="Novelty Search Parse 9 Attempt0" → "Parse"
// Returns null if the remainder is only a number (no meaningful sub-type).
function getSubKey(prefix, label) {
const rest = label.slice(prefix.length).trim();
const m = rest.match(/^([A-Za-z][A-Za-z\s]*?)(?=\s+\d|\s*$)/);
const key = m ? m[1].trim() : "";
return key || null;
}
function buildSubGroups(groupPrefix, items) {
const order = [];
const byKey = {};
let hasSubKeys = false;
items.forEach(p => {
const key = getSubKey(groupPrefix, p.label);
if (key) {
hasSubKeys = true;
if (!byKey[key]) {
byKey[key] = { key, label: key, items: [], total: 0, llm: 0, embed: 0, api: 0, calls: 0, chunks: 0, requests: 0, percent: 0 };
order.push(byKey[key]);
}
const sg = byKey[key];
sg.items.push(p);
sg.total += p.total;
sg.llm += p.llm;
sg.embed += p.embed;
sg.api += p.api;
sg.calls += p.calls;
sg.chunks += p.chunks;
sg.requests += p.requests;
} else {
order.push({ key: p.id, single: p });
}
});
if (!hasSubKeys) return null; // no sub-grouping needed
const grand = order.reduce((s, x) => s + (x.single ? x.single.total : x.total), 0);
order.forEach(x => { if (!x.single) x.percent = grand > 0 ? (x.total / grand) * 100 : 0; });
return order;
}
function SubGroupRow({ sg, phaseMax }) {
const [open, setOpen] = useStateC(false);
const isSingle = sg.items.length === 1;
return (
${sg.total.toFixed(4)}
{sg.percent.toFixed(1)}%
{sg.calls > 0 && {sg.calls} LLM calls}
{sg.requests > 0 && {sg.requests} API requests}
{sg.chunks > 0 && {sg.chunks} chunks}
{open && (
)}
);
}
function GroupRow({ g, phaseMax }) {
const [open, setOpen] = useStateC(false);
const subGroups = React.useMemo(() => buildSubGroups(g.key, g.items), [g.key, g.items]);
return (
${g.total.toFixed(4)}
{g.percent.toFixed(1)}%
{g.calls > 0 && {g.calls} LLM calls}
{g.requests > 0 && {g.requests} API requests}
{g.chunks > 0 && {g.chunks} chunks retrieved}
{open && (
{subGroups
? subGroups.map(x =>
x.single
?
:
)
: g.items.map(p =>
)
}
)}
);
}
function CostView({ tweaks, appCostData, appEntityRuns }) {
const COMPANIES = window.DATA?.companies || [];
const initial = appCostData ?? window.EXTRAS.cost;
const defaultEntityId = initial?.entityId || COMPANIES[0]?.id || "";
const [costData, setCostData] = useStateC(initial);
const [selectedRun, setSelectedRun] = useStateC(initial?.runId || null);
const [pickEntityId, setPickEntityId] = useStateC(defaultEntityId);
const [entityRuns, setEntityRuns] = useStateC(appEntityRuns || []);
const [loading, setLoading] = useStateC(false);
const [loadingRuns, setLoadingRuns] = useStateC(false);
const [error, setError] = useStateC(null);
function fetchEntityRuns(entityId, options = {}) {
const autoLoadFirst = Boolean(options.autoLoadFirst);
if (!entityId) {
setEntityRuns([]);
return Promise.resolve();
}
setLoadingRuns(true);
setError(null);
return fetch(`/api/frontend/cost/runs-by-entity/${encodeURIComponent(entityId)}`)
.then(r => r.json())
.then(data => {
const runs = Array.isArray(data.runs) ? data.runs : [];
setEntityRuns(runs);
if (autoLoadFirst) {
if (!runs.length) {
setError("No pipeline runs for this company.");
} else {
const pick = runs.find(x => x.hasMetrics) || runs[0];
if (pick.hasMetrics) {
return loadBreakdown(pick.runId);
}
setError("No cost metrics stored for this company’s runs yet.");
}
}
return undefined;
})
.catch(e => {
setError(String(e));
setEntityRuns([]);
})
.finally(() => setLoadingRuns(false));
}
function loadBreakdown(runId) {
setLoading(true);
setError(null);
const q = runId ? `?run_id=${encodeURIComponent(runId)}` : "";
return fetch(`/api/frontend/cost/breakdown${q}`)
.then(r => r.json())
.then(data => {
if (data.error && !data.breakdown) {
setError(data.error === "no_metrics" ? "No cost metrics for this run." : data.error);
setCostData(null);
return;
}
if (data.breakdown) {
setCostData(data.breakdown);
setSelectedRun(data.breakdown.runId);
setPickEntityId(data.breakdown.entityId);
fetchEntityRuns(data.breakdown.entityId, { autoLoadFirst: false });
} else {
setCostData(null);
}
})
.catch(e => setError(String(e)))
.finally(() => setLoading(false));
}
useEffectC(() => {
if (appCostData && appEntityRuns?.length) return;
loadBreakdown(initial?.runId || null);
}, []);
function onCompanyChange(entityId) {
setPickEntityId(entityId);
fetchEntityRuns(entityId, { autoLoadFirst: true });
}
function onRunChange(runId8) {
if (runId8) loadBreakdown(runId8);
}
const C = costData;
const recentList = (C && C.recentForBreakdown) ? C.recentForBreakdown : [];
if (loading && !C) {
return (
Loading cost data…
);
}
if (!C) {
return (
No cost metrics available yet. Run the pipeline to populate runs.
{error &&
{error}
}
);
}
const llmTotal = C.llmModels.reduce((s, m) => s + m.cost, 0);
const phaseMax = Math.max(...C.phases.map(p => p.total), 0.0001);
const { rows: phaseRows } = buildPhaseGroups([...C.phases].sort((a, b) => b.total - a.total));
return (
← Activity log
Cost forensics · run {C.runId}
{C.entityName} · {_tk(C.ticker)}
{C.entityId}
·
{C.windowStart.slice(0, 10)} → {C.windowEnd.slice(0, 10)}
·
·
{C.durationSec}s
{loading && · updating…}
Compute tokens cost
${C.summary.llm.toFixed(4)}
{C.llmModels.length} models · {C.llmModels.reduce((s, m) => s + m.calls, 0)} calls
Embeddings cost
${C.summary.embeddings.toFixed(4)}
{C.summary.embeddingTokens.toLocaleString()} tokens · {C.summary.embeddingModel}
Grounding tokens cost*
${C.summary.apiChunks.toFixed(4)}
{C.summary.chunksTotal} chunks × ${C.summary.chunkRate}
Total
${C.summary.total.toFixed(4)}
${(C.summary.total / Math.max(C.durationSec, 1) * 60).toFixed(4)} per minute of runtime
{/* Disclaimer (Change 6) */}
*Note: The Grounding Token cost does not take into account the data licences.
Compute tokens — by model
{C.llmModels.length} models · ${llmTotal.toFixed(4)} total
| Model |
Role |
Calls |
Input tkn |
Output tkn |
Cost |
% of Compute |
{[...C.llmModels].sort((a, b) => b.cost - a.cost).map(m => {
const pct = llmTotal > 0 ? (m.cost / llmTotal) * 100 : 0;
return (
| {m.model} |
{m.role} |
{m.calls} |
{m.promptTokens.toLocaleString()} |
{m.completionTokens.toLocaleString()} |
${m.cost.toFixed(4)} |
|
);
})}
By pipeline phase
Where the dollars actually went
{phaseRows.map((row, i) =>
row.type === "group"
?
:
)}
Compute tokens
Embeddings
Grounding tokens
);
}
window.CostView = CostView;