feat(export): X-coordinate helpers (date, # days reach, daily field)

This commit is contained in:
Experiments DB Dev
2026-07-19 14:54:25 -04:00
parent 3e52117c1a
commit 8510903771
2 changed files with 116 additions and 0 deletions
+64
View File
@@ -56,6 +56,70 @@ export function listExportableParams(dailyTemplate, statuses) {
return params;
}
// Numeric value of a daily field on one status (mirrors the metrics plot's helper).
export function numericFieldVal(status, field) {
if (!field) return null;
const raw = field.builtin ? status?.[field.key] : status?.custom_fields?.[field.fieldId];
const n = parseFloat(raw);
return Number.isNaN(n) ? null : n;
}
// Ordered row members for the chosen X coordinate.
// xField: '__date__' | '__days__' | <daily fieldId>
export function listSampleMembers(statuses, xField, dailyTemplate) {
const list = statuses ?? [];
if (xField === '__date__') {
const keys = new Set();
for (const s of list) if (s?.date) keys.add(dateKey(s.date));
return [...keys].sort().map((k) => ({ id: k, label: k, sampleValue: k }));
}
if (xField === '__days__') {
const counts = new Map();
for (const s of list) {
if (s?.animal_id == null || !s?.date) continue;
counts.set(s.animal_id, (counts.get(s.animal_id) ?? 0) + 1);
}
const max = counts.size ? Math.max(...counts.values()) : 0;
return Array.from({ length: max }, (_, i) => ({ id: String(i + 1), label: String(i + 1), sampleValue: i + 1 }));
}
const field = (dailyTemplate ?? []).find((f) => f.fieldId === xField);
const vals = new Set();
for (const s of list) {
const v = numericFieldVal(s, field);
if (v !== null) vals.add(v);
}
return [...vals].sort((a, b) => a - b).map((v) => ({ id: String(v), label: String(v), sampleValue: v }));
}
// animalId -> Map(sampleValue -> status). First status per (subject, sampleValue) wins,
// scanning each subject's statuses in ascending date order.
export function buildSampleIndex(statuses, xField, dailyTemplate) {
const bySubject = new Map();
for (const s of statuses ?? []) {
if (s?.animal_id == null || !s?.date) continue;
if (!bySubject.has(s.animal_id)) bySubject.set(s.animal_id, []);
bySubject.get(s.animal_id).push(s);
}
const field = xField !== '__date__' && xField !== '__days__'
? (dailyTemplate ?? []).find((f) => f.fieldId === xField)
: null;
const index = new Map();
for (const [animalId, subjStatuses] of bySubject) {
const ordered = [...subjStatuses].sort((a, b) => dateKey(a.date).localeCompare(dateKey(b.date)));
const m = new Map();
ordered.forEach((s, i) => {
let key;
if (xField === '__date__') key = dateKey(s.date);
else if (xField === '__days__') key = i + 1;
else { const v = numericFieldVal(s, field); if (v === null) return; key = v; }
if (!m.has(key)) m.set(key, s);
});
index.set(animalId, m);
}
return index;
}
export const EXPORT_DIMENSIONS = [
{ dim: 'date', label: 'Date' },
{ dim: 'subject', label: 'Subject' },