Results Page — Single Summary Endpoint¶
Context¶
The ResultsPage displays two layers of "big number" cards (no charts in this scope):
- Unit-level totals (3 cards at top) — total tCO2eq, tCO2eq per FTE, year-over-year %
- Per-module totals (3 cards per expanded module) — same 3 metrics scoped to one module
Currently the page hardcodes most values ("37'250", "8.2", "-11.3%"). The existing GET /unit/{unit_id}/{year}/totals endpoint is incomplete — it only sums equipment and doesn't return FTE or per-module breakdown. We replace it with a single endpoint keyed by carbon_report_id that returns everything the ResultsPage needs in one call.
Design principle: One frontend call → one response with unit totals + per-module breakdown + year comparison. The backend reuses existing repo methods and only adds a service method + endpoint route.
Endpoint: Results Summary¶
Route: GET /modules-stats/{carbon_report_id}/results-summary
Purpose: Feed all big-number cards on the ResultsPage with a single call.
Response (tonnes only, car-equivalent computed server-side):
{
"unit_totals": {
"total_tonnes_co2eq": 37.25,
"total_fte": 4532.0,
"tonnes_co2eq_per_fte": 8.22,
"equivalent_car_km": 109559,
"previous_year_total_tonnes_co2eq": 42.0,
"year_comparison_percentage": -11.3
},
"module_results": [
{
"module_type_id": 1,
"total_tonnes_co2eq": 5.0,
"total_fte": 4532.0,
"tonnes_co2eq_per_fte": 1.1,
"equivalent_car_km": 14706,
"previous_year_total_tonnes_co2eq": 5.5,
"year_comparison_percentage": -9.1
},
{
"module_type_id": 2,
"total_tonnes_co2eq": 15.0,
"total_fte": null,
"tonnes_co2eq_per_fte": 3.31,
"equivalent_car_km": 44118,
"previous_year_total_tonnes_co2eq": 17.0,
"year_comparison_percentage": -11.8
}
]
}
module_resultsonly includes validated modules (status = 2)total_fteis only set on module_type_id 1 (headcount); null for otherstonnes_co2eq_per_fteuses the unit's total FTE from headcount as denominatorequivalent_car_km=kg_co2eq / 0.34(0.34 kg CO2eq per km, backend constant)- If headcount is not validated, FTE-related fields are null everywhere
- If previous year has no data,
previous_year_*andyear_comparison_percentageare null - Year comparison is computed per-module too, not just at unit level
1. Repositories (reuse existing methods)¶
Emissions: data_entry_emission_repo.py — get_stats_by_carbon_report_id() (line 158)¶
Already does exactly what we need:
- Joins
DataEntryEmission → DataEntry → CarbonReportModule - Filters:
carbon_report_id,status == VALIDATED,kg_co2eq IS NOT NULL - Groups by:
module_type_id - Returns:
{"2": 15000.0, "4": 41700.0}
Called once for current year, once for previous year (if exists).
FTE: data_entry_repo.py — get_stats_by_carbon_report_id() (line 134)¶
Already does exactly what we need:
- Joins
DataEntry → CarbonReportModule - Filters:
carbon_report_id,status == VALIDATED - Default args:
aggregate_by="module_type_id",aggregate_field="fte" - Returns:
{"1": 4532.0}(headcount module FTE)
Called once for current year, once for previous year (if exists).
No new repo methods needed.
2. Service: unit_totals_service.py¶
Add get_results_summary(carbon_report_id: int)¶
- Load
CarbonReportby id to getunit_idandyear - Look up previous year's
CarbonReportviaCarbonReportRepository.get_by_unit_and_year(unit_id, year - 1) - Call
DataEntryEmissionRepository.get_stats_by_carbon_report_id(carbon_report_id)→ current emissions per module - Call
DataEntryRepository.get_stats_by_carbon_report_id(carbon_report_id)→ current FTE per module - If prev report exists, repeat steps 3-4 for
prev_carbon_report_id - Assemble response:
- For each module in current emissions:
total_tonnes_co2eq = kg / 1000tonnes_co2eq_per_fte = (kg / 1000) / total_fte(total_fte from headcount, key "1")equivalent_car_km = kg / CO2_PER_KM_KGwhereCO2_PER_KM_KG = 0.34previous_year_total_tonnes_co2eqfrom prev year emissions (same module)year_comparison_percentage = (current - prev) / prev * 100
- For headcount module: also set
total_fte - Unit totals = sum across all modules
Total DB queries: 3 (load report + 2 stats) or 5 if previous year exists.
3. Endpoint: carbon_report_module_stats.py¶
Add GET /{carbon_report_id}/results-summary¶
Already mounted at /modules-stats prefix (consistent with existing validated-totals endpoint).
The endpoint:
- Calls
UnitTotalsService(db).get_results_summary(carbon_report_id) - Returns the response directly
4. Frontend: ResultsPage.vue¶
Update fetchUnitTotals → fetchResultsSummary¶
- Change API call to
modules-stats/${carbonReportId}/results-summary - Use
workspaceStore.selectedCarbonReport.idas thecarbon_report_id - Update interface to match new response shape
- Wire top 3 BigNumber cards to
resultsSummary.unit_totals.* - Wire per-module BigNumber cards by matching
module_type_idinresultsSummary.module_results - Show "validate module" placeholder if module not in
module_results - Remove hardcoded
"37'250","8.2","-11.3%"values - Remove
calculateEquivalentKmfunction (server-side now)
Add typed API function: modules.ts¶
interface ResultsSummary {
unit_totals: {
total_tonnes_co2eq: number | null;
total_fte: number | null;
tonnes_co2eq_per_fte: number | null;
equivalent_car_km: number | null;
previous_year_total_tonnes_co2eq: number | null;
year_comparison_percentage: number | null;
};
module_results: Array<{
module_type_id: number;
total_tonnes_co2eq: number;
total_fte: number | null;
tonnes_co2eq_per_fte: number | null;
equivalent_car_km: number;
previous_year_total_tonnes_co2eq: number | null;
year_comparison_percentage: number | null;
}>;
}
function getResultsSummary(carbonReportId: number): Promise<ResultsSummary>;
Files Modified¶
| File | Change |
|---|---|
backend/app/services/unit_totals_service.py | Add get_results_summary(carbon_report_id) |
backend/app/api/v1/carbon_report_module_stats.py | Add GET /{carbon_report_id}/results-summary |
frontend/src/pages/app/ResultsPage.vue | Wire big numbers to real API data |
frontend/src/api/modules.ts | Add getResultsSummary() + interfaces |
Verification¶
- Call
GET /api/v1/modules-stats/{carbon_report_id}/results-summary— verifyunit_totalssums all validated modules - Verify
module_resultsonly contains validated modules (non-validated excluded) - Verify
total_ftecomes from headcount module'sDataEntry.data["fte"], not from emissions - Verify
equivalent_car_km= kg_co2eq / 0.34 for both unit and module level - Verify
tonnes_co2eq_per_fte= total_tonnes / total_fte using headcount FTE - Verify year comparison works: previous year null when no data exists
- Verify a carbon report with zero validated modules returns null totals and empty
module_results - Frontend big numbers display real values, no more hardcoded strings