فهرست منبع

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
maziggy 3 ماه پیش
والد
کامیت
11456c0d2a

+ 7 - 0
CHANGELOG.md

@@ -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

+ 4 - 1
backend/app/api/routes/archives.py

@@ -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)))

+ 4 - 2
backend/app/api/routes/metrics.py

@@ -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}")
 

+ 16 - 12
frontend/src/components/FilamentTrends.tsx

@@ -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>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/assets/index-POsspFOS.js


+ 1 - 1
static/index.html

@@ -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>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است