fix report table

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-24 13:06:46 +01:00
parent 57697a5a39
commit 7b18544085
5 changed files with 537 additions and 258 deletions

View File

@@ -72,6 +72,58 @@ export function compute(
(a, b) => new Date(a).getTime() - new Date(b).getTime(),
);
// Calculate total_count for the formula using the same formula applied to input series' total_count values
// total_count is constant across all dates for a breakdown group, so compute it once
const totalCountScope: Record<string, number> = {};
definitions.slice(0, formulaIndex).forEach((depDef, depIndex) => {
const readableId = alphabetIds[depIndex];
if (!readableId) {
return;
}
// Find the series for this dependency in the current breakdown group
const depSeries = seriesByIndex.get(depIndex);
if (depSeries) {
// Get total_count from any data point (it's the same for all dates)
const totalCount = depSeries.data.find(
(d) => d.total_count != null,
)?.total_count;
totalCountScope[readableId] = totalCount ?? 0;
} else {
// Could be a formula from a previous breakdown group - find it in results
const formulaSerie = results.find(
(s) =>
s.definitionIndex === depIndex &&
'type' in s.definition &&
s.definition.type === 'formula' &&
s.name.slice(1).join(':::') === breakdownSignature,
);
if (formulaSerie) {
const totalCount = formulaSerie.data.find(
(d) => d.total_count != null,
)?.total_count;
totalCountScope[readableId] = totalCount ?? 0;
} else {
totalCountScope[readableId] = 0;
}
}
});
// Evaluate formula for total_count
let formulaTotalCount: number | undefined;
try {
const result = mathjs
.parse(formula.formula)
.compile()
.evaluate(totalCountScope) as number;
formulaTotalCount =
Number.isNaN(result) || !Number.isFinite(result)
? undefined
: round(result, 2);
} catch (error) {
formulaTotalCount = undefined;
}
// Calculate formula for each date
const formulaData = sortedDates.map((date) => {
const scope: Record<string, number> = {};
@@ -124,8 +176,7 @@ export function compute(
Number.isNaN(count) || !Number.isFinite(count)
? 0
: round(count, 2),
total_count: breakdownSeries[0]?.data.find((d) => d.date === date)
?.total_count,
total_count: formulaTotalCount,
};
});

View File

@@ -231,13 +231,60 @@ export function getChartSql({
return sql;
}
const totalUniqueSubquery = `(
SELECT ${sb.select.count}
// Build total_count calculation that accounts for breakdowns
// When breakdowns exist, we need to calculate total_count per breakdown group
if (breakdowns.length > 0) {
// Create a subquery that calculates total_count per breakdown group (without date grouping)
// Then reference it in the main query via JOIN
const breakdownSelects = breakdowns
.map((breakdown, index) => {
const key = `label_${index + 1}`;
const breakdownExpr = getSelectPropertyKey(breakdown.name);
return `${breakdownExpr} as ${key}`;
})
.join(', ');
// GROUP BY needs to use the actual expressions, not aliases
const breakdownGroupByExprs = breakdowns
.map((breakdown) => getSelectPropertyKey(breakdown.name))
.join(', ');
// Build the total_count subquery grouped only by breakdowns (no date)
// Extract the count expression without the alias (remove "as count")
const countExpression = sb.select.count.replace(/\s+as\s+count$/i, '');
const totalCountSubquery = `(
SELECT
${breakdownSelects},
${countExpression} as total_count
FROM ${sb.from}
${getJoins()}
${getWhere()}
)`;
sb.select.total_unique_count = `${totalUniqueSubquery} as total_count`;
GROUP BY ${breakdownGroupByExprs}
) as total_counts`;
// Join the total_counts subquery to get total_count per breakdown
// Match on the breakdown column values
const joinConditions = breakdowns
.map((_, index) => {
const outerKey = `label_${index + 1}`;
return `${outerKey} = total_counts.label_${index + 1}`;
})
.join(' AND ');
sb.joins.total_counts = `LEFT JOIN ${totalCountSubquery} ON ${joinConditions}`;
// Use any() aggregate since total_count is the same for all rows in a breakdown group
sb.select.total_unique_count =
'any(total_counts.total_count) as total_count';
} else {
// No breakdowns - use a simple subquery for total count
const totalUniqueSubquery = `(
SELECT ${sb.select.count}
FROM ${sb.from}
${getJoins()}
${getWhere()}
)`;
sb.select.total_unique_count = `${totalUniqueSubquery} as total_count`;
}
const sql = `${getSelect()} ${getFrom()} ${getJoins()} ${getWhere()} ${getGroupBy()} ${getOrderBy()} ${getFill()}`;
console.log('-- Report --');