Error
+Unable to load project data.
+${String(err)}
+ diff --git a/README.md b/README.md index 2023e68..b744273 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,45 @@ -# filament-drybox-cost +# Filament Drybox Cost Model -Ascertain the out-of-pocket costs to create one or more dryboxes. \ No newline at end of file +20260319 ChatGPT +$Header$ + +## Purpose + +This project provides a transparent and reproducible cost model for the +"Filament Storage 2026" drybox project by GunplaMark. + +The model is intended to answer questions such as: + +- What is the out-of-pocket cost to build 1 drybox? +- How does the per-unit cost change when building 4 dryboxes? +- How does the per-unit cost change when building 12 dryboxes? +- What inventory remains after each build scenario? +- How much do filament and electricity contribute to the total? + +The model accounts for: + +- lot-based purchasing +- per-box usage +- printed part filament cost +- electrical cost of printing +- optional waste factor + +See video at https://youtu.be/lJDoVH7qTKs?si=cQMhUda7RQcuIDJU by user GunplaMark entitled "Filament Storage 2026 - The Final Boss of My Drybox Journey". + + +## Repository Layout + +```text +. +├── LICENSE +├── README.md +├── data +│ ├── assumptions.json +│ └── bom.json +├── export +├── tools +│ └── generate_csv.pl +└── web + ├── app.js + ├── index.html + └── style.css diff --git a/data/assumptions.json b/data/assumptions.json new file mode 100644 index 0000000..0f75eab --- /dev/null +++ b/data/assumptions.json @@ -0,0 +1,33 @@ +{ + "currency": "USD", + "waste_factor": 1.15, + "electricity_cost_per_kwh": 0.12, + "printer_average_watts": 120, + "print_hours_per_box": 8.0, + "target_box_counts": [1, 4, 12], + "printed_materials": [ + { + "id": "petg", + "name": "PETG", + "cost_per_kg": 22.00, + "grams_per_box": 220 + }, + { + "id": "asa", + "name": "ASA", + "cost_per_kg": 28.00, + "grams_per_box": 90 + }, + { + "id": "tpu", + "name": "TPU", + "cost_per_kg": 26.00, + "grams_per_box": 20 + } + ], + "notes": [ + "Printed weights are initial estimates and should later be replaced with slicer-derived actuals.", + "Waste factor is applied to printed material cost only.", + "Electricity uses printer_average_watts multiplied by print_hours_per_box." + ] +} diff --git a/data/bom.json b/data/bom.json new file mode 100644 index 0000000..41691db --- /dev/null +++ b/data/bom.json @@ -0,0 +1,85 @@ +{ + "project_name": "Filament Drybox Cost Model", + "source_model": "Filament Storage 2026 by GunplaMark", + "source_url": "https://www.printables.com/", + "currency": "USD", + "components": [ + { + "id": "container", + "name": "4L cereal container", + "category": "hardware", + "unit_type": "each", + "pack_qty": 4, + "pack_cost": 39.99, + "units_used_per_box": 1, + "optional": false, + "notes": "Praki, Wildone, or Skroam style 4L container" + }, + { + "id": "bearing", + "name": "608 bearing", + "category": "hardware", + "unit_type": "each", + "pack_qty": 10, + "pack_cost": 11.99, + "units_used_per_box": 4, + "optional": false, + "notes": "Open, ungreased fidget-spinner style bearings" + }, + { + "id": "badge_reel", + "name": "badge reel", + "category": "hardware", + "unit_type": "each", + "pack_qty": 10, + "pack_cost": 14.99, + "units_used_per_box": 1, + "optional": false, + "notes": "Used to harvest spring" + }, + { + "id": "foam_cord", + "name": "3mm silicone foam cord", + "category": "consumable", + "unit_type": "meter", + "pack_qty": 10, + "pack_cost": 9.99, + "units_used_per_box": 0.70, + "optional": false, + "notes": "Length per box is an estimate; measure actual lid circumference" + }, + { + "id": "desiccant", + "name": "activated alumina desiccant", + "category": "consumable", + "unit_type": "gram", + "pack_qty": 2000, + "pack_cost": 24.99, + "units_used_per_box": 150, + "optional": false, + "notes": "Silica gel could be modeled as an alternative later" + }, + { + "id": "hygrometer", + "name": "rectangular hygrometer", + "category": "hardware", + "unit_type": "each", + "pack_qty": 4, + "pack_cost": 17.99, + "units_used_per_box": 1, + "optional": false, + "notes": "Common small rectangular hygrometer" + }, + { + "id": "nfc_tag", + "name": "25mm NFC tag", + "category": "optional", + "unit_type": "each", + "pack_qty": 20, + "pack_cost": 9.99, + "units_used_per_box": 1, + "optional": true, + "notes": "Optional spool identification tag" + } + ] +} diff --git a/tools/generate_cvs.pl b/tools/generate_cvs.pl new file mode 100755 index 0000000..b27de50 --- /dev/null +++ b/tools/generate_cvs.pl @@ -0,0 +1,21 @@ +#!/usr/bin/perl +# +# Example command lines: +# +# perl generate_csv.pl +# +# 20260319 ChatGPT +# $Header$ +# +# Purpose: +# Placeholder script for later CSV export from the BOM and assumptions data. +# + +use strict; +use warnings; + +print "generate_csv.pl: placeholder script\n"; +print "Later revision will read ../data/bom.json and ../data/assumptions.json\n"; +print "and produce a CSV summary under ../export/.\n"; + +exit 0; diff --git a/web/app.js b/web/app.js new file mode 100644 index 0000000..c592037 --- /dev/null +++ b/web/app.js @@ -0,0 +1,249 @@ +/* +20260319 ChatGPT +$Header$ +*/ + +"use strict"; + +function currency(value) { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD" + }).format(value); +} + +function ceilDivUsage(requiredUnits, packQty) { + return Math.ceil(requiredUnits / packQty); +} + +function computePrintingCost(assumptions, boxCount) { + let materialCost = 0; + + for (const material of assumptions.printed_materials) { + const totalGrams = material.grams_per_box * boxCount; + const totalKg = totalGrams / 1000.0; + materialCost += totalKg * material.cost_per_kg; + } + + materialCost *= assumptions.waste_factor; + + const totalKwh = + (assumptions.printer_average_watts / 1000.0) * + assumptions.print_hours_per_box * + boxCount; + + const electricityCost = totalKwh * assumptions.electricity_cost_per_kwh; + + return { + material_cost: materialCost, + electricity_cost: electricityCost, + total_printing_cost: materialCost + electricityCost + }; +} + +function computeHardwareScenario(bom, boxCount, includeOptional = true) { + const componentRows = []; + let totalHardwareCost = 0; + + for (const component of bom.components) { + if (!includeOptional && component.optional) { + continue; + } + + const requiredUnits = component.units_used_per_box * boxCount; + const packsRequired = ceilDivUsage(requiredUnits, component.pack_qty); + const purchasedUnits = packsRequired * component.pack_qty; + const leftoverUnits = purchasedUnits - requiredUnits; + const totalCost = packsRequired * component.pack_cost; + + totalHardwareCost += totalCost; + + componentRows.push({ + id: component.id, + name: component.name, + required_units: requiredUnits, + pack_qty: component.pack_qty, + pack_cost: component.pack_cost, + packs_required: packsRequired, + purchased_units: purchasedUnits, + leftover_units: leftoverUnits, + total_cost: totalCost, + optional: component.optional, + unit_type: component.unit_type, + notes: component.notes + }); + } + + return { + box_count: boxCount, + total_hardware_cost: totalHardwareCost, + component_rows: componentRows + }; +} + +function renderAssumptions(assumptions) { + const div = document.getElementById("assumptions"); + + const materialLines = assumptions.printed_materials.map((m) => { + return `
Printed materials
+| Component | +Required Units | +Pack Qty | +Pack Cost | +Packs Required | +Total Cost | +Notes | +
|---|
| Component | +Purchased Units | +Used Units | +Leftover Units | +Unit Type | +
|---|
Unable to load project data.
+${String(err)}
+ + Lot-based purchasing, filament cost, and electricity for 1, 4, and 12 dryboxes +
++ This page estimates the out-of-pocket cost of building filament dryboxes, + taking into account both purchased hardware sold in lots and the printing + cost of the required parts. +
+| Boxes | +Total Hardware | +Total Printing | +Total Cost | +Cost / Box | +
|---|