Introduce enums for items, machines, and recipes, and integrate recipe selection logic. Add .gitignore and improve Flask app for query-based input handling.

This commit is contained in:
2025-11-06 20:36:26 +00:00
parent 5536485a8c
commit 562b33dff9
6 changed files with 721 additions and 224 deletions

164
.gitignore vendored Normal file
View File

@@ -0,0 +1,164 @@
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

View File

@@ -1,7 +1,11 @@
<component name="ProjectDictionaryState"> <component name="ProjectDictionaryState">
<dictionary name="project"> <dictionary name="project">
<words> <words>
<w>aurovite</w>
<w>callanite</w> <w>callanite</w>
<w>caterium</w>
<w>larrussite</w>
<w>quickwire</w>
<w>siterite</w> <w>siterite</w>
</words> </words>
</dictionary> </dictionary>

8
.idea/pysatcalc.iml generated
View File

@@ -7,4 +7,12 @@
<orderEntry type="jdk" jdkName="uv (pysatcalc)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="uv (pysatcalc)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
</module> </module>

290
main.py
View File

@@ -1,201 +1,12 @@
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum from enum import Enum
from math import ceil from math import ceil
from typing import Dict, List, Tuple, Optional from typing import Dict, List, Tuple, Optional
from flask import Flask, render_template, request from flask import Flask, render_template, request
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from plus import Items, Machines, Recipes, Recipe
app = Flask(__name__) app = Flask(__name__)
class Item(BaseModel):
name: str
class Machine(BaseModel):
name: str
class Machines(Enum):
Miner = Machine(name="Miner")
Smelter = Machine(name="Smelter")
Constructor = Machine(name="Constructor")
Assembler = Machine(name="Assembler")
Sorter = Machine(name="Sorter")
Crusher = Machine(name="Crusher")
Foundry = Machine(name="Foundry")
class Items(Enum):
IronIngot = Item(name="Iron Ingot")
CopperIngot = Item(name="Copper Ingot")
Limestone = Item(name="Limestone")
IronOre = Item(name="Iron Ore")
CopperOre = Item(name="Copper Ore")
IronPlate = Item(name="Iron Plate")
IronRod = Item(name="Iron Rod")
Wire = Item(name="Wire")
Cable = Item(name="Cable")
Concrete = Item(name="Concrete")
ReinforcedIronPlate = Item(name="Reinforced Iron Plate")
ModularFrame = Item(name="Modular Frame")
BronzeBeam = Item(name="Bronze Beam")
TinPlate = Item(name="Tin Plate")
TinIngot = Item(name="Tin Ingot")
CrushedTin = Item(name="Crushed Tin")
CrushedIron = Item(name="Crushed Iron")
CrushedGangue = Item(name="Crushed Gangue")
CrushedSiterite = Item(name="Crushed Siterite")
SiteriteOre = Item(name="Siterite Ore")
Screws = Item(name="Screws")
BronzeIngot = Item(name="Bronze Ingot")
CrushedCopper = Item(name="Crushed Copper")
CrushedMagnesium = Item(name="Crushed Magnesium")
CrushedCallanite = Item(name="Crushed Callanite")
CallaniteOre = Item(name="Callanite Ore")
class RawResources(Enum):
IronOre = Items.IronOre
CopperOre = Items.CopperOre
Limestone = Items.Limestone
# --- Domain model (minimal, extensible) ---
class Recipe(BaseModel):
name: str # Human-friendly name
building: Machines # e.g., "Smelter", "Constructor"
outputs: Dict[Items, float] # Produced item name
inputs: Dict[Items, float] = Field(default_factory=dict)
# A very small starter dataset (default, non-alternate recipes)
# Rates are per building per minute, matching Satisfactory default recipes.
class Recipes(Enum):
ModularFrame = Recipe(
name="Modular Frame",
building=Machines.Assembler,
outputs={Items.ModularFrame: 6.0},
inputs={
Items.ReinforcedIronPlate: 7.5,
Items.BronzeBeam: 7.5,
},
)
ReinforcedIronPlate = Recipe(
name="Reinforced Iron Plate",
building=Machines.Assembler,
outputs={Items.ReinforcedIronPlate: 5.0},
inputs={
Items.TinPlate: 20.0,
Items.Screws: 37.5,
},
)
TinPlate = Recipe(
name="Tin Plate",
building=Machines.Assembler,
outputs={Items.TinPlate: 40.0},
inputs={
Items.IronPlate: 20.0,
Items.TinIngot: 30.0,
},
)
TinIngot = Recipe(
name="Tin Ingot",
building=Machines.Smelter,
outputs={Items.TinIngot: 15.0},
inputs={Items.CrushedTin: 15.0},
)
CrushedIron = Recipe(
name="Crushed Iron",
building=Machines.Sorter,
outputs={
Items.CrushedIron: 90.0,
Items.CrushedTin: 60.0,
Items.CrushedGangue: 40.0,
},
inputs={Items.CrushedSiterite: 120.0},
)
CrushedSiterite = Recipe(
name="Crushed Siterite",
building=Machines.Crusher,
outputs={
Items.CrushedSiterite: 60.0,
Items.CrushedGangue: 25.0,
},
inputs={Items.SiteriteOre: 60.0},
)
IronPlate = Recipe(
name="Iron Plate",
building=Machines.Constructor,
outputs={Items.IronPlate: 20.0},
inputs={Items.IronIngot: 30.0},
)
IronIngot = Recipe(
name="Iron Ingot",
building=Machines.Smelter,
outputs={Items.IronIngot: 30.0},
inputs={Items.CrushedIron: 30.0},
)
Screws = Recipe(
name="Screws",
building=Machines.Constructor,
outputs={Items.Screws: 50.0},
inputs={Items.IronRod: 20.0},
)
IronRod = Recipe(
name="Iron Rod",
building=Machines.Constructor,
outputs={Items.IronRod: 15.0},
inputs={Items.IronIngot: 15.0},
)
BronzeBeam = Recipe(
name="Bronze Beam",
building=Machines.Constructor,
outputs={Items.BronzeBeam: 7.5},
inputs={Items.BronzeIngot: 22.5},
)
BronzeIngot = Recipe(
name="Bronze Ingot",
building=Machines.Foundry,
outputs={Items.BronzeIngot: 45.0},
inputs={
Items.CopperIngot: 36.0,
Items.TinIngot: 15.0,
},
)
CopperIngot = Recipe(
name="Copper Ingot",
building=Machines.Smelter,
outputs={Items.CopperIngot: 48.0},
inputs={Items.CrushedCopper: 48.0},
)
CrushedCopper = Recipe(
name="Crushed Copper",
building=Machines.Sorter,
outputs={
Items.CrushedCopper: 96.0,
Items.CrushedMagnesium: 80.0,
Items.CrushedGangue: 40.0,
},
inputs={Items.CrushedCallanite: 120.0},
)
CrushedCallanite = Recipe(
name="Crushed Callanite",
building=Machines.Sorter,
outputs={
Items.CrushedCallanite: 60.0,
Items.CrushedGangue: 25.0,
},
inputs={Items.CallaniteOre: 60.0},
)
# Items which are considered raw resources (mined); they have no crafting recipes here
RAW_RESOURCES = {
Items.IronOre,
Items.CopperOre,
Items.Limestone,
Items.SiteriteOre,
}
def compute_chain(targets: Dict[Items, float]) -> Tuple[Dict[str, float], List[dict], Dict[str, float], Dict[str, float]]: def compute_chain(targets: Dict[Items, float]) -> Tuple[Dict[str, float], List[dict], Dict[str, float], Dict[str, float]]:
""" """
@@ -205,14 +16,43 @@ def compute_chain(targets: Dict[Items, float]) -> Tuple[Dict[str, float], List[d
- total production rates (echo of targets summed if multiple entries per item) - total production rates (echo of targets summed if multiple entries per item)
- unused byproducts (items/min) produced by multi-output recipes but not consumed or targeted - unused byproducts (items/min) produced by multi-output recipes but not consumed or targeted
Uses the Recipes enum and Items objects. Uses the Recipes enum and Items objects.
Now supports alternate recipes: when multiple recipes produce the same output item,
a selection heuristic is used unless an explicit preference is configured.
""" """
# Build a mapping from output item -> recipe that produces it (default recipe set) # Build a mapping from output item -> list of recipes that produce it
output_to_recipe: Dict[Items, Recipe] = {} output_to_recipes: Dict[Items, List[Recipe]] = {}
for r in Recipes: for r in Recipes:
recipe = r.value recipe = r.value
for out_item in recipe.outputs.keys(): for out_item in recipe.outputs.keys():
# prefer the first seen; can be extended to handle alternates later output_to_recipes.setdefault(out_item, []).append(recipe)
output_to_recipe.setdefault(out_item, recipe)
# Optional explicit preferences: map output Item -> recipe name to prefer
# Users can populate/modify this mapping elsewhere if desired.
PREFERRED_RECIPE_BY_OUTPUT: Dict[Items, str] = {}
# Heuristic to select a recipe when multiple alternatives exist
def select_recipe_for(item: Items) -> Optional[Recipe]:
candidates = output_to_recipes.get(item, [])
if not candidates:
return None
# If explicit preference exists and matches a candidate, use it
pref_name = PREFERRED_RECIPE_BY_OUTPUT.get(item)
if pref_name:
for c in candidates:
if c.name == pref_name:
return c
# Otherwise pick the candidate with the highest per-building output for this item
# Tie-breaker 1: smallest total input per unit of this output
# Tie-breaker 2: deterministic by name
def score(c: Recipe) -> Tuple[float, float, str]:
per_build_out = c.outputs.get(item, 0.0)
total_input = sum(c.inputs.values())
# Lower input per unit is better; we express as (total_input/per_build_out)
# Protect against division by zero
eff = float('inf') if per_build_out <= 0 else (total_input / per_build_out)
return (per_build_out, -eff, c.name)
return sorted(candidates, key=score, reverse=True)[0]
# Aggregate demands for each item # Aggregate demands for each item
demand: Dict[Items, float] = {} demand: Dict[Items, float] = {}
@@ -236,18 +76,24 @@ def compute_chain(targets: Dict[Items, float]) -> Tuple[Dict[str, float], List[d
# Expand demanded craftable items into their inputs until only raw remain # Expand demanded craftable items into their inputs until only raw remain
while True: while True:
craftable_item = next( craftable_item = next(
(i for i, r in demand.items() if r > 1e-9 and i in output_to_recipe), (i for i, r in demand.items() if r > 1e-9 and i in output_to_recipes),
None, None,
) )
if craftable_item is None: if craftable_item is None:
break break
needed_rate = demand[craftable_item] needed_rate = demand[craftable_item]
recipe = output_to_recipe[craftable_item] recipe = select_recipe_for(craftable_item)
if recipe is None:
# Should not happen because craftable_item is in output_to_recipes,
# but guard anyway: treat as raw if selection failed.
demand[craftable_item] = 0.0
raw_requirements[craftable_item.value.name] = raw_requirements.get(craftable_item.value.name, 0.0) + needed_rate
continue
per_building_output = recipe.outputs[craftable_item] per_building_output = recipe.outputs[craftable_item]
# Buildings needed # Buildings needed
buildings = needed_rate / per_building_output buildings = needed_rate / per_building_output if per_building_output > 0 else 0.0
buildings_ceiled = ceil(buildings - 1e-9) buildings_ceiled = ceil(buildings - 1e-9)
utilization = 0.0 if buildings_ceiled == 0 else buildings / buildings_ceiled utilization = 0.0 if buildings_ceiled == 0 else buildings / buildings_ceiled
@@ -282,11 +128,7 @@ def compute_chain(targets: Dict[Items, float]) -> Tuple[Dict[str, float], List[d
for item, rate in demand.items(): for item, rate in demand.items():
if rate <= 1e-9: if rate <= 1e-9:
continue continue
if item in RAW_RESOURCES or item not in output_to_recipe: raw_requirements[item.value.name] = raw_requirements.get(item.value.name, 0.0) + rate
raw_requirements[item.value.name] = raw_requirements.get(item.value.name, 0.0) + rate
else:
# Shouldn't happen, but guard
raw_requirements[item.value.name] = raw_requirements.get(item.value.name, 0.0) + rate
# Merge steps for same item/building # Merge steps for same item/building
merged: Dict[Tuple[str, str], dict] = {} merged: Dict[Tuple[str, str], dict] = {}
@@ -321,7 +163,7 @@ def compute_chain(targets: Dict[Items, float]) -> Tuple[Dict[str, float], List[d
return raw_requirements, merged_steps, total_outputs, unused_byproducts return raw_requirements, merged_steps, total_outputs, unused_byproducts
@app.route("/", methods=["GET", "POST"]) @app.route("/", methods=["GET"])
def index(): def index():
# Build selectable items list from Items enum (display names) # Build selectable items list from Items enum (display names)
item_names = sorted([i.value.name for i in Items]) item_names = sorted([i.value.name for i in Items])
@@ -332,9 +174,11 @@ def index():
selected_item = item_names[0] if item_names else "" selected_item = item_names[0] if item_names else ""
selected_rate = 60.0 selected_rate = 60.0
if request.method == "POST": # Read from query parameters for bookmarkable URLs
item_name = request.form.get("item") or selected_item item_name = request.args.get("item") or selected_item
rate_str = request.form.get("rate") rate_str = request.args.get("rate")
rate = None
if rate_str is not None and rate_str != "":
try: try:
rate = float(rate_str) rate = float(rate_str)
if rate < 0: if rate < 0:
@@ -343,24 +187,24 @@ def index():
error = "Please enter a valid non-negative number for rate (items per minute)." error = "Please enter a valid non-negative number for rate (items per minute)."
rate = None rate = None
selected_item = item_name selected_item = item_name
if rate is not None: if rate is not None:
selected_rate = rate selected_rate = rate
if not error and item_name and rate is not None: if not error and item_name and rate is not None:
item_obj = name_to_item.get(item_name) item_obj = name_to_item.get(item_name)
if item_obj is None: if item_obj is None:
error = "Unknown item selected." error = "Unknown item selected."
else: else:
targets = {item_obj: rate} targets = {item_obj: rate}
raw, steps, outputs, unused = compute_chain(targets) raw, steps, outputs, unused = compute_chain(targets)
result = { result = {
"targets": {item_name: rate}, "targets": {item_name: rate},
"raw": raw, "raw": raw,
"steps": steps, "steps": steps,
"outputs": outputs, "outputs": outputs,
"unused": unused, "unused": unused,
} }
return render_template( return render_template(
"index.html", "index.html",

477
plus.py Normal file
View File

@@ -0,0 +1,477 @@
from enum import Enum
from typing import Dict
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
class Machine(BaseModel):
name: str
class Machines(Enum):
Miner = Machine(name="Miner")
Smelter = Machine(name="Smelter")
Constructor = Machine(name="Constructor")
Assembler = Machine(name="Assembler")
Sorter = Machine(name="Sorter")
Crusher = Machine(name="Crusher")
Foundry = Machine(name="Foundry")
class Items(Enum):
IronIngot = Item(name="Iron Ingot")
CopperIngot = Item(name="Copper Ingot")
Limestone = Item(name="Limestone")
IronOre = Item(name="Iron Ore")
CopperOre = Item(name="Copper Ore")
IronPlate = Item(name="Iron Plate")
IronRod = Item(name="Iron Rod")
Wire = Item(name="Wire")
Cable = Item(name="Cable")
Concrete = Item(name="Concrete")
ReinforcedIronPlate = Item(name="Reinforced Iron Plate")
ModularFrame = Item(name="Modular Frame")
BronzeBeam = Item(name="Bronze Beam")
TinPlate = Item(name="Tin Plate")
TinIngot = Item(name="Tin Ingot")
CrushedTin = Item(name="Crushed Tin")
CrushedIron = Item(name="Crushed Iron")
CrushedGangue = Item(name="Crushed Gangue")
CrushedSiterite = Item(name="Crushed Siterite")
SiteriteOre = Item(name="Siterite Ore")
Screws = Item(name="Screws")
BronzeIngot = Item(name="Bronze Ingot")
CrushedCopper = Item(name="Crushed Copper")
CrushedMagnesium = Item(name="Crushed Magnesium")
CrushedCallanite = Item(name="Crushed Callanite")
CallaniteOre = Item(name="Callanite Ore")
CrushedLarrussite = Item(name="Crushed Larrussite")
CrushedAurovite = Item(name="Crushed Aurovite")
AuroviteOre = Item(name="Aurovite Ore")
Sand = Item(name="Sand")
MagnesiumGranules = Item(name="Magnesium Granules")
CrushedZinc = Item(name="Crushed Zinc")
LarrussiteOre = Item(name="Larrussite Ore")
ZincIngot = Item(name="Zinc Ingot")
Glass = Item(name="Glass")
IronSheet = Item(name="Iron Sheet")
CopperRod = Item(name="Copper Rod")
CopperSheet = Item(name="Copper Sheet")
BronzePlates = Item(name="Bronze Plates")
CateriumRod = Item(name="Caterium Rod")
CateriumIngot = Item(name="Caterium Ingot")
CateriumPlate = Item(name="Caterium Plate")
TinRod = Item(name="Tin Rod")
ZincPlates = Item(name="Zinc Plates")
IronWire = Item(name="Iron Wire")
CopperBusbars = Item(name="Copper Busbars")
Quickwire = Item(name="Quickwire")
TinnedWire = Item(name="Tinned Wire")
SolarCell = Item(name="Solar Cell")
Silica = Item(name="Silica")
BronzePipes = Item(name="Bronze Pipes")
SmoothBeltDrive = Item(name="Smooth Belt Drive")
TinnedSheet = Item(name="Tinned Sheet")
Rotor = Item(name="Rotor")
BronzeFrame = Item(name="Bronze Frame")
AILimiter = Item(name="AI Limiter")
class Recipe(BaseModel):
name: str # Human-friendly name
building: Machines # e.g., "Smelter", "Constructor"
outputs: Dict[Items, float] # Produced item name
inputs: Dict[Items, float] = Field(default_factory=dict)
class Recipes(Enum):
# Crusher
# - Crushing Ores
CrushedSiterite = Recipe(
name="Crushed Siterite",
building=Machines.Crusher,
outputs={
Items.CrushedSiterite: 60.0,
Items.CrushedGangue: 25.0,
},
inputs={Items.SiteriteOre: 60.0},
)
CrushedLarrussite = Recipe(
name="Crushed Larrussite",
building=Machines.Crusher,
outputs={
Items.CrushedLarrussite: 40.0,
Items.CrushedGangue: 20.0,
},
inputs={Items.LarrussiteOre: 60.0},
)
CrushedCallanite = Recipe(
name="Crushed Callanite",
building=Machines.Crusher,
outputs={
Items.CrushedCallanite: 60.0,
Items.CrushedGangue: 25.0,
},
inputs={Items.CallaniteOre: 60.0},
)
CrushedAurovite = Recipe(
name="Crushed Aurovite",
building=Machines.Crusher,
outputs={
Items.CrushedAurovite: 60.0,
Items.CrushedGangue: 22.5,
},
inputs={Items.AuroviteOre: 60.0},
)
# - Crushing Powders
CoarseSand = Recipe(
name="Coarse Sand",
building=Machines.Crusher,
outputs={
Items.Sand: 30.0
},
inputs={Items.CrushedGangue: 60.0},
)
MagnesiumGranules = Recipe(
name="Magnesium Granules",
building=Machines.Crusher,
outputs={
Items.MagnesiumGranules: 72.0
},
inputs={Items.CrushedMagnesium: 80.0},
)
# Sorter
# - Simple Sorting
CrushedIron = Recipe(
name="Crushed Iron",
building=Machines.Sorter,
outputs={
Items.CrushedIron: 90.0,
Items.CrushedTin: 60.0,
Items.CrushedGangue: 40.0,
},
inputs={Items.CrushedSiterite: 120.0},
)
CrushedCopper = Recipe(
name="Crushed Copper",
building=Machines.Sorter,
outputs={
Items.CrushedCopper: 96.0,
Items.CrushedMagnesium: 80.0,
Items.CrushedGangue: 40.0,
},
inputs={Items.CrushedCallanite: 120.0},
)
CrushedZinc = Recipe(
name="Crushed Zinc",
building=Machines.Sorter,
outputs={
Items.CrushedZinc: 96.0,
Items.CrushedMagnesium: 48.0,
Items.CrushedGangue: 60.0,
},
inputs={Items.CrushedLarrussite: 120.0},
)
# Smelter
# - smelting
IronIngot = Recipe(
name="Iron Ingot",
building=Machines.Smelter,
outputs={Items.IronIngot: 30.0},
inputs={Items.CrushedIron: 30.0},
)
CopperIngot = Recipe(
name="Copper Ingot",
building=Machines.Smelter,
outputs={Items.CopperIngot: 48.0},
inputs={Items.CrushedCopper: 48.0},
)
TinIngot = Recipe(
name="Tin Ingot",
building=Machines.Smelter,
outputs={Items.TinIngot: 15.0},
inputs={Items.CrushedTin: 15.0},
)
# - Ingots
ImpureIronIngot = Recipe(
name="Impure Iron Ingot",
building=Machines.Smelter,
outputs={Items.IronIngot: 30.0},
inputs={Items.CrushedSiterite: 30.0},
)
ImpureCopperIngot = Recipe(
name="Impure Copper Ingot",
building=Machines.Smelter,
inputs={Items.CrushedCallanite: 24.0},
outputs={Items.CopperIngot: 24.0},
)
ImpureTinIngot = Recipe(
name="Impure Tin Ingot",
building=Machines.Smelter,
inputs={Items.CrushedSiterite: 30.0},
outputs={Items.TinIngot: 15.0},
)
ImpureCateriumIngot = Recipe(
name="Impure Caterium Ingot",
building=Machines.Smelter,
inputs={Items.CrushedAurovite: 40.0},
outputs={Items.CopperIngot: 24.0},
)
ZincIngot = Recipe(
name="Zinc Ingot",
building=Machines.Smelter,
outputs={Items.ZincIngot: 15.0},
inputs={Items.CrushedZinc: 24.0},
)
# - Standard Parts
SloppyGlass = Recipe(
name="Sloppy Glass",
building=Machines.Smelter,
outputs={Items.Glass: 20.0},
inputs={Items.Sand: 30.0},
)
# Constructor
# - Standard Parts
IronPlate = Recipe(
name="Iron Plate",
building=Machines.Constructor,
outputs={Items.IronPlate: 20.0},
inputs={Items.IronIngot: 30.0},
)
Screws = Recipe(
name="Screws",
building=Machines.Constructor,
outputs={Items.Screws: 50.0},
inputs={Items.IronRod: 20.0},
)
IronRod = Recipe(
name="Iron Rod",
building=Machines.Constructor,
outputs={Items.IronRod: 15.0},
inputs={Items.IronIngot: 15.0},
)
IronSheet = Recipe(
name="Iron Sheet",
building=Machines.Constructor,
outputs={Items.IronSheet: 50.0},
inputs={Items.IronPlate: 40.0},
)
CopperRod = Recipe(
name="Copper Rod",
building=Machines.Constructor,
outputs={Items.CopperRod: 18.0},
inputs={Items.CopperIngot: 12.0},
)
CopperSheet = Recipe(
name="Copper Sheet",
building=Machines.Constructor,
outputs={Items.CopperSheet: 30.0},
inputs={Items.CopperIngot: 24.0},
)
BronzeBeam = Recipe(
name="Bronze Beam",
building=Machines.Constructor,
outputs={Items.BronzeBeam: 7.5},
inputs={Items.BronzeIngot: 22.5},
)
BronzePlates = Recipe(
name="Bronze Plates",
building=Machines.Constructor,
outputs={Items.BronzePlates: 24.0},
inputs={Items.BronzeIngot: 30.0},
)
CateriumRod = Recipe(
name="Caterium Rod",
building=Machines.Constructor,
outputs={Items.CateriumRod: 32.0},
inputs={Items.CateriumIngot: 16.0},
)
CateriumPlate = Recipe(
name="Caterium Plate",
building=Machines.Constructor,
outputs={Items.CateriumPlate: 36.0},
inputs={Items.CateriumIngot: 60.0},
)
TinRod = Recipe(
name="Tin Rod",
building=Machines.Constructor,
outputs={Items.TinRod: 15.0},
inputs={Items.TinIngot: 30.0},
)
ZincPlates = Recipe(
name="Zinc Plates",
building=Machines.Constructor,
outputs={Items.ZincPlates: 15.0},
inputs={Items.ZincIngot: 12.5},
)
# - Electronics
IronWire = Recipe(
name="Iron Wire",
building=Machines.Constructor,
outputs={Items.IronWire: 30.0},
inputs={Items.IronRod: 20.0},
)
Wire = Recipe(
name="Wire",
building=Machines.Constructor,
outputs={Items.Wire: 30.0},
inputs={Items.CopperRod: 18.0},
)
CopperBusbars = Recipe(
name="Copper Busbars",
building=Machines.Constructor,
outputs={Items.CopperBusbars: 15.0},
inputs={Items.CopperRod: 24.0},
)
Cable = Recipe(
name="Cable",
building=Machines.Constructor,
outputs={Items.Cable: 30.0},
inputs={Items.Wire: 60.0},
)
Quickwire = Recipe(
name="Quickwire",
building=Machines.Constructor,
outputs={Items.Quickwire: 80.0},
inputs={Items.CateriumRod: 24.0},
)
PureTinWire = Recipe(
name="Pure Tin Wire",
building=Machines.Constructor,
outputs={Items.TinnedWire: 11.25},
inputs={Items.TinRod: 15.0},
)
# - Compounds
Concrete = Recipe(
name="Concrete",
building=Machines.Constructor,
outputs={Items.Concrete: 15.0},
inputs={Items.CrushedGangue: 45.0},
)
# Foundry
# - Building Parts
SolarCell = Recipe(
name="Solar Cell",
building=Machines.Foundry,
outputs={Items.SolarCell: 11.25},
inputs={
Items.ZincPlates: 22.5,
Items.Silica: 67.5,
},
)
# - Alloys
BronzePipes = Recipe(
name="Bronze Pipes",
building=Machines.Foundry,
outputs={Items.BronzePipes: 36.0},
inputs={
Items.CopperIngot: 24.0,
Items.TinIngot: 15.0,
},
)
# - ingots
BronzeIngot = Recipe(
name="Bronze Ingot",
building=Machines.Foundry,
outputs={Items.BronzeIngot: 45.0},
inputs={
Items.CopperIngot: 36.0,
Items.TinIngot: 15.0,
},
)
# - Other
ThermalSilica = Recipe(
name="Thermal Silica",
building=Machines.Foundry,
outputs={Items.Silica: 45.0},
inputs={
Items.Sand: 30.0,
Items.MagnesiumGranules: 27.0,
},
)
# Assembler
# - Building Parts
SmoothBeltDrive = Recipe(
name="Smooth Belt Drive",
building=Machines.Assembler,
outputs={Items.SmoothBeltDrive: 18.75},
inputs={
Items.TinnedSheet: 15.0,
Items.Rotor: 3.75,
},
)
# - Standard Parts
TinPlate = Recipe(
name="Tin Plate",
building=Machines.Assembler,
outputs={Items.TinPlate: 40.0},
inputs={
Items.TinIngot: 30.0,
Items.IronPlate: 20.0,
},
)
TinnedWire = Recipe(
name="Tinned Wire",
building=Machines.Assembler,
outputs={Items.TinnedWire: 90},
inputs={
Items.Wire: 60.0,
Items.TinRod: 45.0,
},
)
TinnedSheet = Recipe(
name="Tinned Sheet",
building=Machines.Assembler,
outputs={Items.TinnedSheet: 40.0},
inputs={
Items.TinIngot: 20.0,
Items.CopperSheet: 20.0,
},
)
Rotor = Recipe(
name="Rotor",
building=Machines.Assembler,
outputs={Items.Rotor: 7.5},
inputs={
Items.CopperBusbars: 22.5,
Items.IronWire: 60.0,
},
)
ReinforcedIronPlate = Recipe(
name="Reinforced Iron Plate",
building=Machines.Assembler,
outputs={Items.ReinforcedIronPlate: 5.0},
inputs={
Items.TinPlate: 20.0,
Items.Screws: 37.5,
},
)
BronzeFrame = Recipe(
name="Bronze Frame",
building=Machines.Assembler,
outputs={Items.BronzeFrame: 5.0},
inputs={
Items.BronzeBeam: 10.0,
Items.BronzePipes: 24.0,
},
)
ModularFrame = Recipe(
name="Modular Frame",
building=Machines.Assembler,
outputs={Items.ModularFrame: 6.0},
inputs={
Items.ReinforcedIronPlate: 7.5,
Items.BronzeBeam: 7.5,
},
)
AILimiter = Recipe(
name="AI Limiter",
building=Machines.Assembler,
outputs={Items.AILimiter: 6.0},
inputs={
Items.CateriumPlate: 24.0,
Items.TinnedWire: 18.0,
},
)

View File

@@ -33,7 +33,7 @@
<h1>Satisfactory Production Calculator</h1> <h1>Satisfactory Production Calculator</h1>
<p>Compute buildings and raw inputs for a target output rate (items per minute). Data set includes a few early-game default recipes; you can extend it in <code>main.py</code>.</p> <p>Compute buildings and raw inputs for a target output rate (items per minute). Data set includes a few early-game default recipes; you can extend it in <code>main.py</code>.</p>
<form class="card" method="post"> <form class="card" method="get">
<div class="row"> <div class="row">
<div> <div>
<label for="item">Product</label> <label for="item">Product</label>