refactor(export): remove dead assignable-axes helpers and their tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// Pure helpers for exporting daily-parameter data as a CSV matrix. buildMatrix is
|
||||
// dimension-agnostic: rows and columns are assignable to Date/Subject/Parameter,
|
||||
// with the leftover dimension pinned to a single value.
|
||||
// Pure helpers for exporting daily-parameter data as a CSV matrix. The export is a
|
||||
// subject-series matrix: subjects are always columns, the chosen X coordinate
|
||||
// (Date / # Days Reach / a daily field) provides the rows, and one Data
|
||||
// parameter supplies the cell values.
|
||||
// No React, no I/O — mirrors the crossSubjectChart.js pure-helper pattern.
|
||||
|
||||
// Read a single parameter's raw value from one daily status.
|
||||
@@ -156,20 +157,6 @@ export function buildSubjectSeriesMatrix({ xField, dataParam, groupField }, { st
|
||||
};
|
||||
}
|
||||
|
||||
export const EXPORT_DIMENSIONS = [
|
||||
{ dim: 'date', label: 'Date' },
|
||||
{ dim: 'subject', label: 'Subject' },
|
||||
{ dim: 'parameter', label: 'Parameter' },
|
||||
];
|
||||
|
||||
export function dimLabel(dim) {
|
||||
return EXPORT_DIMENSIONS.find((d) => d.dim === dim)?.label ?? '';
|
||||
}
|
||||
|
||||
export function otherDimension(rowDim, colDim) {
|
||||
return EXPORT_DIMENSIONS.map((d) => d.dim).find((d) => d !== rowDim && d !== colDim) ?? null;
|
||||
}
|
||||
|
||||
// Resolve a subject's group value for a given group field id.
|
||||
// null -> no grouping (field is falsy or '__none__')
|
||||
// '—' -> grouping is on but this subject has no value
|
||||
@@ -181,29 +168,6 @@ export function subjectGroupValue(animal, groupField) {
|
||||
return v === null || v === undefined || v === '' ? '—' : v;
|
||||
}
|
||||
|
||||
export function listDateMembers(statuses) {
|
||||
const keys = new Set();
|
||||
for (const s of statuses ?? []) {
|
||||
if (s?.date) keys.add(dateKey(s.date));
|
||||
}
|
||||
return [...keys].sort().map((k) => ({ id: k, label: k, dateKey: k }));
|
||||
}
|
||||
|
||||
// dateKey -> Map(animalId -> status); first status per (date, animal) wins,
|
||||
// scanning in date order (matches the legacy buildMatrix tie-break).
|
||||
export function buildStatusIndex(statuses) {
|
||||
const index = new Map();
|
||||
const ordered = [...(statuses ?? [])].sort((a, b) => dateKey(a?.date).localeCompare(dateKey(b?.date)));
|
||||
for (const s of ordered) {
|
||||
if (!s?.date || s.animal_id === null || s.animal_id === undefined) continue;
|
||||
const dk = dateKey(s.date);
|
||||
if (!index.has(dk)) index.set(dk, new Map());
|
||||
const byAnimal = index.get(dk);
|
||||
if (!byAnimal.has(s.animal_id)) byAnimal.set(s.animal_id, s);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
export function listSubjectMembers(animals, groupField) {
|
||||
const list = [...(animals ?? [])];
|
||||
const nameCounts = new Map();
|
||||
@@ -227,19 +191,6 @@ export function listSubjectMembers(animals, groupField) {
|
||||
return members;
|
||||
}
|
||||
|
||||
export function listParameterMembers(dailyTemplate, statuses) {
|
||||
return listExportableParams(dailyTemplate, statuses).map((p) => ({
|
||||
id: p.id, label: p.label, param: p, group: p.group,
|
||||
}));
|
||||
}
|
||||
|
||||
export function listDimensionMembers(dim, { statuses, animals, dailyTemplate, groupField }) {
|
||||
if (dim === 'date') return listDateMembers(statuses);
|
||||
if (dim === 'subject') return listSubjectMembers(animals, groupField);
|
||||
if (dim === 'parameter') return listParameterMembers(dailyTemplate, statuses);
|
||||
return [];
|
||||
}
|
||||
|
||||
// A cell has a value unless it is null, undefined, or the empty string.
|
||||
// (0 and false are real values.)
|
||||
function hasValue(v) {
|
||||
@@ -250,59 +201,6 @@ function dateKey(date) {
|
||||
return String(date).slice(0, 10); // ISO datetime or date → YYYY-MM-DD
|
||||
}
|
||||
|
||||
// Pivot statuses into a 2-D matrix per the chosen row/column dimensions.
|
||||
// The leftover (pinned) dimension is fixed to pinnedMember. Every cell resolves
|
||||
// a (dateKey, animalId, param) triple through the status index.
|
||||
export function buildMatrix(config, ctx) {
|
||||
const { rowDim, colDim, pinnedMember, groupField } = config;
|
||||
const pinnedDim = otherDimension(rowDim, colDim);
|
||||
const index = buildStatusIndex(ctx.statuses);
|
||||
const full = { ...ctx, groupField };
|
||||
const rowMembers = listDimensionMembers(rowDim, full);
|
||||
const colMembers = listDimensionMembers(colDim, full);
|
||||
|
||||
const cellValue = (rowM, colM) => {
|
||||
const byDim = { [rowDim]: rowM, [colDim]: colM, [pinnedDim]: pinnedMember };
|
||||
const dk = byDim.date?.dateKey ?? null;
|
||||
const animalId = byDim.subject?.animalId ?? null;
|
||||
const param = byDim.parameter?.param ?? null;
|
||||
if (dk === null || animalId === null || animalId === undefined || !param) return '';
|
||||
const status = index.get(dk)?.get(animalId);
|
||||
if (!status) return '';
|
||||
const v = extractValue(status, param);
|
||||
return hasValue(v) ? v : '';
|
||||
};
|
||||
|
||||
let rows = rowMembers.map((rowM) => ({
|
||||
member: rowM,
|
||||
values: colMembers.map((colM) => cellValue(rowM, colM)),
|
||||
}));
|
||||
|
||||
// Empty-member rule: keep all subjects; drop entirely-blank date/parameter rows & columns.
|
||||
if (rowDim !== 'subject') rows = rows.filter((r) => r.values.some((v) => v !== ''));
|
||||
|
||||
let keptCols = colMembers.map((_, i) => i);
|
||||
if (colDim !== 'subject') keptCols = keptCols.filter((i) => rows.some((r) => r.values[i] !== ''));
|
||||
const columns = keptCols.map((i) => colMembers[i]);
|
||||
rows = rows.map((r) => ({ member: r.member, values: keptCols.map((i) => r.values[i]) }));
|
||||
|
||||
const grouped = !!groupField && groupField !== '__none__';
|
||||
const groupAxis = grouped
|
||||
? (rowDim === 'subject' ? 'row' : colDim === 'subject' ? 'col' : null)
|
||||
: null;
|
||||
|
||||
return {
|
||||
rowDim,
|
||||
colDim,
|
||||
pinnedDim,
|
||||
corner: dimLabel(rowDim),
|
||||
context: { label: dimLabel(pinnedDim), value: pinnedMember?.label ?? '' },
|
||||
groupAxis,
|
||||
columns,
|
||||
rows,
|
||||
};
|
||||
}
|
||||
|
||||
function escapeCsvCell(value) {
|
||||
const s = value === null || value === undefined ? '' : String(value);
|
||||
return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
||||
|
||||
Reference in New Issue
Block a user