Skip to content

Commit dd4b303

Browse files
Merge remote-tracking branch 'origin/codex/enhance-asset-observability-app'
2 parents d145d58 + b4f9384 commit dd4b303

4 files changed

Lines changed: 425 additions & 6 deletions

File tree

apps/asset-observatory/app.js

Lines changed: 279 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
const providerChartContainer = document.querySelector('[data-provider-chart]');
1010
const datasetTableBody = document.querySelector('[data-dataset-table]');
1111
const similarityGrid = document.querySelector('[data-similarity-grid]');
12+
const freshnessGrid = document.querySelector('[data-freshness-grid]');
1213
const sourceList = document.querySelector('[data-source-list]');
1314
const generatedAtNode = document.querySelector('[data-generated-at]');
1415
const providerCountNode = document.querySelector('[data-provider-count]');
1516

16-
if (!summaryRoot || !datasetChartContainer || !providerChartContainer || !datasetTableBody || !similarityGrid || !sourceList) {
17+
if (!summaryRoot || !datasetChartContainer || !providerChartContainer || !datasetTableBody || !similarityGrid || !freshnessGrid || !sourceList) {
1718
return;
1819
}
1920

@@ -58,6 +59,103 @@
5859
});
5960
};
6061

62+
const toDate = (value) => {
63+
if (!value) {
64+
return null;
65+
}
66+
if (value instanceof Date) {
67+
return Number.isNaN(value.getTime()) ? null : value;
68+
}
69+
if (typeof value === 'string' || typeof value === 'number') {
70+
const parsed = new Date(value);
71+
return Number.isNaN(parsed.getTime()) ? null : parsed;
72+
}
73+
return null;
74+
};
75+
76+
const msPerDay = 1000 * 60 * 60 * 24;
77+
78+
const differenceInDays = (laterDate, earlierDate) => {
79+
const later = toDate(laterDate);
80+
const earlier = toDate(earlierDate);
81+
if (!later || !earlier) {
82+
return null;
83+
}
84+
const diff = later.getTime() - earlier.getTime();
85+
if (!Number.isFinite(diff) || diff <= 0) {
86+
return 0;
87+
}
88+
return diff / msPerDay;
89+
};
90+
91+
const formatRelativeDays = (days) => {
92+
if (days === null || !Number.isFinite(days)) {
93+
return 'No signal';
94+
}
95+
if (days < 0.5) {
96+
return 'today';
97+
}
98+
if (days < 1.5) {
99+
return '1 day ago';
100+
}
101+
if (days < 7) {
102+
return `${decimalFormatter.format(days)} days ago`;
103+
}
104+
return `${Math.round(days)} days ago`;
105+
};
106+
107+
const classifyFreshness = (ageDays) => {
108+
if (ageDays === null || !Number.isFinite(ageDays)) {
109+
return { bucket: 'missing', className: 'status-muted', toneLabel: 'Missing' };
110+
}
111+
if (ageDays <= 2) {
112+
return { bucket: 'fresh', className: 'status-healthy', toneLabel: 'Fresh' };
113+
}
114+
if (ageDays <= 7) {
115+
return { bucket: 'due', className: 'status-warning', toneLabel: 'Due soon' };
116+
}
117+
return { bucket: 'stale', className: 'status-critical', toneLabel: 'Needs refresh' };
118+
};
119+
120+
const referenceDate = toDate(data.generatedAt) || new Date();
121+
122+
const computeComponentStatus = (timestamps, count) => {
123+
const valid = timestamps
124+
.map((entry) => toDate(entry))
125+
.filter((entry) => entry);
126+
127+
if (valid.length === 0) {
128+
return {
129+
count,
130+
bucket: 'missing',
131+
className: 'status-muted',
132+
toneLabel: count > 0 ? 'Untracked' : 'Missing',
133+
newestDate: null,
134+
oldestDate: null,
135+
ageDays: null,
136+
spanDays: null,
137+
};
138+
}
139+
140+
const sorted = valid.slice().sort((a, b) => a.getTime() - b.getTime());
141+
const oldestDate = sorted[0];
142+
const newestDate = sorted[sorted.length - 1];
143+
const ageDays = differenceInDays(referenceDate, newestDate);
144+
const spanDays = sorted.length > 1 ? differenceInDays(newestDate, oldestDate) : 0;
145+
const classification = classifyFreshness(ageDays);
146+
147+
return {
148+
count,
149+
bucket: classification.bucket,
150+
className: classification.className,
151+
toneLabel: classification.toneLabel,
152+
newestDate,
153+
oldestDate,
154+
ageDays,
155+
spanDays,
156+
};
157+
};
158+
61159
if (generatedAtNode) {
62160
generatedAtNode.textContent = formatTimestamp(data.generatedAt);
63161
}
@@ -69,6 +167,68 @@
69167
providerCountNode.textContent = formatNumber(providerCount);
70168
}
71169

170+
const datasets = [...data.datasets];
171+
const providerTotals = Array.isArray(data.providers) ? [...data.providers] : [];
172+
173+
const deckFreshness = datasets.map((deck) => {
174+
const manifestStatus = computeComponentStatus([deck.manifest?.generatedAt], deck.manifest ? 1 : 0);
175+
const embeddingRecords = Array.isArray(deck.embeddings) ? deck.embeddings : [];
176+
const similarityRecords = Array.isArray(deck.similarityReports) ? deck.similarityReports : [];
177+
const embeddingStatus = computeComponentStatus(embeddingRecords.map((record) => record.generatedAt), embeddingRecords.length);
178+
const similarityStatus = computeComponentStatus(similarityRecords.map((record) => record.generatedAt), similarityRecords.length);
179+
const statuses = [manifestStatus, embeddingStatus, similarityStatus];
180+
181+
const latestTimestamp = statuses.reduce((accumulator, status) => {
182+
if (!status.newestDate) {
183+
return accumulator;
184+
}
185+
if (!accumulator || status.newestDate.getTime() > accumulator.getTime()) {
186+
return status.newestDate;
187+
}
188+
return accumulator;
189+
}, null);
190+
191+
const latestAgeDays = latestTimestamp ? differenceInDays(referenceDate, latestTimestamp) : null;
192+
193+
const statusCounts = {
194+
dueSoon: statuses.filter((status) => status.bucket === 'due').length,
195+
stale: statuses.filter((status) => status.bucket === 'stale').length,
196+
missing: statuses.filter((status) => status.bucket === 'missing').length,
197+
};
198+
199+
return {
200+
id: deck.id,
201+
label: deck.label,
202+
statuses: {
203+
manifest: manifestStatus,
204+
embeddings: embeddingStatus,
205+
similarity: similarityStatus,
206+
},
207+
counts: {
208+
embeddings: embeddingStatus.count,
209+
similarityReports: similarityStatus.count,
210+
},
211+
latestTimestamp,
212+
latestAgeDays,
213+
statusCounts,
214+
};
215+
});
216+
217+
const totalAttention = deckFreshness.reduce((accumulator, entry) => accumulator + entry.statusCounts.dueSoon + entry.statusCounts.stale, 0);
218+
const totalStale = deckFreshness.reduce((accumulator, entry) => accumulator + entry.statusCounts.stale, 0);
219+
const totalMissingSignals = deckFreshness.reduce((accumulator, entry) => accumulator + entry.statusCounts.missing, 0);
220+
const averageLagDays = (() => {
221+
const values = deckFreshness
222+
.map((entry) => entry.latestAgeDays)
223+
.filter((value) => value !== null && Number.isFinite(value));
224+
if (values.length === 0) {
225+
return null;
226+
}
227+
const sum = values.reduce((accumulator, value) => accumulator + value, 0);
228+
return sum / values.length;
229+
})();
230+
const averageLagLabel = averageLagDays === null ? '—' : formatRelativeDays(averageLagDays);
231+
72232
const summaryMetrics = [
73233
{
74234
label: 'Curated entries',
@@ -90,6 +250,11 @@
90250
value: formatBytes(data.totals?.totalBytes || 0),
91251
detail: `${formatNumber(data.totals?.totalFiles || 0)} files across datasets & sources`,
92252
},
253+
{
254+
label: 'Refresh queue',
255+
value: formatNumber(totalAttention),
256+
detail: `${formatNumber(totalStale)} stale • ${formatNumber(totalMissingSignals)} missing signals • avg refresh ${averageLagLabel}`,
257+
},
93258
];
94259

95260
summaryRoot.innerHTML = '';
@@ -112,11 +277,120 @@
112277
summaryRoot.appendChild(card);
113278
});
114279

115-
const datasets = [...data.datasets];
116-
const providerTotals = Array.isArray(data.providers) ? [...data.providers] : [];
117-
118280
const datasetById = new Map(datasets.map((deck) => [deck.id, deck]));
119281

282+
const buildFreshnessCards = (assessments) => {
283+
freshnessGrid.innerHTML = '';
284+
285+
if (!Array.isArray(assessments) || assessments.length === 0) {
286+
const fallback = document.createElement('p');
287+
fallback.textContent = 'No decks available to audit.';
288+
fallback.style.color = 'var(--text-secondary)';
289+
freshnessGrid.appendChild(fallback);
290+
return;
291+
}
292+
293+
assessments.forEach((assessment) => {
294+
const card = document.createElement('article');
295+
card.className = 'freshness-card';
296+
297+
if (assessment.statusCounts.stale > 0) {
298+
card.classList.add('needs-action');
299+
} else if (assessment.statusCounts.dueSoon > 0) {
300+
card.classList.add('due-soon');
301+
}
302+
303+
const heading = document.createElement('h3');
304+
heading.textContent = assessment.label;
305+
card.appendChild(heading);
306+
307+
const lastRefresh = document.createElement('p');
308+
if (assessment.latestAgeDays === null) {
309+
lastRefresh.textContent = 'No refresh timestamps captured.';
310+
} else {
311+
const timestampLabel = assessment.latestTimestamp
312+
? formatTimestamp(assessment.latestTimestamp.toISOString())
313+
: '—';
314+
lastRefresh.textContent = `Last refresh ${formatRelativeDays(assessment.latestAgeDays)} (${timestampLabel})`;
315+
}
316+
card.appendChild(lastRefresh);
317+
318+
const meta = document.createElement('p');
319+
meta.className = 'freshness-meta';
320+
const embeddingLabel = `${formatNumber(assessment.counts.embeddings)} embedding store${assessment.counts.embeddings === 1 ? '' : 's'}`;
321+
const similarityLabel = `${formatNumber(assessment.counts.similarityReports)} similarity report${assessment.counts.similarityReports === 1 ? '' : 's'}`;
322+
meta.textContent = `${embeddingLabel}${similarityLabel}`;
323+
card.appendChild(meta);
324+
325+
const statusList = document.createElement('ul');
326+
statusList.className = 'freshness-status-list';
327+
328+
const descriptors = [
329+
{ key: 'manifest', label: 'Manifest', missing: 'Manifest not generated' },
330+
{ key: 'embeddings', label: 'Embeddings', missing: 'No embedding stores tracked' },
331+
{ key: 'similarity', label: 'Similarity', missing: 'No similarity reports' },
332+
];
333+
334+
descriptors.forEach((descriptor) => {
335+
const info = assessment.statuses[descriptor.key];
336+
const item = document.createElement('li');
337+
item.className = `status-chip ${info.className || 'status-muted'}`;
338+
339+
const title = document.createElement('span');
340+
title.className = 'status-chip__title';
341+
title.textContent = descriptor.label;
342+
item.appendChild(title);
343+
344+
const value = document.createElement('span');
345+
value.className = 'status-chip__value';
346+
if (info.ageDays === null) {
347+
value.textContent = info.count > 0 ? 'Timestamp missing' : descriptor.missing;
348+
} else {
349+
value.textContent = formatRelativeDays(info.ageDays);
350+
}
351+
item.appendChild(value);
352+
353+
const detailParts = [];
354+
355+
if (info.ageDays !== null) {
356+
detailParts.push(info.toneLabel);
357+
}
358+
359+
if (descriptor.key === 'embeddings') {
360+
const storeLabel = `${formatNumber(info.count)} store${info.count === 1 ? '' : 's'}`;
361+
detailParts.push(storeLabel);
362+
if (info.spanDays && info.spanDays > 0.4) {
363+
detailParts.push(`spread ${decimalFormatter.format(info.spanDays)}d`);
364+
}
365+
} else if (descriptor.key === 'similarity') {
366+
const reportLabel = `${formatNumber(info.count)} report${info.count === 1 ? '' : 's'}`;
367+
detailParts.push(reportLabel);
368+
if (info.spanDays && info.spanDays > 0.4) {
369+
detailParts.push(`span ${decimalFormatter.format(info.spanDays)}d`);
370+
}
371+
} else if (descriptor.key === 'manifest' && info.count > 0 && info.ageDays !== null) {
372+
detailParts.push('1 file');
373+
}
374+
375+
if (info.newestDate) {
376+
detailParts.push(formatTimestamp(info.newestDate.toISOString()));
377+
}
378+
379+
if (detailParts.length > 0) {
380+
const detail = document.createElement('span');
381+
detail.className = 'status-chip__detail';
382+
detail.textContent = detailParts.join(' • ');
383+
item.appendChild(detail);
384+
}
385+
386+
statusList.appendChild(item);
387+
});
388+
389+
card.appendChild(statusList);
390+
freshnessGrid.appendChild(card);
391+
});
392+
};
393+
120394
const buildDatasetChart = () => {
121395
datasetChartContainer.innerHTML = '';
122396
const svgNamespace = 'http://www.w3.org/2000/svg';
@@ -521,6 +795,7 @@
521795
});
522796
};
523797

798+
buildFreshnessCards(deckFreshness);
524799
buildDatasetChart();
525800
buildProviderChart();
526801
buildDatasetTable();

apps/asset-observatory/asset-data.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
window.assetObservatoryData = {
2-
"generatedAt": "2025-09-27T03:52:20.468Z",
2+
"generatedAt": "2025-09-27T03:53:29.383Z",
33
"datasets": [
44
{
55
"id": "jokes",

0 commit comments

Comments
 (0)