diff --git a/.env.install b/.env.install index 96b024d..0e81669 100644 --- a/.env.install +++ b/.env.install @@ -8,3 +8,5 @@ TZ=Europe/Berlin FILAMENTDB_URL=https://web.filamentdb.orb.local VAT_RATE=19 FILAMENT_PRICE_PER_KG=15 +MATERIAL_FACTOR=0.50 +PRINT_SPEED_G_PER_HOUR=32 diff --git a/app/estimation.py b/app/estimation.py index 80c274c..95d586c 100644 --- a/app/estimation.py +++ b/app/estimation.py @@ -1,7 +1,7 @@ """Job cost and time estimation from STL geometry.""" import os -# g/cm³ per material — used to convert volume → weight +# g/cm³ per material _DENSITY: dict[str, float] = { "PLA": 1.24, "PETG": 1.27, @@ -13,43 +13,46 @@ _DENSITY: dict[str, float] = { } _DEFAULT_DENSITY = 1.24 -# Conservative average FDM print speed for weight-based time estimate -_PRINT_SPEED_G_PER_HOUR = 20.0 - def estimate(volume_ccm: float, material: str | None) -> dict: """ Returns a cost/time estimate dict for one job. - Assumptions: - - 20 % infill → material used = volume * 0.20 - - filament density from _DENSITY (default 1.24 g/cm³) - - print speed: 20 g/h (conservative FDM average) - - filament cost: FILAMENT_PRICE_PER_KG EUR / kg (env, default 15) - - misprint buffer: +20 % - - gross profit margin: +30 % - - VAT: VAT_RATE % (env, default 19) + MATERIAL_FACTOR (env, default 0.50): + Fraction of model volume that becomes actual filament. + '20 % infill' in the slicer does NOT mean 20 % of volume is printed — + walls, top/bottom solid layers, and shells are 100 % solid and typically + dominate. Empirically ~50 % of model volume is printed for typical FDM + parts (validated against Bambu Lab Studio output). + + PRINT_SPEED_G_PER_HOUR (env, default 32): + Average material throughput in g/h. ~32 g/h matches Bambu Lab printers + at normal quality; slower printers (~20 g/h) can be set here. """ - vat_rate = float(os.environ.get("VAT_RATE", 19)) - price_per_kg = float(os.environ.get("FILAMENT_PRICE_PER_KG", 15)) - density = _DENSITY.get(material or "", _DEFAULT_DENSITY) + vat_rate = float(os.environ.get("VAT_RATE", 19)) + price_per_kg = float(os.environ.get("FILAMENT_PRICE_PER_KG", 15)) + material_factor = float(os.environ.get("MATERIAL_FACTOR", 0.50)) + print_speed = float(os.environ.get("PRINT_SPEED_G_PER_HOUR", 32)) + density = _DENSITY.get(material or "", _DEFAULT_DENSITY) - material_ccm = volume_ccm * 0.20 + material_ccm = volume_ccm * material_factor weight_g = material_ccm * density - print_minutes = round((weight_g / _PRINT_SPEED_G_PER_HOUR) * 60) + print_minutes = round((weight_g / print_speed) * 60) - filament_cost = weight_g / 1000 * price_per_kg + filament_cost = weight_g / 1000 * price_per_kg after_misprint = filament_cost * 1.20 - net_price = after_misprint * 1.30 - gross_price = net_price * (1 + vat_rate / 100) + net_price = after_misprint * 1.30 + gross_price = net_price * (1 + vat_rate / 100) return { - "material_ccm": round(material_ccm, 2), - "weight_g": round(weight_g, 1), - "print_minutes": print_minutes, - "filament_cost": round(filament_cost, 2), - "net_price": round(net_price, 2), - "gross_price": round(gross_price, 2), - "vat_rate": vat_rate, - "price_per_kg": price_per_kg, + "material_ccm": round(material_ccm, 2), + "weight_g": round(weight_g, 1), + "print_minutes": print_minutes, + "filament_cost": round(filament_cost, 2), + "net_price": round(net_price, 2), + "gross_price": round(gross_price, 2), + "vat_rate": vat_rate, + "price_per_kg": price_per_kg, + "material_factor": material_factor, + "print_speed": print_speed, } diff --git a/app/templates/job_detail.html b/app/templates/job_detail.html index 29e1820..3ea8280 100644 --- a/app/templates/job_detail.html +++ b/app/templates/job_detail.html @@ -140,7 +140,10 @@
Estimate - 20 % infill · {{ estimation.price_per_kg }} €/kg · {{ estimation.vat_rate }}% VAT + {{ (estimation.material_factor * 100) | round | int }}% material factor + · {{ estimation.print_speed | int }} g/h + · {{ estimation.price_per_kg }} €/kg + · {{ estimation.vat_rate }}% VAT