mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-09 07:25:44 +02:00
Fix monthly comparison calculation ignoring quantity multiplier (Issue #229)
The filament statistics were under-reporting totals because the quantity field was not being multiplied with filament_used_grams. When users printed multiple items (quantity > 1), only the base filament amount was counted instead of the total. Closes #229
This commit is contained in:
@@ -26,6 +26,13 @@ All notable changes to Bambuddy will be documented in this file.
|
||||
- Added locale parity test to ensure English and German stay in sync
|
||||
|
||||
### Fixed
|
||||
- **Monthly Comparison Calculation Off** (Issue #229):
|
||||
- Fixed filament statistics not accounting for quantity multiplier
|
||||
- Monthly comparison chart now correctly multiplies `filament_used_grams` by `quantity`
|
||||
- Daily and weekly charts also now account for quantity
|
||||
- Filament type breakdown includes quantity in calculations
|
||||
- Backend stats endpoint (`/archives/stats`) and Prometheus metrics also fixed
|
||||
- Prints count now shows total items (sum of quantities) instead of archive count
|
||||
- **Authentication Required for Downloads** (Issue #231):
|
||||
- Fixed support bundle download returning 401 Unauthorized when auth is enabled
|
||||
- Fixed archive export (CSV/XLSX) failing with authentication enabled
|
||||
|
||||
@@ -451,7 +451,10 @@ async def get_archive_stats(db: AsyncSession = Depends(get_db)):
|
||||
total_seconds += print_time_seconds
|
||||
total_time = total_seconds / 3600 # Convert to hours
|
||||
|
||||
filament_result = await db.execute(select(func.sum(PrintArchive.filament_used_grams)))
|
||||
# Multiply filament by quantity to account for multiple items printed
|
||||
filament_result = await db.execute(
|
||||
select(func.sum(PrintArchive.filament_used_grams * func.coalesce(PrintArchive.quantity, 1)))
|
||||
)
|
||||
total_filament = filament_result.scalar() or 0
|
||||
|
||||
cost_result = await db.execute(select(func.sum(PrintArchive.cost)))
|
||||
|
||||
@@ -362,11 +362,13 @@ async def get_metrics(
|
||||
)
|
||||
lines.append(f"bambuddy_printer_prints_total{labels} {count}")
|
||||
|
||||
# Total filament used
|
||||
# Total filament used (multiply by quantity to account for multiple items printed)
|
||||
lines.append("")
|
||||
lines.append("# HELP bambuddy_filament_used_grams Total filament used in grams")
|
||||
lines.append("# TYPE bambuddy_filament_used_grams counter")
|
||||
result = await db.execute(select(func.coalesce(func.sum(PrintArchive.filament_used_grams), 0)))
|
||||
result = await db.execute(
|
||||
select(func.coalesce(func.sum(PrintArchive.filament_used_grams * func.coalesce(PrintArchive.quantity, 1)), 0))
|
||||
)
|
||||
total_filament = result.scalar() or 0
|
||||
lines.append(f"bambuddy_filament_used_grams {total_filament:.1f}")
|
||||
|
||||
|
||||
@@ -61,9 +61,10 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
|
||||
const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
|
||||
const existing = dataMap.get(key) || { date: key, filament: 0, cost: 0, prints: 0 };
|
||||
existing.filament += archive.filament_used_grams || 0;
|
||||
const qty = archive.quantity || 1;
|
||||
existing.filament += (archive.filament_used_grams || 0) * qty;
|
||||
existing.cost += archive.cost || 0;
|
||||
existing.prints += 1;
|
||||
existing.prints += qty;
|
||||
dataMap.set(key, existing);
|
||||
});
|
||||
|
||||
@@ -89,9 +90,10 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
|
||||
const key = `${weekStart.getFullYear()}-${String(weekStart.getMonth() + 1).padStart(2, '0')}-${String(weekStart.getDate()).padStart(2, '0')}`;
|
||||
|
||||
const existing = dataMap.get(key) || { week: key, filament: 0, cost: 0, prints: 0 };
|
||||
existing.filament += archive.filament_used_grams || 0;
|
||||
const qty = archive.quantity || 1;
|
||||
existing.filament += (archive.filament_used_grams || 0) * qty;
|
||||
existing.cost += archive.cost || 0;
|
||||
existing.prints += 1;
|
||||
existing.prints += qty;
|
||||
dataMap.set(key, existing);
|
||||
});
|
||||
|
||||
@@ -110,10 +112,11 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
|
||||
|
||||
filteredArchives.forEach(archive => {
|
||||
const type = archive.filament_type || 'Unknown';
|
||||
const qty = archive.quantity || 1;
|
||||
// Handle multiple types (e.g., "PLA, PETG")
|
||||
const types = type.split(', ');
|
||||
types.forEach(t => {
|
||||
const grams = (archive.filament_used_grams || 0) / types.length;
|
||||
const grams = ((archive.filament_used_grams || 0) * qty) / types.length;
|
||||
dataMap.set(t, (dataMap.get(t) || 0) + grams);
|
||||
});
|
||||
});
|
||||
@@ -140,9 +143,9 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
|
||||
|
||||
months.push({
|
||||
month: monthStr,
|
||||
filament: Math.round(monthArchives.reduce((sum, a) => sum + (a.filament_used_grams || 0), 0)),
|
||||
filament: Math.round(monthArchives.reduce((sum, a) => sum + (a.filament_used_grams || 0) * (a.quantity || 1), 0)),
|
||||
cost: monthArchives.reduce((sum, a) => sum + (a.cost || 0), 0),
|
||||
prints: monthArchives.length,
|
||||
prints: monthArchives.reduce((sum, a) => sum + (a.quantity || 1), 0),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,8 +153,9 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
|
||||
}, [archives]);
|
||||
|
||||
const chartData = timeRange === '7d' || timeRange === '30d' ? dailyData : weeklyData;
|
||||
const totalFilament = filteredArchives.reduce((sum, a) => sum + (a.filament_used_grams || 0), 0);
|
||||
const totalFilament = filteredArchives.reduce((sum, a) => sum + (a.filament_used_grams || 0) * (a.quantity || 1), 0);
|
||||
const totalCost = filteredArchives.reduce((sum, a) => sum + (a.cost || 0), 0);
|
||||
const totalPrints = filteredArchives.reduce((sum, a) => sum + (a.quantity || 1), 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -185,17 +189,17 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
|
||||
<div className="bg-bambu-dark rounded-lg p-4">
|
||||
<p className="text-sm text-bambu-gray">Period Cost</p>
|
||||
<p className="text-2xl font-bold text-white">{currency}{totalCost.toFixed(2)}</p>
|
||||
<p className="text-xs text-bambu-gray">{filteredArchives.length} prints</p>
|
||||
<p className="text-xs text-bambu-gray">{totalPrints} prints</p>
|
||||
</div>
|
||||
<div className="bg-bambu-dark rounded-lg p-4">
|
||||
<p className="text-sm text-bambu-gray">Avg per Print</p>
|
||||
<p className="text-2xl font-bold text-white">
|
||||
{filteredArchives.length > 0
|
||||
? (totalFilament / filteredArchives.length).toFixed(0)
|
||||
{totalPrints > 0
|
||||
? (totalFilament / totalPrints).toFixed(0)
|
||||
: 0}g
|
||||
</p>
|
||||
<p className="text-xs text-bambu-gray">
|
||||
{currency}{filteredArchives.length > 0 ? (totalCost / filteredArchives.length).toFixed(2) : '0.00'} avg
|
||||
{currency}{totalPrints > 0 ? (totalCost / totalPrints).toFixed(2) : '0.00'} avg
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-BXCvE4aN.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-POsspFOS.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CPqcJWwC.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user