Compare commits

..

9 Commits

Author SHA1 Message Date
67ade78747 testing 2025-11-12 17:18:16 +00:00
9f4ff856e6 checkpoint 2025-11-11 21:24:54 +00:00
47bef8cbe4 testing 2025-11-11 19:27:55 +00:00
adee0f5ffb checkpoint 2025-11-11 18:01:55 +00:00
9096d38f78 checkpoint 2025-11-09 17:40:11 +00:00
f9e1ad0768 checkpoint 2025-11-09 08:52:03 +00:00
11fe895274 checkpoint 2025-11-08 21:51:39 +00:00
df2bf7058c checkpoint 2025-11-08 09:55:28 +00:00
bdd0d26acb testing 2025-11-07 17:07:52 +00:00
10 changed files with 2234 additions and 179 deletions

View File

@@ -4,9 +4,12 @@
<w>aurovite</w> <w>aurovite</w>
<w>callanite</w> <w>callanite</w>
<w>caterium</w> <w>caterium</w>
<w>heatsink</w>
<w>kyalite</w>
<w>larrussite</w> <w>larrussite</w>
<w>quickwire</w> <w>quickwire</w>
<w>siterite</w> <w>siterite</w>
<w>tailings</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

6
.idea/ruff.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RuffConfigService">
<option name="globalRuffExecutablePath" value="c:\users\peterdwyer\.local\bin\ruff.exe" />
</component>
</project>

799
main.py
View File

@@ -1,14 +1,18 @@
from collections import defaultdict, deque from collections import defaultdict, deque
from enum import Enum from enum import Enum
from math import ceil from math import ceil
from typing import Dict, List, Tuple, Optional, Any from typing import Dict, List, Tuple, Optional, Any, Deque, Annotated
import networkx as nx import networkx as nx
from flask import Flask, render_template, request from flask import Flask, render_template, request, jsonify
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, computed_field, BeforeValidator
from plus import Items, Machines, Recipes, Recipe from plus import Items, Machines, Recipes, Recipe, RawResources, MachineOrder
# from vanilla import Items, Machines, Recipes, Recipe, RawResources
from rich import print from rich import print
import matplotlib.pyplot as plt
app = Flask(__name__) app = Flask(__name__)
# Helpers to map item names to safe query parameter keys # Helpers to map item names to safe query parameter keys
def _slugify(name: str) -> str: def _slugify(name: str) -> str:
s = ''.join(ch.lower() if ch.isalnum() else '_' for ch in name) s = ''.join(ch.lower() if ch.isalnum() else '_' for ch in name)
@@ -25,57 +29,263 @@ def _slugify(name: str) -> str:
prev_us = False prev_us = False
return ''.join(out).strip('_') return ''.join(out).strip('_')
class ProductionLink(BaseModel):
item: Items
quantity: float
production: Optional[Production]
raw: bool = False
def positive_float(number: float) -> float:
return max(0.0, number)
class Production(BaseModel): class Production(BaseModel):
recipe: Recipe recipe: Recipe
quantity: float ingress: Dict[Items, List[ProductionLink]] = Field(default_factory=lambda : defaultdict(list))
egress: Dict[Items, List[ProductionLink]] = Field(
default_factory=lambda: defaultdict(list)
)
demand: Dict[Items, Annotated[float, BeforeValidator(positive_float)]] = Field(default_factory=lambda: defaultdict(float))
@computed_field
@property
def production_level(self) -> float:
levels = []
for item, quantity in self.demand.items():
levels.append(quantity/(self.recipe.outputs|self.recipe.byproducts)[item])
return max(levels) if levels else 0.0
@computed_field
@property
def inputs(self) -> Dict[Items, float]:
return {
item: quantity*self.production_level for item, quantity in self.recipe.inputs.items()
}
@computed_field
@property
def outputs(self) -> Dict[Items, float]:
return {
item: quantity*self.production_level for item, quantity in self.recipe.outputs.items() | self.recipe.byproducts.items()
}
def demand_satisfied(self):
if self.production_level <= 0:
return True
current_demand = self.demand.copy()
for item in self.ingress:
for link in self.ingress[item]:
current_demand[item] -= link.quantity
return all(current_demand[item] <= 0 for item in current_demand)
def remaining_output(self):
outputs = self.outputs
for item in self.egress:
for link in self.egress[item]:
outputs[item] -= link.quantity
return outputs
def remaining_input(self):
inputs = self.inputs
for item in self.ingress:
for link in self.ingress[item]:
inputs[item] -= link.quantity
return inputs
def get_recipe(item: Items, recipe_map: Dict[Items, list[Recipes]]) -> Optional[Recipe]:
return recipe_map.get(item, (None,))[0]
def compute_chain2(targets: Dict[Items, float]) -> Any: class ProductionChain:
if not targets: def __init__(self, preferred_recipes: Optional[Dict[Items, Recipe]] = None):
return {} self.item_to_recipies: Dict[Items, list[Recipes]] = defaultdict(list)
recipe_map: Dict[Items, list[Recipes]] = defaultdict(list)
for r in Recipes: for r in Recipes:
for o in r.value.outputs: for o in r.value.outputs:
recipe_map[o].append(r.value) self.item_to_recipies[o].append(r.value)
# print(recipe_map) self.recipe_name_to_obj: Dict[str, Recipe] = {}
for r in Recipes:
self.recipe_name_to_obj[r.value.name] = r.value
self.demand: Dict[Items, float] = defaultdict(float)
self.production: Dict[Items, float] = defaultdict(float)
self.raw_resources: Dict[Items, float] = defaultdict(float)
self.production_chain: Dict[str, float] = defaultdict(float)
self.excess: Dict[Items, float] = defaultdict(float)
self.byproduct: Dict[Items, float] = defaultdict(float)
self.preferred_recipes = preferred_recipes or {}
self.preferred_recipes[Items.Steam] = Recipes.SteamMk1.value
# self.preferred_recipes[Items.Sand] = Recipes.CoarseSand.value
def get_recipe(self, item: Items) -> Optional[Recipe]:
if item in self.preferred_recipes:
return self.preferred_recipes[item]
return self.item_to_recipies.get(item, (None,))[0]
def calculate_excess(self, production: Dict[Items, float], demand: Dict[Items, float], raw_resources: Dict[Items, float]):
excess = defaultdict(float)
for item, quantity in demand.items():
total = production[item] - demand[item]
if total != 0:
if item in RawResources:
raw_resources[item] -= production[item]
else:
excess[item] = total
return excess
def calculate_byproduct(self, production: Dict[Items, float], demand: Dict[Items, float], raw_resources: Dict[Items, float]):
byproduct = defaultdict(float)
for item, quantity in production.items():
if item not in demand:
byproduct[item] = quantity
return byproduct
def compute_chain(self, targets: Dict[Items, float]) -> Any:
if not targets:
return {}
demand = defaultdict(float) demand = defaultdict(float)
for target in targets: for target in targets:
demand[target] = targets[target] demand[target] = targets[target]
production = defaultdict(float)
# add demands to production
# find a recipe for that demand
# add inputs to demand
queue = deque(targets) queue = deque(targets)
g = nx.DiGraph() production = defaultdict(float)
production_queue = []
raw_resources = defaultdict(float) raw_resources = defaultdict(float)
production_chain = defaultdict(float) production_chain = defaultdict(float)
while queue: while queue:
item = queue.popleft() item = queue.popleft()
recipe = get_recipe(item, recipe_map) if item == Items.Silica:
if recipe is None: print("silica")
recipe = self.get_recipe(item)
if item in RawResources:
raw_resources[item] += demand[item] - raw_resources[item] raw_resources[item] += demand[item] - raw_resources[item]
continue continue
if item in production:
if production[item] == demand[item]:
continue
levels = [] levels = []
for out, quantity in recipe.outputs.items(): for out, quantity in recipe.outputs.items():
if out in demand: if out in demand:
target_quantity = demand[out] - production[out] target_quantity = demand[out] - production[out]
if target_quantity > 0:
levels.append(target_quantity / quantity) levels.append(target_quantity / quantity)
else:
levels.append(0) production_level = max(levels)
production_level = max(levels) if max(levels) > 0 else 0 if production_level == 0:
production_chain[recipe.name] = production_level continue
production_chain[recipe.name] += production_level
if production_chain[recipe.name] < 0:
del(production_chain[recipe.name])
production_level = 0
for out, quantity in recipe.outputs.items(): for out, quantity in recipe.outputs.items():
production[out] += production_level * quantity production[out] += production_level * quantity
for byproduct, quantity in recipe.byproducts.items():
production[byproduct] += production_level * quantity
queue.append(byproduct)
for inp, quantity in recipe.inputs.items(): for inp, quantity in recipe.inputs.items():
queue.append(inp) queue.append(inp)
demand[inp] += production_level * quantity demand[inp] += production_level * quantity
print(demand, production, raw_resources, production_chain)
return production excess = self.calculate_excess(production, demand, raw_resources)
byproduct =self.calculate_byproduct(production, demand, raw_resources)
print("demand:", demand)
print("production:", production)
print("excess:", excess)
print("byproduct:", byproduct)
print("raw resources:", raw_resources)
print("production chain:", production_chain)
self.demand = demand
self.production = production
self.raw_resources = raw_resources
self.production_chain = production_chain
self.excess = excess
self.byproduct = byproduct
# self.generate_graph(production_chain, production, raw_resources, excess, byproduct)
return production_chain
def build_chain(self, targets: Dict[Items, float]) -> Any:
if not targets:
return {}
demand = defaultdict(float)
item_to_production: Dict[Items, List[Production]] = defaultdict(list)
queue: Deque[Items] = deque()
for target in targets:
recipe = self.get_recipe(target)
p = Production(recipe=recipe)
p.demand[target] = targets[target]
p.egress[target].append(ProductionLink(item=target, quantity=targets[target], raw=True, production=None))
queue.extend(p.inputs)
for output in p.outputs:
item_to_production[output].append(p)
while queue:
item = queue.popleft()
if item in item_to_production or item in RawResources:
continue
recipe = self.get_recipe(item)
p = Production(recipe=recipe)
for output in p.outputs:
item_to_production[output].append(p)
queue.extend(p.inputs)
calc_queue: Deque[Production] = deque()
for item in item_to_production:
for production in item_to_production[item]:
if not production.demand_satisfied():
calc_queue.append(production)
print(calc_queue)
while calc_queue:
prod = calc_queue.popleft()
if prod.demand_satisfied():
continue
for inp in prod.inputs:
if inp in RawResources:
if prod.remaining_input()[inp] <= 0:
continue
prod.ingress[inp].append(ProductionLink(item=inp, quantity=prod.remaining_input()[inp], production=None, raw=True))
else:
prod_list = item_to_production[inp]
prod_source = None
if len(prod_list) == 1:
prod_source = prod_list[0]
else:
for prod_list_item in prod_list:
if self.get_recipe(inp) == prod_list_item.recipe:
prod_source = prod_list_item
break
if not prod_source:
continue
current_quantity = prod_source.remaining_output()[inp]
target_quantity = prod.remaining_input()[inp]
if current_quantity >= target_quantity:
prod.ingress[inp].append(
ProductionLink(
item=inp,
quantity=target_quantity,
production=prod_source,
raw=False,
)
)
prod_source.egress[inp].append(
ProductionLink(
item=inp,
quantity=target_quantity,
production=prod,
raw=False,
)
)
else:
pass
if target_quantity > 0:
prod_source.demand[inp] += target_quantity
prod.ingress[inp].append(ProductionLink(item=inp, quantity=target_quantity, production=prod_source, raw=False))
prod_source.egress[inp].append(ProductionLink(item=inp, quantity=target_quantity, production=prod, raw=False))
calc_queue.append(prod_source)
return item_to_production
# print("item_to_production:", item_to_production)
def compute_chain(targets: Dict[Items, float], preferred_by_output: Optional[Dict[Items, str]] = None) -> Tuple[Dict[str, float], List[dict], Dict[str, float], Dict[str, float]]: def compute_chain(targets: Dict[Items, float], preferred_by_output: Optional[Dict[Items, str]] = None) -> Tuple[Dict[str, float], List[dict], Dict[str, float], Dict[str, float]]:
@@ -232,6 +442,18 @@ def compute_chain(targets: Dict[Items, float], preferred_by_output: Optional[Dic
unused_byproducts[item.value.name] = unused unused_byproducts[item.value.name] = unused
return raw_requirements, merged_steps, total_outputs, unused_byproducts return raw_requirements, merged_steps, total_outputs, unused_byproducts
def generate_graph(self, production_chain, production, raw_resources, excess, byproduct):
nx_graph = nx.DiGraph()
for recipe in production_chain:
nx_graph.add_node(recipe)
for inp in self.recipe_name_to_obj[recipe].inputs.keys():
if inp in RawResources:
nx_graph.add_edge(recipe, inp.name)
continue
input_recipe = self.get_recipe(inp)
nx_graph.add_edge(recipe, input_recipe.name)
nx.draw_spring(nx_graph, with_labels=True, font_weight='bold')
plt.show()
@app.route("/", methods=["GET"]) @app.route("/", methods=["GET"])
@@ -241,13 +463,77 @@ def index():
name_to_item = {i.value.name: i for i in Items} name_to_item = {i.value.name: i for i in Items}
result = None result = None
error = None error_msgs: List[str] = []
selected_item = item_names[0] if item_names else ""
selected_rate = 60.0
# Read from query parameters for bookmarkable URLs # --------------------
item_name = request.args.get("item") or selected_item # Parse targets (support multiple products)
rate_str = request.args.get("rate") # --------------------
# Accept both legacy single `item`/`rate` and indexed `item_1`/`rate_1`, ...
# Determine how many indexed rows we have
indexed_pairs: List[Tuple[str, str]] = []
max_idx = 0
for key in request.args.keys():
if key.startswith("item_"):
try:
idx = int(key.split("_", 1)[1])
max_idx = max(max_idx, idx)
except ValueError:
pass
for i in range(1, max_idx + 1):
indexed_pairs.append((request.args.get(f"item_{i}"), request.args.get(f"rate_{i}")))
# Build targets_ui (for rendering the form) and targets dict (for compute)
targets_ui: List[dict] = []
targets: Dict[Items, float] = defaultdict(float)
def _add_target_row(item_name: Optional[str], rate_str: Optional[str]):
nonlocal targets, targets_ui
if not item_name and not rate_str:
return
item_name = (item_name or "").strip()
rate_val: Optional[float] = None
if rate_str is not None and rate_str != "":
try:
rate_val = float(rate_str)
if rate_val < 0:
raise ValueError
except (TypeError, ValueError):
error_msgs.append(f"Invalid rate for product '{item_name or '(missing)'}'. Enter a non-negative number.")
rate_val = None
if item_name:
targets_ui.append({"item": item_name, "rate": rate_val if rate_val is not None else 0.0})
if rate_val is not None:
item_obj = name_to_item.get(item_name)
if item_obj is None:
error_msgs.append(f"Unknown item '{item_name}'.")
else:
targets[item_obj] += rate_val
elif rate_val is not None:
# Rate without item name just keep it in UI
targets_ui.append({"item": "", "rate": rate_val})
if indexed_pairs:
for item_nm, rate_s in indexed_pairs:
_add_target_row(item_nm, rate_s)
else:
# Legacy single controls
default_item = item_names[0] if item_names else ""
single_item = request.args.get("item") or default_item
single_rate = request.args.get("rate")
_add_target_row(single_item, single_rate)
if not request.args:
# Initial load: ensure at least one default row
targets_ui = [{"item": default_item, "rate": 60.0}]
if default_item:
item_obj0 = name_to_item.get(default_item)
if item_obj0:
targets[item_obj0] += 60.0
# Selected defaults are taken from the first row (for compatibility with existing template pieces)
selected_item = targets_ui[0]["item"] if targets_ui else (item_names[0] if item_names else "")
selected_rate = targets_ui[0]["rate"] if targets_ui else 60.0
# Optional top-level recipe (applies only when exactly one target)
selected_recipe = request.args.get("recipe") or "" selected_recipe = request.args.get("recipe") or ""
# Parse per-item recipe overrides from query params recipe_for_<slug(item)> # Parse per-item recipe overrides from query params recipe_for_<slug(item)>
@@ -272,23 +558,9 @@ def index():
if value in candidates: if value in candidates:
overrides[item_enum] = value overrides[item_enum] = value
rate = None # Candidate recipes for the (first) selected item (single-target convenience)
if rate_str is not None and rate_str != "":
try:
rate = float(rate_str)
if rate < 0:
raise ValueError
except (TypeError, ValueError):
error = "Please enter a valid non-negative number for rate (items per minute)."
rate = None
selected_item = item_name
if rate is not None:
selected_rate = rate
# Determine candidate recipes for the selected output item
recipe_options: List[str] = [] recipe_options: List[str] = []
item_obj_for_options = name_to_item.get(selected_item) item_obj_for_options = name_to_item.get(selected_item) if len(targets) <= 1 else None
if item_obj_for_options is not None: if item_obj_for_options is not None:
for r in Recipes: for r in Recipes:
recipe = r.value recipe = r.value
@@ -312,22 +584,131 @@ def index():
# Compute and also prepare per-item override options based on resulting chain # Compute and also prepare per-item override options based on resulting chain
overrides_ui: List[dict] = [] overrides_ui: List[dict] = []
if not error and item_name and rate is not None: if targets:
item_obj = name_to_item.get(item_name) # Translate preferred mapping (Items -> recipe name) into objects and pass to ProductionChain
if item_obj is None: preferred_recipes_obj: Optional[Dict[Items, Recipe]] = None
error = "Unknown item selected." if preferred:
else: name_to_recipe = {r.value.name: r.value for r in Recipes}
targets = {item_obj: rate} preferred_recipes_obj = {}
raw, steps, outputs, unused = compute_chain(targets, preferred_by_output=preferred) for itm, rec_name in preferred.items():
rec_obj = name_to_recipe.get(rec_name)
if rec_obj:
preferred_recipes_obj[itm] = rec_obj
prod_chain = ProductionChain(preferred_recipes=preferred_recipes_obj)
prod_chain.compute_chain(targets)
# Build UI-facing structures from ProductionChain state
raw = {itm.value.name: qty for itm, qty in prod_chain.raw_resources.items() if qty > 0}
unused = {itm.value.name: qty for itm, qty in prod_chain.byproduct.items() if qty > 0}
excess = {itm.value.name: qty for itm, qty in prod_chain.excess.items() if qty > 0}
outputs = {itm.value.name: qty for itm, qty in prod_chain.production.items() if qty > 0}
# Steps per recipe
steps = []
for recipe_name, level in prod_chain.production_chain.items():
rec = prod_chain.recipe_name_to_obj.get(recipe_name)
if not rec:
continue
chosen_item = None
if rec.outputs:
demanded = sorted(rec.outputs.keys(), key=lambda it: prod_chain.demand.get(it, 0.0), reverse=True)
chosen_item = demanded[0]
if not chosen_item:
continue
per_building_output = rec.outputs[chosen_item]
buildings_float = float(level)
buildings = ceil(buildings_float) if buildings_float > 0 else 0
utilization = (buildings_float / buildings) if buildings > 0 else 0.0
target_rate_item = buildings_float * per_building_output
# Compute per-step input item rates for this recipe at the computed level
step_inputs = []
for inp_item, qty_per_building in rec.inputs.items():
rate = buildings_float * qty_per_building
step_inputs.append({
"item": inp_item.value.name,
"rate": rate,
})
# Sort inputs by descending rate for a stable display
step_inputs.sort(key=lambda x: x["rate"], reverse=True)
steps.append({
"item": chosen_item.value.name,
"recipe": rec.name,
"building": rec.building.value.name,
"target_rate": target_rate_item,
"per_building_output": per_building_output,
"buildings_float": buildings_float,
"buildings": buildings,
"utilization": utilization,
"inputs": step_inputs,
})
# Build consumers index per item from the steps' inputs
consumers_by_item: Dict[str, List[dict]] = defaultdict(list)
for s in steps:
for inp in s.get("inputs", []):
consumers_by_item[inp["item"]].append({
"recipe": s["recipe"],
"building": s["building"],
"rate": inp["rate"],
})
# Targets by item for final outputs annotation
target_rates_by_item: Dict[str, float] = {}
for itm, qty in targets.items():
if qty > 0:
target_rates_by_item[itm.value.name] = qty
# Attach destinations to each step (who consumes this step's primary output)
for s in steps:
item_name = s["item"]
dests = list(consumers_by_item.get(item_name, []))
# If this item is also a final target, add a synthetic destination
if item_name in target_rates_by_item:
dests.append({
"recipe": "Final output",
"building": "",
"rate": target_rates_by_item[item_name],
})
# If there is excess of this item, show it as a destination
if item_name in excess and excess[item_name] > 0:
dests.append({
"recipe": f"Excess: {item_name}",
"building": "",
"rate": excess[item_name],
})
# Also list byproducts produced by this step's recipe at the computed level
# Find this step's recipe and level to compute byproduct rates
rec_obj = prod_chain.recipe_name_to_obj.get(s["recipe"]) if hasattr(prod_chain, "recipe_name_to_obj") else None
if rec_obj is not None:
# Find buildings_float for this step (already stored on s)
lvl = s.get("buildings_float", 0.0)
for byp_item, per_building in rec_obj.byproducts.items():
rate = lvl * per_building
if rate > 0:
dests.append({
"recipe": f"Byproduct: {byp_item.value.name}",
"building": "",
"rate": rate,
})
# Sort destinations by descending rate for display
dests.sort(key=lambda x: x["rate"], reverse=True)
s["destinations"] = dests
# Aggregate targets for display
result_targets = {}
for itm, qty in targets.items():
if qty > 0:
result_targets[itm.value.name] = qty
result = { result = {
"targets": {item_name: rate}, "targets": result_targets,
"raw": raw, "raw": raw,
"steps": steps, "steps": steps,
"outputs": outputs, "outputs": outputs,
"unused": unused, "unused": unused,
"excess": excess,
} }
# Collect unique output items from steps # Collect unique output items from steps for override options
unique_items = [] unique_items = []
seen = set() seen = set()
for s in steps: for s in steps:
@@ -335,7 +716,6 @@ def index():
if item_nm and item_nm not in seen: if item_nm and item_nm not in seen:
seen.add(item_nm) seen.add(item_nm)
unique_items.append(item_nm) unique_items.append(item_nm)
# For each item, compute candidate recipes and current selection
for item_nm in unique_items: for item_nm in unique_items:
item_enum2 = name_to_item.get(item_nm) item_enum2 = name_to_item.get(item_nm)
if not item_enum2: if not item_enum2:
@@ -349,7 +729,7 @@ def index():
"building": rec.building.value.name, "building": rec.building.value.name,
}) })
if len(candidates) <= 1: if len(candidates) <= 1:
continue # only show when alternates exist continue
candidates.sort(key=lambda x: (x["name"])) candidates.sort(key=lambda x: (x["name"]))
sel = None sel = None
if preferred and item_enum2 in preferred: if preferred and item_enum2 in preferred:
@@ -362,11 +742,22 @@ def index():
"selected": sel or "", "selected": sel or "",
}) })
# Build reset query (clear overrides) # Build reset query (clear overrides) while preserving current targets
if indexed_pairs or len(targets_ui) > 1:
# Multi-target
parts = []
for idx, row in enumerate(targets_ui, start=1):
parts.append(f"item_{idx}={row['item']}")
parts.append(f"rate_{idx}={row['rate']}")
reset_query = "?" + "&".join(parts)
else:
reset_query = f"?item={selected_item}&rate={selected_rate}" reset_query = f"?item={selected_item}&rate={selected_rate}"
if selected_recipe: if selected_recipe:
reset_query += f"&recipe={selected_recipe}" reset_query += f"&recipe={selected_recipe}"
# Combine error messages
error = " ".join(error_msgs) if error_msgs else None
return render_template( return render_template(
"index.html", "index.html",
items=item_names, items=item_names,
@@ -378,6 +769,281 @@ def index():
selected_recipe=selected_recipe, selected_recipe=selected_recipe,
overrides_ui=overrides_ui, overrides_ui=overrides_ui,
reset_query=reset_query, reset_query=reset_query,
targets_ui=targets_ui,
)
@app.route("/new", methods=["GET"])
def new():
# Render a page similar to index but powered by ProductionChain.build_chain
item_names = sorted([i.value.name for i in Items])
name_to_item = {i.value.name: i for i in Items}
# Parse multiple targets (item_1/rate_1, ...). Fallback to legacy item/rate
indexed_pairs: List[Tuple[Optional[str], Optional[str]]] = []
max_idx = 0
for key in request.args.keys():
if key.startswith("item_"):
try:
idx = int(key.split("_", 1)[1])
max_idx = max(max_idx, idx)
except ValueError:
pass
for i in range(1, max_idx + 1):
indexed_pairs.append((request.args.get(f"item_{i}"), request.args.get(f"rate_{i}")))
targets: Dict[Items, float] = defaultdict(float)
targets_ui: List[dict] = []
errors: List[str] = []
def _add_row(item_name: Optional[str], rate_str: Optional[str]):
nonlocal targets, targets_ui
if not item_name and not rate_str:
return
item_name = (item_name or "").strip()
rate_val: Optional[float] = None
if rate_str is not None and rate_str != "":
try:
rate_val = float(rate_str)
if rate_val < 0:
raise ValueError
except (TypeError, ValueError):
errors.append(f"Invalid rate for product '{item_name or '(missing)'}'. Enter a non-negative number.")
rate_val = None
if item_name:
targets_ui.append({"item": item_name, "rate": rate_val if rate_val is not None else 0.0})
if rate_val is not None:
item_obj = name_to_item.get(item_name)
if item_obj is None:
errors.append(f"Unknown item '{item_name}'.")
else:
targets[item_obj] += rate_val
elif rate_val is not None:
# Rate without item name keep in UI only
targets_ui.append({"item": "", "rate": rate_val})
if indexed_pairs:
for item_nm, rate_s in indexed_pairs:
_add_row(item_nm, rate_s)
else:
# Legacy single controls
default_item = item_names[0] if item_names else ""
single_item = request.args.get("item") or default_item
single_rate = request.args.get("rate")
_add_row(single_item, single_rate)
if not request.args:
# Initial load: ensure default row
targets_ui = [{"item": default_item, "rate": 60.0}]
if default_item:
item_obj0 = name_to_item.get(default_item)
if item_obj0:
targets[item_obj0] += 60.0
# Build reset query preserving current targets
if indexed_pairs or len(targets_ui) > 1:
parts = []
for idx, row in enumerate(targets_ui, start=1):
parts.append(f"item_{idx}={row['item']}")
parts.append(f"rate_{idx}={row['rate']}")
reset_query = "?" + "&".join(parts)
else:
selected_item = targets_ui[0]["item"] if targets_ui else (item_names[0] if item_names else "")
selected_rate = targets_ui[0]["rate"] if targets_ui else 60.0
reset_query = f"?item={selected_item}&rate={selected_rate}"
error = " ".join(errors) if errors else None
# Parse per-item recipe overrides from query params: recipe_for_<slug(item name)>
# Build slug -> Items map and recipe name -> Recipe map
slug_to_item: Dict[str, Items] = { _slugify(i.value.name): i for i in Items }
recipe_name_to_obj: Dict[str, Recipe] = {}
for r in Recipes:
recipe_name_to_obj[r.value.name] = r.value
preferred: Dict[Items, Recipe] = {}
for key, value in request.args.items():
if not key.startswith("recipe_for_"):
continue
if not value:
# Empty value means no override for this item
continue
slug = key[len("recipe_for_") :]
item_enum = slug_to_item.get(slug)
if not item_enum:
continue
# Validate that the provided recipe name is a candidate for this item
candidates = []
for r in Recipes:
rec = r.value
if item_enum in rec.outputs:
candidates.append(rec.name)
if value in candidates:
preferred[item_enum] = recipe_name_to_obj[value]
# Build chain using the object-oriented builder
# Now organize the UI data by production (recipe/building instance) rather than by item
chain_ui: List[dict] = []
# Additionally, build a separate table for alternate recipes organized by item
alternates_by_item_ui: List[dict] = []
if not errors and targets:
# Pass preferred recipes so alternates can be selected
prod_chain = ProductionChain(preferred_recipes=preferred)
item_to_production = prod_chain.build_chain(targets)
def _it_name(i: Items) -> str:
return i.value.name
# Collect unique Production objects (they may appear under multiple output items)
seen_ids: set[int] = set()
productions: List[Production] = []
for plist in item_to_production.values():
for p in plist:
pid = id(p)
if pid in seen_ids:
continue
seen_ids.add(pid)
productions.append(p)
# Sort productions by MachineOrder (then by recipe name for stability)
order_index_map = {m: i for i, m in enumerate(MachineOrder)}
def _prod_sort_key(pr: Production):
b = pr.recipe.building
# Fallback to the end if a building isn't listed in MachineOrder
order_idx = order_index_map.get(b, len(MachineOrder))
return (order_idx, pr.recipe.name)
productions.sort(key=_prod_sort_key)
chain_ui = []
used_slugs: set[str] = set()
for p in productions:
# Choose an anchor output item to bind alternate recipe selection.
# Prefer items that actually egress (flow to another production or final output),
# falling back to any output if there is no egress.
egress_items = list(p.egress.keys())
output_items = list(p.outputs.keys())
anchor_item: Optional[Items] = None
pick_from: List[Items] = egress_items if egress_items else output_items
if pick_from:
anchor_item = sorted(pick_from, key=lambda i: i.value.name)[0]
# Build candidate recipes for the anchor item
candidates: List[dict] = []
selected_recipe_name = p.recipe.name
recipe_slug = _slugify(anchor_item.value.name) if anchor_item else ""
if anchor_item is not None:
for r in Recipes:
rec = r.value
if anchor_item in rec.outputs:
candidates.append({
"name": rec.name,
"building": rec.building.value.name,
})
candidates.sort(key=lambda x: (x["name"]))
# Only show the selector once per anchor item to avoid duplicate keys in the form
if recipe_slug in used_slugs:
# clear candidates and slug so template renders plain text
candidates = []
recipe_slug = ""
else:
used_slugs.add(recipe_slug)
# Build ingress/egress presentation lists
ingress_ui: List[dict] = []
for it, links in p.ingress.items():
for lk in links:
if lk.raw or lk.production is None:
via = "Raw"
else:
via = f"{lk.production.recipe.name} ({lk.production.recipe.building.value.name})"
ingress_ui.append({
"item": _it_name(it),
"rate": lk.quantity,
"via": via,
})
egress_ui: List[dict] = []
for it, links in p.egress.items():
for lk in links:
if lk.production is None:
to = "Final Output"
else:
to = f"{lk.production.recipe.name} ({lk.production.recipe.building.value.name})"
egress_ui.append({
"item": _it_name(it),
"rate": lk.quantity,
"to": to,
})
chain_ui.append(
{
"recipe": p.recipe.name,
"building": p.recipe.building.value.name,
"production_level": p.production_level,
"inputs": [
{"item": _it_name(i), "rate": q} for i, q in p.inputs.items()
],
"outputs": [
{"item": _it_name(i), "rate": q} for i, q in p.outputs.items()
],
# Link visualization
"ingress": ingress_ui,
"egress": egress_ui,
# Deprecated in UI: per-production alternate selector (kept for reference)
"recipe_options": candidates,
"recipe_selected": selected_recipe_name,
"recipe_slug": recipe_slug,
}
)
# Build the alternate-recipe table data organized by item.
# Consider items that appear as outputs in the current chain.
items_in_chain: set[Items] = set()
for p in productions:
for it in p.outputs.keys():
items_in_chain.add(it)
# Helper: map current chain's selected recipe for an item (if any)
current_selected_by_item: Dict[Items, str] = {}
for p in productions:
for it in p.outputs.keys():
# if multiple productions output same item, first one wins (they should be consistent)
current_selected_by_item.setdefault(it, p.recipe.name)
# Build candidate lists and choose selected value
for item in sorted(items_in_chain, key=lambda i: i.value.name):
candidates: List[dict] = []
for r in Recipes:
rec = r.value
if item in rec.outputs:
candidates.append({
"name": rec.name,
"building": rec.building.value.name,
})
# Only include items that actually have alternates (2 or more options)
if len(candidates) <= 1:
continue
candidates.sort(key=lambda x: x["name"])
# Selected recipe prioritizes explicit override if provided; otherwise use what chain used
selected_name = None
if item in preferred:
selected_name = preferred[item].name
else:
selected_name = current_selected_by_item.get(item)
alternates_by_item_ui.append({
"item": item.value.name,
"slug": _slugify(item.value.name),
"options": candidates,
"selected": selected_name,
})
return render_template(
"new.html",
items=item_names,
targets_ui=targets_ui,
error=error,
chain=chain_ui,
reset_query=reset_query,
alternates_by_item=alternates_by_item_ui,
) )
@@ -387,5 +1053,10 @@ def create_app():
if __name__ == "__main__": if __name__ == "__main__":
# For local dev: python main.py # For local dev: python main.py
# app.run(host="0.0.0.0", port=5000, debug=True) app.run(host="0.0.0.0", port=5000, debug=True)
compute_chain2({Items.ModularFrame: 45.0}) # prod_chain = ProductionChain()
# chain = prod_chain.build_chain({Items.BrassBeltDrive: 10.0})
# print(chain)
# prod_chain.compute_chain({Items.FusedModularFrame: 1.5})

495
plus.py
View File

@@ -1,15 +1,9 @@
from enum import Enum from enum import Enum
from typing import Dict from typing import Dict, Tuple
from shared import Machine, Item
from pydantic import BaseModel, Field, ConfigDict from pydantic import BaseModel, Field, ConfigDict
class Item(BaseModel):
name: str
class Machine(BaseModel):
name: str
class Machines(Enum): class Machines(Enum):
Miner = Machine(name="Miner") Miner = Machine(name="Miner")
Smelter = Machine(name="Smelter") Smelter = Machine(name="Smelter")
@@ -18,6 +12,24 @@ class Machines(Enum):
Sorter = Machine(name="Sorter") Sorter = Machine(name="Sorter")
Crusher = Machine(name="Crusher") Crusher = Machine(name="Crusher")
Foundry = Machine(name="Foundry") Foundry = Machine(name="Foundry")
FlexibleBlastFurnace = Machine(name="Flexible Blast Furnace")
Reformer = Machine(name="Reformer")
WetWasher = Machine(name="Wet Washer")
BoilerMk1 = Machine(name="Boiler Mk1")
MachineOrder: Tuple[Machines, ...] = (
Machines.Miner,
Machines.BoilerMk1,
Machines.Crusher,
Machines.WetWasher,
Machines.Sorter,
Machines.Smelter,
Machines.Foundry,
Machines.FlexibleBlastFurnace,
Machines.Reformer,
Machines.Constructor,
Machines.Assembler,
)
class Items(Enum): class Items(Enum):
IronIngot = Item(name="Iron Ingot") IronIngot = Item(name="Iron Ingot")
@@ -76,15 +88,55 @@ class Items(Enum):
Rotor = Item(name="Rotor") Rotor = Item(name="Rotor")
BronzeFrame = Item(name="Bronze Frame") BronzeFrame = Item(name="Bronze Frame")
AILimiter = Item(name="AI Limiter") AILimiter = Item(name="AI Limiter")
Water = Item(name="Water")
CrushedCaterium = Item(name="Crushed Caterium")
CateriumHeatsink = Item(name="Caterium Heatsink")
MoltenIron = Item(name="Molten Iron")
MoltenCopper = Item(name="Molten Copper")
MoltenTin = Item(name="Molten Tin")
Steam = Item(name="Steam")
MoltenBrass = Item(name="Molten Brass")
BrassIngot = Item(name="Brass Ingot")
BrassPlates = Item(name="Brass Plates")
BrassPipes = Item(name="Brass Pipes")
ColdSlag = Item(name="Cold Slag")
FanBlades = Item(name="Fan Blades")
TailingsSlurry = Item(name="Tailings Slurry")
CarbonPowder = Item(name="Carbon Powder")
Coal = Item(name="Coal")
MoltenSteel = Item(name="Molten Steel")
FlueGas = Item(name="Flue Gas")
SteelIngot = Item(name="Steel Ingot")
Air = Item(name="Air")
SteelBeam = Item(name="Steel Beam")
SteelPipe = Item(name="Steel Pipe")
SteelBolt = Item(name="Steel Bolt")
SteelRod = Item(name="Steel Rod")
Stator = Item(name="Stator")
Kyalite = Item(name="Kyalite")
Salt = Item(name="Salt")
Diamonds = Item(name="Diamonds")
QuartzCrystal = Item(name="Quartz Crystal")
BrassBeltDrive = Item(name="Brass Belt Drive")
class RawResources(Enum):
Water = Items.Water
SiteriteOre = Items.SiteriteOre
LarrussiteOre = Items.LarrussiteOre
CallaniteOre = Items.CallaniteOre
AuroviteOre = Items.AuroviteOre
Kyalite = Items.Kyalite
Coal = Items.Coal
Air = Items.Air
class Recipe(BaseModel): class Recipe(BaseModel):
model_config = ConfigDict(frozen=True) model_config = ConfigDict(frozen=True)
name: str # Human-friendly name name: str
building: Machines # e.g., "Smelter", "Constructor" building: Machines
outputs: Dict[Items, float] # Produced item name outputs: Dict[Items, float]
inputs: Dict[Items, float] = Field(default_factory=dict) inputs: Dict[Items, float] = Field(default_factory=dict)
byproducts: Dict[Items, float] = Field(default_factory=dict)
class Recipes(Enum): class Recipes(Enum):
# Crusher # Crusher
@@ -103,9 +155,9 @@ class Recipes(Enum):
building=Machines.Crusher, building=Machines.Crusher,
outputs={ outputs={
Items.CrushedLarrussite: 40.0, Items.CrushedLarrussite: 40.0,
Items.CrushedGangue: 20.0,
}, },
inputs={Items.LarrussiteOre: 60.0}, inputs={Items.LarrussiteOre: 60.0},
byproducts={Items.CrushedGangue: 20.0,}
) )
CrushedCallanite = Recipe( CrushedCallanite = Recipe(
name="Crushed Callanite", name="Crushed Callanite",
@@ -125,6 +177,17 @@ class Recipes(Enum):
}, },
inputs={Items.AuroviteOre: 60.0}, inputs={Items.AuroviteOre: 60.0},
) )
CrushedSilica = Recipe(
name="Crushed Silica",
building=Machines.Crusher,
outputs={
Items.Silica: 60.0,
},
inputs={Items.Kyalite: 60.0},
byproducts={
Items.Sand: 40.0,
}
)
# - Crushing Powders # - Crushing Powders
CoarseSand = Recipe( CoarseSand = Recipe(
name="Coarse Sand", name="Coarse Sand",
@@ -142,7 +205,14 @@ class Recipes(Enum):
}, },
inputs={Items.CrushedMagnesium: 80.0}, inputs={Items.CrushedMagnesium: 80.0},
) )
CarbonPowder = Recipe(
name="Carbon Powder",
building=Machines.Crusher,
outputs={
Items.CarbonPowder: 40.0
},
inputs={Items.Coal: 80.0},
)
# Sorter # Sorter
# - Simple Sorting # - Simple Sorting
CrushedIron = Recipe( CrushedIron = Recipe(
@@ -151,9 +221,11 @@ class Recipes(Enum):
outputs={ outputs={
Items.CrushedIron: 90.0, Items.CrushedIron: 90.0,
Items.CrushedTin: 60.0, Items.CrushedTin: 60.0,
Items.CrushedGangue: 40.0,
}, },
inputs={Items.CrushedSiterite: 120.0}, inputs={Items.CrushedSiterite: 120.0},
byproducts={
Items.CrushedGangue: 40.0,
},
) )
CrushedCopper = Recipe( CrushedCopper = Recipe(
name="Crushed Copper", name="Crushed Copper",
@@ -161,19 +233,57 @@ class Recipes(Enum):
outputs={ outputs={
Items.CrushedCopper: 96.0, Items.CrushedCopper: 96.0,
Items.CrushedMagnesium: 80.0, Items.CrushedMagnesium: 80.0,
Items.CrushedGangue: 40.0,
}, },
inputs={Items.CrushedCallanite: 120.0}, inputs={Items.CrushedCallanite: 120.0},
byproducts={
Items.CrushedGangue: 40.0,
},
) )
CrushedZinc = Recipe( CrushedZinc = Recipe(
name="Crushed Zinc", name="Crushed Zinc",
building=Machines.Sorter, building=Machines.Sorter,
outputs={ outputs={
Items.CrushedZinc: 96.0, Items.CrushedZinc: 96.0,
Items.CrushedMagnesium: 48.0,
Items.CrushedGangue: 60.0,
}, },
inputs={Items.CrushedLarrussite: 120.0}, inputs={Items.CrushedLarrussite: 120.0},
byproducts={
Items.CrushedGangue: 60.0,
Items.Sand: 48.0,
},
)
CrushedCaterium = Recipe(
name="Crushed Caterium",
building=Machines.Sorter,
outputs={
Items.CrushedCaterium: 80.0,
Items.CrushedCopper: 40.0,
Items.CrushedGangue: 30.0,
},
inputs={Items.CrushedAurovite: 120.0},
)
QuartzCrystal = Recipe(
name="Quartz Crystal",
building=Machines.Sorter,
outputs={
Items.QuartzCrystal: 100.0,
Items.Salt: 40.0,
Items.Sand: 40.0,
},
inputs={Items.Kyalite: 120.0},
)
# - Advanced Sorting
SaltKyalite = Recipe(
name="Salt (Kyalite)",
building=Machines.Sorter,
outputs={
Items.Salt: 90.0,
Items.Silica: 45.0,
Items.Sand: 30.0,
},
inputs={
Items.Kyalite: 120.0,
Items.Water: 45.0,
},
) )
# Smelter # Smelter
# - smelting # - smelting
@@ -195,6 +305,12 @@ class Recipes(Enum):
outputs={Items.TinIngot: 15.0}, outputs={Items.TinIngot: 15.0},
inputs={Items.CrushedTin: 15.0}, inputs={Items.CrushedTin: 15.0},
) )
CateriumIngot = Recipe(
name="Caterium Ingot",
building=Machines.Smelter,
outputs={Items.CateriumIngot: 40.0},
inputs={Items.CrushedCaterium: 40.0},
)
# - Ingots # - Ingots
ImpureIronIngot = Recipe( ImpureIronIngot = Recipe(
name="Impure Iron Ingot", name="Impure Iron Ingot",
@@ -217,8 +333,8 @@ class Recipes(Enum):
ImpureCateriumIngot = Recipe( ImpureCateriumIngot = Recipe(
name="Impure Caterium Ingot", name="Impure Caterium Ingot",
building=Machines.Smelter, building=Machines.Smelter,
inputs={Items.CrushedAurovite: 40.0},
outputs={Items.CateriumIngot: 24.0}, outputs={Items.CateriumIngot: 24.0},
inputs={Items.CrushedAurovite: 40.0},
) )
ZincIngot = Recipe( ZincIngot = Recipe(
name="Zinc Ingot", name="Zinc Ingot",
@@ -233,6 +349,12 @@ class Recipes(Enum):
outputs={Items.Glass: 20.0}, outputs={Items.Glass: 20.0},
inputs={Items.Sand: 30.0}, inputs={Items.Sand: 30.0},
) )
CastIronScrew = Recipe(
name="Cast Iron Screw",
building=Machines.Smelter,
outputs={Items.Screws: 40.0},
inputs={Items.IronIngot: 20.0},
)
# Constructor # Constructor
# - Standard Parts # - Standard Parts
IronPlate = Recipe( IronPlate = Recipe(
@@ -307,6 +429,18 @@ class Recipes(Enum):
outputs={Items.ZincPlates: 15.0}, outputs={Items.ZincPlates: 15.0},
inputs={Items.ZincIngot: 12.5}, inputs={Items.ZincIngot: 12.5},
) )
RolledBronzePlate = Recipe(
name="Rolled Bronze Plate",
building=Machines.Constructor,
outputs={Items.BronzePipes: 16.0},
inputs={Items.BronzePlates: 8.0},
)
CompactedIronPlate = Recipe(
name="Compacted Iron Plate",
building=Machines.Constructor,
outputs={Items.ReinforcedIronPlate: 3.0},
inputs={Items.IronPlate: 20.0},
)
# - Electronics # - Electronics
IronWire = Recipe( IronWire = Recipe(
name="Iron Wire", name="Iron Wire",
@@ -351,7 +485,25 @@ class Recipes(Enum):
outputs={Items.Concrete: 15.0}, outputs={Items.Concrete: 15.0},
inputs={Items.CrushedGangue: 45.0}, inputs={Items.CrushedGangue: 45.0},
) )
# - Industrial Parts
BrassPlates = Recipe(
name="Brass Plates",
building=Machines.Constructor,
outputs={Items.BrassPlates: 18.0},
inputs={Items.BrassIngot: 25.0},
)
SteelBolt = Recipe(
name="Steel Bolt",
building=Machines.Constructor,
outputs={Items.SteelBolt: 37.5},
inputs={Items.SteelRod: 12.5},
)
HighPressureDiamond = Recipe(
name="High Pressure Diamond",
building=Machines.Constructor,
outputs={Items.Diamonds: 1.0},
inputs={Items.CarbonPowder: 20.0},
)
# Foundry # Foundry
# - Building Parts # - Building Parts
SolarCell = Recipe( SolarCell = Recipe(
@@ -383,6 +535,35 @@ class Recipes(Enum):
Items.TinIngot: 15.0, Items.TinIngot: 15.0,
}, },
) )
# - Standard Parts
TemperedSteelRod = Recipe(
name="Tempered Steel Rod",
building=Machines.Foundry,
outputs={Items.SteelRod: 12.5},
inputs={
Items.IronRod: 30.0,
Items.CarbonPowder: 12.5
}
)
# - Industrial Parts
SteelBeam = Recipe(
name="Steel Beam",
building=Machines.Foundry,
outputs={Items.SteelBeam: 22.5},
inputs={
Items.SteelIngot: 60.0,
Items.ZincIngot: 20.0
},
)
CastSteelPipe = Recipe(
name="Cast Steel Pipe",
building=Machines.Foundry,
outputs={Items.SteelPipe: 45.0},
inputs={
Items.SteelIngot: 30.0,
Items.Sand: 24.0,
},
)
# - Other # - Other
ThermalSilica = Recipe( ThermalSilica = Recipe(
name="Thermal Silica", name="Thermal Silica",
@@ -393,6 +574,15 @@ class Recipes(Enum):
Items.MagnesiumGranules: 27.0, Items.MagnesiumGranules: 27.0,
}, },
) )
SaltySlag = Recipe(
name="Salty Slag",
building=Machines.Foundry,
outputs={Items.ColdSlag: 15.0},
inputs={
Items.Salt: 22.5,
Items.MagnesiumGranules: 30.0,
},
)
# Assembler # Assembler
# - Building Parts # - Building Parts
SmoothBeltDrive = Recipe( SmoothBeltDrive = Recipe(
@@ -404,6 +594,24 @@ class Recipes(Enum):
Items.Rotor: 3.75, Items.Rotor: 3.75,
}, },
) )
SlagConcrete = Recipe(
name="Slag Concrete",
building=Machines.Assembler,
outputs={Items.Concrete: 22.5},
inputs={
Items.ColdSlag: 33.75,
Items.CrushedGangue: 11.25,
},
)
FanBlades = Recipe(
name="Fan Blades",
building=Machines.Assembler,
outputs={Items.FanBlades: 7.5},
inputs={
Items.BrassPlates: 8.0,
Items.IronRod: 22.5,
},
)
# - Standard Parts # - Standard Parts
TinPlate = Recipe( TinPlate = Recipe(
name="Tin Plate", name="Tin Plate",
@@ -468,6 +676,25 @@ class Recipes(Enum):
Items.BronzeBeam: 7.5, Items.BronzeBeam: 7.5,
}, },
) )
FastenedFrame = Recipe(
name="Fastened Frame",
building=Machines.Assembler,
outputs={Items.ModularFrame: 20.0},
inputs={
Items.ReinforcedIronPlate: 15,
Items.Screws: 150,
},
)
BrassPipes = Recipe(
name="Brass Pipes",
building=Machines.Assembler,
outputs={Items.BrassPipes: 12.5},
inputs={
Items.BrassPlates: 15.0,
Items.CopperRod: 7.5
},
)
# - Electronics
AILimiter = Recipe( AILimiter = Recipe(
name="AI Limiter", name="AI Limiter",
building=Machines.Assembler, building=Machines.Assembler,
@@ -477,3 +704,231 @@ class Recipes(Enum):
Items.TinnedWire: 18.0, Items.TinnedWire: 18.0,
}, },
) )
# - Industrial
CateriumHeatsink = Recipe(
name="Caterium Heatsink",
building=Machines.Assembler,
outputs={Items.CateriumHeatsink: 10.0},
inputs={
Items.CateriumPlate: 28.0,
Items.CopperBusbars: 10.0,
},
)
Stator = Recipe(
name="Stator",
building=Machines.Assembler,
outputs={Items.Stator: 6.0},
inputs={
Items.SteelPipe: 18.0,
Items.TinnedWire: 36.0,
},
)
# Flexible Blast Furnace
# - Molten Metals
MoltenIron = Recipe(
name="Molten Iron",
building=Machines.FlexibleBlastFurnace,
outputs={Items.MoltenIron: 60.0},
inputs={
Items.CrushedIron: 120.0,
},
)
MoltenCopper = Recipe(
name="Molten Copper",
building=Machines.FlexibleBlastFurnace,
outputs={Items.MoltenCopper: 60.0},
inputs={
Items.CrushedCopper: 120.0,
},
)
MoltenTin = Recipe(
name="Molten Tin",
building=Machines.FlexibleBlastFurnace,
outputs={Items.MoltenTin: 60.0},
inputs={
Items.CrushedTin: 120.0,
},
)
# - Alloys
MoltenBrass = Recipe(
name="Molten Brass",
building=Machines.FlexibleBlastFurnace,
outputs={Items.MoltenBrass: 60.0},
inputs={
Items.CrushedZinc: 32.0,
Items.MoltenCopper: 20.0,
},
)
MoltenSteel = Recipe(
name="Molten Steel",
building=Machines.FlexibleBlastFurnace,
outputs={
Items.MoltenSteel: 40.0
},
inputs={
Items.MoltenIron: 40.0,
Items.CarbonPowder: 40.0,
Items.Steam: 60.0
},
byproducts={
Items.FlueGas: 30.0
}
)
DirectSteel = Recipe(
name="Direct Steel",
building=Machines.FlexibleBlastFurnace,
outputs={
Items.MoltenSteel: 60.0,
Items.FlueGas: 10.0
},
inputs={
Items.IronIngot: 120.0,
Items.Steam: 40.0,
Items.Air: 75.0
}
)
# Reformer
# - Cooling
CastSteelRod = Recipe(
name="Cast Steel Rod",
building=Machines.Reformer,
outputs={
Items.SteelRod: 37.5,
},
inputs={
Items.MoltenSteel: 20.0,
Items.Water: 40.0,
},
byproducts={
Items.Steam: 40.0,
Items.ColdSlag: 15.0,
}
)
# - Ingots
CastIronIngot = Recipe(
name="Cast Iron Ingot",
building=Machines.Reformer,
outputs={
Items.IronIngot: 180.0,
Items.Steam: 60.0
},
inputs={
Items.MoltenIron: 60.0,
Items.Water: 60.0,
},
)
CastCopperIngot = Recipe(
name="Cast Copper Ingot",
building=Machines.Reformer,
outputs={
Items.CopperIngot: 144.0,
Items.Steam: 24.0
},
inputs={
Items.MoltenCopper: 48.0,
Items.Water: 24.0,
}
)
CastTinIngot = Recipe(
name="Cast Tin Ingot",
building=Machines.Reformer,
outputs={
Items.TinIngot: 180.0,
Items.Steam: 20.0
},
inputs={
Items.MoltenTin: 60.0,
Items.Water: 20.0,
}
)
CastBrassIngot = Recipe(
name="Cast Brass Ingot",
building=Machines.Reformer,
outputs={
Items.BrassIngot: 50.0,
Items.Steam: 15.0
},
inputs={
Items.MoltenBrass: 40.0,
Items.Water: 15.0,
}
)
CastSteelIngot = Recipe(
name="Cast Steel Ingot",
building=Machines.Reformer,
outputs={
Items.SteelIngot: 90.0,
Items.FlueGas: 30.0,
Items.ColdSlag: 37.5,
},
inputs={
Items.CrushedGangue: 37.5,
Items.MoltenSteel: 30.0,
Items.Air: 30.0
}
)
# - Other
BrassBeltDrive = Recipe(
name="Brass Belt Drive",
building=Machines.Reformer,
outputs={
Items.BrassBeltDrive: 20.0,
},
inputs={
Items.Stator: 8.0,
Items.MoltenBrass: 20.0,
Items.Air: 36.0,
}
)
# Wet Washer
# - Washing
WashedIron = Recipe(
name="Washed Iron",
building=Machines.WetWasher,
outputs={
Items.CrushedIron: 60.0,
Items.TailingsSlurry: 40.0
},
inputs={
Items.SiteriteOre: 40.0,
Items.Water: 40.0,
}
)
WashedCopper = Recipe(
name="Washed Copper",
building=Machines.WetWasher,
outputs={
Items.CrushedCopper: 36.0,
Items.TailingsSlurry: 24.0
},
inputs={
Items.CallaniteOre: 24.0,
Items.Water: 24.0,
}
)
WashedTin = Recipe(
name="Washed Tin",
building=Machines.WetWasher,
outputs={
Items.CrushedTin: 45.0,
},
inputs={
Items.SiteriteOre: 60.0,
Items.Water: 30.0,
},
byproducts={
Items.TailingsSlurry: 60.0
}
)
SteamMk1 = Recipe(
name="Steam Mk1",
building=Machines.BoilerMk1,
outputs={
Items.Steam: 30.0,
},
inputs={
Items.Water: 15.0,
}
)
Recipe.model_rebuild()

View File

@@ -5,7 +5,9 @@ description = "Web-based Satisfactory production calculator in Python (Flask)"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"flask>=3.0", "flask>=3.0",
"matplotlib>=3.10.7",
"networkx>=3.5", "networkx>=3.5",
"pydantic>=2.12.4", "pydantic>=2.12.4",
"rich>=14.2.0", "rich>=14.2.0",
"scipy>=1.16.3",
] ]

12
shared.py Normal file
View File

@@ -0,0 +1,12 @@
from enum import Enum, EnumType
from typing import Dict
from pydantic import BaseModel, ConfigDict, Field
class Item(BaseModel):
name: str
class Machine(BaseModel):
name: str

View File

@@ -26,6 +26,8 @@
code { color: var(--accent); } code { color: var(--accent); }
.mono { font-variant-numeric: tabular-nums; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } .mono { font-variant-numeric: tabular-nums; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.pill { display:inline-block; padding:2px 8px; border-radius: 999px; background:#0b1220; border:1px solid #334155; color: var(--muted); font-size: 12px; } .pill { display:inline-block; padding:2px 8px; border-radius: 999px; background:#0b1220; border:1px solid #334155; color: var(--muted); font-size: 12px; }
.completed td:not(.done-col) { text-decoration: line-through; color: var(--muted); }
.done-col { width: 1%; white-space: nowrap; }
</style> </style>
</head> </head>
<body> <body>
@@ -33,19 +35,39 @@
<h1>Satisfactory Production Calculator</h1> <h1>Satisfactory Production Calculator</h1>
<p>Compute buildings and raw inputs for a target output rate (items per minute).</p> <p>Compute buildings and raw inputs for a target output rate (items per minute).</p>
<form class="card" method="get"> <form class="card" method="get" id="targets-form">
<div class="row">
<div> <div>
<label for="item">Product</label> <label>Products</label>
<select id="item" name="item" required> <table style="width:100%; border-collapse: collapse;">
<thead>
<tr>
<th style="width:60%">Item</th>
<th style="width:30%" class="mono">Rate (items/min)</th>
<th style="width:10%"></th>
</tr>
</thead>
<tbody id="targets-rows">
{% for row in targets_ui %}
<tr class="target-row">
<td>
<select name="item_{{ loop.index }}" required>
{% for it in items %} {% for it in items %}
<option value="{{ it }}" {% if selected_item and it==selected_item %}selected{% endif %}>{{ it }}</option> <option value="{{ it }}" {% if row.item == it %}selected{% endif %}>{{ it }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </td>
<div> <td>
<label for="rate">Target rate (items/min)</label> <input name="rate_{{ loop.index }}" type="number" step="0.01" min="0" value="{{ row.rate }}" required>
<input id="rate" name="rate" type="number" step="0.01" min="0" value="{{ selected_rate }}" required> </td>
<td>
<button type="button" class="pill" onclick="removeRow(this)" title="Remove"></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="mt">
<button type="button" onclick="addRow()">Add product</button>
</div> </div>
</div> </div>
@@ -64,11 +86,12 @@
{% if result.raw %} {% if result.raw %}
<table> <table>
<thead> <thead>
<tr><th>Item</th><th class="mono">Rate (items/min)</th></tr> <tr><th class="done-col">Done</th><th>Item</th><th class="mono">Rate (items/min)</th></tr>
</thead> </thead>
<tbody> <tbody>
{% for item,rate in result.raw.items() %} {% for item,rate in result.raw.items() %}
<tr> <tr>
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
<td>{{ item }}</td> <td>{{ item }}</td>
<td class="mono">{{ '%.2f'|format(rate) }}</td> <td class="mono">{{ '%.2f'|format(rate) }}</td>
</tr> </tr>
@@ -82,8 +105,11 @@
{% if overrides_ui and overrides_ui|length > 0 %} {% if overrides_ui and overrides_ui|length > 0 %}
<h3 class="mt">Recipe overrides</h3> <h3 class="mt">Recipe overrides</h3>
<form method="get" class="card" style="background: transparent; border:0; padding:0;"> <form method="get" class="card" style="background: transparent; border:0; padding:0;">
<input type="hidden" name="item" value="{{ selected_item }}"> {# Preserve all target rows in the recalculation #}
<input type="hidden" name="rate" value="{{ selected_rate }}"> {% for row in targets_ui %}
<input type="hidden" name="item_{{ loop.index }}" value="{{ row.item }}">
<input type="hidden" name="rate_{{ loop.index }}" value="{{ row.rate }}">
{% endfor %}
{% if selected_recipe %}<input type="hidden" name="recipe" value="{{ selected_recipe }}">{% endif %} {% if selected_recipe %}<input type="hidden" name="recipe" value="{{ selected_recipe }}">{% endif %}
<table> <table>
<thead> <thead>
@@ -120,8 +146,10 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Output Item</th> <th class="done-col">Done</th>
<th>Recipe</th> <th>Recipe</th>
<th>Inputs</th>
<th>Output Items</th>
<th>Building</th> <th>Building</th>
<th class="mono">Target rate</th> <th class="mono">Target rate</th>
<th class="mono">Per-building output</th> <th class="mono">Per-building output</th>
@@ -132,9 +160,33 @@
<tbody> <tbody>
{% for s in result.steps %} {% for s in result.steps %}
<tr> <tr>
<td>{{ s.item }}</td> <td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
<td>{{ s.recipe }}</td> <td>{{ s.recipe }}</td>
<td>
{% if s.inputs and s.inputs|length > 0 %}
<div>
{% for inp in s.inputs %}
<div>{{ inp.item }} — <span class="mono">{{ '%.2f'|format(inp.rate) }}</span></div>
{% endfor %}
</div>
{% else %}
<span class="pill">None</span>
{% endif %}
</td>
<td>
{% if s.destinations and s.destinations|length > 0 %}
<div>
{% for d in s.destinations %}
<div>{{ d.recipe }}{% if d.building %} ({{ d.building }}){% endif %} — <span class="mono">{{ '%.2f'|format(d.rate) }}</span></div>
{% endfor %}
</div>
{% else %}
<span class="pill">None</span>
{% endif %}
</td>
<td>{{ s.building }}</td> <td>{{ s.building }}</td>
<td class="mono">{{ '%.2f'|format(s.target_rate) }}</td> <td class="mono">{{ '%.2f'|format(s.target_rate) }}</td>
<td class="mono">{{ '%.2f'|format(s.per_building_output) }}</td> <td class="mono">{{ '%.2f'|format(s.per_building_output) }}</td>
<td class="mono">{{ '%.2f'|format(s.buildings_float) }} (~ {{ s.buildings }})</td> <td class="mono">{{ '%.2f'|format(s.buildings_float) }} (~ {{ s.buildings }})</td>
@@ -147,15 +199,36 @@
<p class="pill">No production steps (target is a raw resource).</p> <p class="pill">No production steps (target is a raw resource).</p>
{% endif %} {% endif %}
<h3 class="mt">Excess products</h3>
{% if result.excess and result.excess|length > 0 %}
<table>
<thead>
<tr><th class="done-col">Done</th><th>Item</th><th class="mono">Excess rate (items/min)</th></tr>
</thead>
<tbody>
{% for item,rate in result.excess.items() %}
<tr>
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
<td>{{ item }}</td>
<td class="mono">{{ '%.2f'|format(rate) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="pill">No excess products for this target.</p>
{% endif %}
<h3 class="mt">Unused byproducts</h3> <h3 class="mt">Unused byproducts</h3>
{% if result.unused and result.unused|length > 0 %} {% if result.unused and result.unused|length > 0 %}
<table> <table>
<thead> <thead>
<tr><th>Item</th><th class="mono">Unused rate (items/min)</th></tr> <tr><th class="done-col">Done</th><th>Item</th><th class="mono">Unused rate (items/min)</th></tr>
</thead> </thead>
<tbody> <tbody>
{% for item,rate in result.unused.items() %} {% for item,rate in result.unused.items() %}
<tr> <tr>
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
<td>{{ item }}</td> <td>{{ item }}</td>
<td class="mono">{{ '%.2f'|format(rate) }}</td> <td class="mono">{{ '%.2f'|format(rate) }}</td>
</tr> </tr>
@@ -170,8 +243,45 @@
</div> </div>
<script> <script>
// Tiny helper to maintain selected item after submit if not handled by server // Dynamic add/remove for multiple product targets
// (we already set it via Jinja, this is just progressive enhancement) const itemsList = {{ items | tojson }};
function addRow() {
const tbody = document.getElementById('targets-rows');
const idx = tbody.querySelectorAll('tr').length + 1;
const tr = document.createElement('tr');
tr.className = 'target-row';
const options = itemsList.map(it => `<option value="${it}">${it}</option>`).join('');
tr.innerHTML = `
<td>
<select name="item_${idx}" required>${options}</select>
</td>
<td>
<input name="rate_${idx}" type="number" step="0.01" min="0" value="60.0" required>
</td>
<td>
<button type="button" class="pill" onclick="removeRow(this)" title="Remove">✕</button>
</td>`;
tbody.appendChild(tr);
}
function removeRow(btn) {
const tr = btn.closest('tr');
const tbody = tr && tr.parentElement;
if (!tbody) return;
tbody.removeChild(tr);
// Optionally renumber names to keep indices compact
Array.from(tbody.querySelectorAll('tr')).forEach((row, i) => {
const idx = i + 1;
const sel = row.querySelector('select[name^="item_"]');
const rate = row.querySelector('input[name^="rate_"]');
if (sel) sel.name = `item_${idx}`;
if (rate) rate.name = `rate_${idx}`;
});
}
function toggleDone(cb) {
const tr = cb.closest('tr');
if (!tr) return;
tr.classList.toggle('completed', cb.checked);
}
</script> </script>
</body> </body>
</html> </html>

228
templates/new.html Normal file
View File

@@ -0,0 +1,228 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Satisfactory Production Calculator — New</title>
<style>
:root { --bg: #0f172a; --card: #111827; --text: #e5e7eb; --muted:#94a3b8; --accent:#22d3ee; --ok:#4ade80; }
* { box-sizing: border-box; }
body { margin:0; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: var(--bg); color: var(--text); }
.container { margin: 0 auto; padding: 24px; }
h1 { font-size: 1.75rem; margin: 0 0 8px; }
p { color: var(--muted); margin-top: 0; }
.card { background: var(--card); border: 1px solid #1f2937; border-radius: 10px; padding: 16px; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 800px) { .row { grid-template-columns: 1fr; } }
label { display:block; margin-bottom:8px; font-weight:600; }
select, input[type=number] { width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid #334155; background: #0b1220; color: var(--text); }
button { padding: 10px 14px; border: 0; border-radius: 8px; background: var(--accent); color: #012b30; font-weight: 700; cursor: pointer; }
button:hover { filter: brightness(1.08); }
.mt { margin-top: 16px; }
.error { color: #fda4af; background: #451a1a; border: 1px solid #7f1d1d; padding: 10px; border-radius: 8px; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 8px; border-bottom: 1px solid #1f2937; vertical-align: top; }
th { color: var(--muted); font-weight: 600; }
code { color: var(--accent); }
.mono { font-variant-numeric: tabular-nums; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.pill { display:inline-block; padding:2px 8px; border-radius: 999px; background:#0b1220; border:1px solid #334155; color: var(--muted); font-size: 12px; }
</style>
<link rel="preconnect" href="/">
<meta name="robots" content="noindex">
<meta name="color-scheme" content="dark">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Satisfactory Production Calculator (new build-chain view)">
<link rel="icon" href="data:,">
<base href="/new">
<style>
details summary { cursor: pointer; }
</style>
</head>
<body>
<div class="container">
<h1>Satisfactory Production Calculator — New</h1>
<p>Compute production using the new build-chain logic. This page mirrors the index UI.</p>
<form class="card" method="get" id="targets-form">
<div>
<label>Products</label>
<table style="width:100%; border-collapse: collapse;">
<thead>
<tr>
<th style="width:60%">Item</th>
<th style="width:30%" class="mono">Rate (items/min)</th>
<th style="width:10%"></th>
</tr>
</thead>
<tbody id="targets-rows">
{% for row in targets_ui %}
<tr class="target-row">
<td>
<select name="item_{{ loop.index }}" required>
{% for it in items %}
<option value="{{ it }}" {% if row.item == it %}selected{% endif %}>{{ it }}</option>
{% endfor %}
</select>
</td>
<td>
<input name="rate_{{ loop.index }}" type="number" step="0.01" min="0" value="{{ row.rate }}" required>
</td>
<td>
<button type="button" class="pill" onclick="removeRow(this)" title="Remove"></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="mt">
<button type="button" onclick="addRow()">Add product</button>
</div>
</div>
<div class="mt">
<button type="submit">Calculate</button>
<a href="{{ reset_query }}" class="pill" style="margin-left:8px; display:inline-block; text-decoration:none;">Reset</a>
</div>
{% if error %}
<div class="mt error">{{ error }}</div>
{% endif %}
</form>
{% if chain and chain|length > 0 %}
<div class="mt card">
<h2 style="margin-top:0">Results</h2>
<form method="get" style="background: transparent; border:0; padding:0;">
{# Preserve all target rows in the recalculation #}
{% for row in targets_ui %}
<input type="hidden" name="item_{{ loop.index }}" value="{{ row.item }}">
<input type="hidden" name="rate_{{ loop.index }}" value="{{ row.rate }}">
{% endfor %}
{# Separate table: Alternate recipe choices organized by item #}
{% if alternates_by_item and alternates_by_item|length > 0 %}
<h3>Alternate recipes by item</h3>
<table class="mt">
<thead>
<tr>
<th style="width:40%">Item</th>
<th style="width:60%">Preferred recipe</th>
</tr>
</thead>
<tbody>
{% for row in alternates_by_item %}
<tr>
<td>{{ row.item }}</td>
<td>
<select name="recipe_for_{{ row.slug }}">
{% for opt in row.options %}
<option value="{{ opt.name }}" {% if opt.name == row.selected %}selected{% endif %}>{{ opt.name }} ({{ opt.building }})</option>
{% endfor %}
</select>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<table>
<thead>
<tr>
<th>Recipe</th>
<th>Building</th>
<th class="mono">Production level</th>
<th>Ingress</th>
<th>Outputs</th>
<th>Egress</th>
</tr>
</thead>
<tbody>
{% for p in chain %}
<tr>
<td>{{ p.recipe }}</td>
<td>{{ p.building }}</td>
<td class="mono">{{ '%.2f'|format(p.production_level) }}</td>
<td>
{% if p.ingress and p.ingress|length > 0 %}
<div>
{% for ing in p.ingress %}
<div>{{ ing.item }} ← {{ ing.via }} — <span class="mono">{{ '%.2f'|format(ing.rate) }}</span></div>
{% endfor %}
</div>
{% else %}
<span class="pill">None</span>
{% endif %}
</td>
<td>
{% if p.outputs and p.outputs|length > 0 %}
<div>
{% for out in p.outputs %}
<div>{{ out.item }} — <span class="mono">{{ '%.2f'|format(out.rate) }}</span></div>
{% endfor %}
</div>
{% else %}
<span class="pill">None</span>
{% endif %}
</td>
<td>
{% if p.egress and p.egress|length > 0 %}
<div>
{% for eg in p.egress %}
<div>{{ eg.item }} → {{ eg.to }} — <span class="mono">{{ '%.2f'|format(eg.rate) }}</span></div>
{% endfor %}
</div>
{% else %}
<span class="pill">None</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="mt">
<button type="submit">Recalculate</button>
<a href="{{ reset_query }}" class="pill" style="margin-left:8px; display:inline-block; text-decoration:none;">Reset overrides</a>
</div>
</form>
</div>
{% endif %}
</div>
<script>
// Dynamic add/remove for multiple product targets
const itemsList = {{ items | tojson }};
function addRow() {
const tbody = document.getElementById('targets-rows');
const idx = tbody.querySelectorAll('tr').length + 1;
const tr = document.createElement('tr');
tr.className = 'target-row';
const options = itemsList.map(it => `<option value="${it}">${it}</option>`).join('');
tr.innerHTML = `
<td>
<select name="item_${idx}" required>${options}</select>
</td>
<td>
<input name="rate_${idx}" type="number" step="0.01" min="0" value="60.0" required>
</td>
<td>
<button type="button" class="pill" onclick="removeRow(this)" title="Remove">✕</button>
</td>`;
tbody.appendChild(tr);
}
function removeRow(btn) {
const tr = btn.closest('tr');
const tbody = tr && tr.parentElement;
if (!tbody) return;
tbody.removeChild(tr);
// Renumber names to keep indices compact
Array.from(tbody.querySelectorAll('tr')).forEach((row, i) => {
const idx = i + 1;
const sel = row.querySelector('select[name^="item_"]');
const rate = row.querySelector('input[name^="rate_"]');
if (sel) sel.name = `item_${idx}`;
if (rate) rate.name = `rate_${idx}`;
});
}
</script>
</body>
</html>

407
uv.lock generated
View File

@@ -41,6 +41,70 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
] ]
[[package]]
name = "contourpy"
version = "1.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" },
{ url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" },
{ url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" },
{ url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" },
{ url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" },
{ url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" },
{ url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" },
{ url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" },
{ url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" },
{ url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" },
{ url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" },
{ url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" },
{ url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" },
{ url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" },
{ url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" },
{ url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" },
{ url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" },
{ url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" },
{ url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" },
{ url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" },
{ url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" },
{ url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" },
{ url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" },
{ url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" },
{ url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" },
{ url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" },
{ url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" },
{ url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" },
{ url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" },
{ url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" },
{ url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" },
{ url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" },
{ url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" },
{ url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" },
{ url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" },
{ url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" },
{ url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" },
{ url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" },
{ url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" },
{ url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" },
{ url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" },
{ url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" },
{ url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" },
{ url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" },
]
[[package]]
name = "cycler"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
]
[[package]] [[package]]
name = "flask" name = "flask"
version = "3.1.2" version = "3.1.2"
@@ -58,6 +122,39 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" },
] ]
[[package]]
name = "fonttools"
version = "4.60.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777, upload-time = "2025-09-29T21:12:01.22Z" },
{ url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080, upload-time = "2025-09-29T21:12:03.785Z" },
{ url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082, upload-time = "2025-09-29T21:12:06.382Z" },
{ url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125, upload-time = "2025-09-29T21:12:09.314Z" },
{ url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454, upload-time = "2025-09-29T21:12:11.931Z" },
{ url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495, upload-time = "2025-09-29T21:12:15.241Z" },
{ url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028, upload-time = "2025-09-29T21:12:17.96Z" },
{ url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200, upload-time = "2025-09-29T21:12:20.14Z" },
{ url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" },
{ url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" },
{ url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" },
{ url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" },
{ url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" },
{ url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" },
{ url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" },
{ url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" },
{ url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" },
{ url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" },
{ url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" },
{ url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" },
{ url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" },
{ url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" },
{ url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" },
{ url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" },
{ url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" },
]
[[package]] [[package]]
name = "itsdangerous" name = "itsdangerous"
version = "2.2.0" version = "2.2.0"
@@ -79,6 +176,65 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
] ]
[[package]]
name = "kiwisolver"
version = "1.4.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" },
{ url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" },
{ url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" },
{ url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" },
{ url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" },
{ url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" },
{ url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" },
{ url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" },
{ url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" },
{ url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" },
{ url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" },
{ url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" },
{ url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" },
{ url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" },
{ url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" },
{ url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" },
{ url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" },
{ url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" },
{ url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" },
{ url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" },
{ url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" },
{ url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" },
{ url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" },
{ url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" },
{ url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" },
{ url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" },
{ url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" },
{ url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" },
{ url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" },
{ url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" },
{ url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" },
{ url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" },
{ url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" },
{ url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" },
{ url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" },
{ url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" },
{ url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" },
{ url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" },
{ url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" },
{ url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" },
{ url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" },
{ url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" },
{ url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" },
{ url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" },
{ url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" },
{ url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" },
{ url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" },
{ url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" },
{ url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" },
{ url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" },
]
[[package]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
version = "4.0.0" version = "4.0.0"
@@ -143,6 +299,53 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
] ]
[[package]]
name = "matplotlib"
version = "3.10.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "contourpy" },
{ name = "cycler" },
{ name = "fonttools" },
{ name = "kiwisolver" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "pillow" },
{ name = "pyparsing" },
{ name = "python-dateutil" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f", size = 8273212, upload-time = "2025-10-09T00:26:56.752Z" },
{ url = "https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c", size = 8128713, upload-time = "2025-10-09T00:26:59.001Z" },
{ url = "https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1", size = 8698527, upload-time = "2025-10-09T00:27:00.69Z" },
{ url = "https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632", size = 9529690, upload-time = "2025-10-09T00:27:02.664Z" },
{ url = "https://files.pythonhosted.org/packages/b8/95/b80fc2c1f269f21ff3d193ca697358e24408c33ce2b106a7438a45407b63/matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84", size = 9593732, upload-time = "2025-10-09T00:27:04.653Z" },
{ url = "https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815", size = 8122727, upload-time = "2025-10-09T00:27:06.814Z" },
{ url = "https://files.pythonhosted.org/packages/b3/a6/2faaf48133b82cf3607759027f82b5c702aa99cdfcefb7f93d6ccf26a424/matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7", size = 7992958, upload-time = "2025-10-09T00:27:08.567Z" },
{ url = "https://files.pythonhosted.org/packages/4a/f0/b018fed0b599bd48d84c08794cb242227fe3341952da102ee9d9682db574/matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355", size = 8316849, upload-time = "2025-10-09T00:27:10.254Z" },
{ url = "https://files.pythonhosted.org/packages/b0/b7/bb4f23856197659f275e11a2a164e36e65e9b48ea3e93c4ec25b4f163198/matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b", size = 8178225, upload-time = "2025-10-09T00:27:12.241Z" },
{ url = "https://files.pythonhosted.org/packages/62/56/0600609893ff277e6f3ab3c0cef4eafa6e61006c058e84286c467223d4d5/matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67", size = 8711708, upload-time = "2025-10-09T00:27:13.879Z" },
{ url = "https://files.pythonhosted.org/packages/d8/1a/6bfecb0cafe94d6658f2f1af22c43b76cf7a1c2f0dc34ef84cbb6809617e/matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67", size = 9541409, upload-time = "2025-10-09T00:27:15.684Z" },
{ url = "https://files.pythonhosted.org/packages/08/50/95122a407d7f2e446fd865e2388a232a23f2b81934960ea802f3171518e4/matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84", size = 9594054, upload-time = "2025-10-09T00:27:17.547Z" },
{ url = "https://files.pythonhosted.org/packages/13/76/75b194a43b81583478a81e78a07da8d9ca6ddf50dd0a2ccabf258059481d/matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2", size = 8200100, upload-time = "2025-10-09T00:27:20.039Z" },
{ url = "https://files.pythonhosted.org/packages/f5/9e/6aefebdc9f8235c12bdeeda44cc0383d89c1e41da2c400caf3ee2073a3ce/matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf", size = 8042131, upload-time = "2025-10-09T00:27:21.608Z" },
{ url = "https://files.pythonhosted.org/packages/0d/4b/e5bc2c321b6a7e3a75638d937d19ea267c34bd5a90e12bee76c4d7c7a0d9/matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100", size = 8273787, upload-time = "2025-10-09T00:27:23.27Z" },
{ url = "https://files.pythonhosted.org/packages/86/ad/6efae459c56c2fbc404da154e13e3a6039129f3c942b0152624f1c621f05/matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f", size = 8131348, upload-time = "2025-10-09T00:27:24.926Z" },
{ url = "https://files.pythonhosted.org/packages/a6/5a/a4284d2958dee4116359cc05d7e19c057e64ece1b4ac986ab0f2f4d52d5a/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715", size = 9533949, upload-time = "2025-10-09T00:27:26.704Z" },
{ url = "https://files.pythonhosted.org/packages/de/ff/f3781b5057fa3786623ad8976fc9f7b0d02b2f28534751fd5a44240de4cf/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1", size = 9804247, upload-time = "2025-10-09T00:27:28.514Z" },
{ url = "https://files.pythonhosted.org/packages/47/5a/993a59facb8444efb0e197bf55f545ee449902dcee86a4dfc580c3b61314/matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722", size = 9595497, upload-time = "2025-10-09T00:27:30.418Z" },
{ url = "https://files.pythonhosted.org/packages/0d/a5/77c95aaa9bb32c345cbb49626ad8eb15550cba2e6d4c88081a6c2ac7b08d/matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866", size = 8252732, upload-time = "2025-10-09T00:27:32.332Z" },
{ url = "https://files.pythonhosted.org/packages/74/04/45d269b4268d222390d7817dae77b159651909669a34ee9fdee336db5883/matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb", size = 8124240, upload-time = "2025-10-09T00:27:33.94Z" },
{ url = "https://files.pythonhosted.org/packages/4b/c7/ca01c607bb827158b439208c153d6f14ddb9fb640768f06f7ca3488ae67b/matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1", size = 8316938, upload-time = "2025-10-09T00:27:35.534Z" },
{ url = "https://files.pythonhosted.org/packages/84/d2/5539e66e9f56d2fdec94bb8436f5e449683b4e199bcc897c44fbe3c99e28/matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4", size = 8178245, upload-time = "2025-10-09T00:27:37.334Z" },
{ url = "https://files.pythonhosted.org/packages/77/b5/e6ca22901fd3e4fe433a82e583436dd872f6c966fca7e63cf806b40356f8/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318", size = 9541411, upload-time = "2025-10-09T00:27:39.387Z" },
{ url = "https://files.pythonhosted.org/packages/9e/99/a4524db57cad8fee54b7237239a8f8360bfcfa3170d37c9e71c090c0f409/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca", size = 9803664, upload-time = "2025-10-09T00:27:41.492Z" },
{ url = "https://files.pythonhosted.org/packages/e6/a5/85e2edf76ea0ad4288d174926d9454ea85f3ce5390cc4e6fab196cbf250b/matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", size = 9594066, upload-time = "2025-10-09T00:27:43.694Z" },
{ url = "https://files.pythonhosted.org/packages/39/69/9684368a314f6d83fe5c5ad2a4121a3a8e03723d2e5c8ea17b66c1bad0e7/matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", size = 8342832, upload-time = "2025-10-09T00:27:45.543Z" },
{ url = "https://files.pythonhosted.org/packages/04/5f/e22e08da14bc1a0894184640d47819d2338b792732e20d292bf86e5ab785/matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", size = 8172585, upload-time = "2025-10-09T00:27:47.185Z" },
]
[[package]] [[package]]
name = "mdurl" name = "mdurl"
version = "0.1.2" version = "0.1.2"
@@ -161,6 +364,125 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" },
] ]
[[package]]
name = "numpy"
version = "2.3.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" },
{ url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" },
{ url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" },
{ url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" },
{ url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" },
{ url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" },
{ url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" },
{ url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" },
{ url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" },
{ url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" },
{ url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" },
{ url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" },
{ url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" },
{ url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" },
{ url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" },
{ url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" },
{ url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" },
{ url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" },
{ url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" },
{ url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" },
{ url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" },
{ url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" },
{ url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" },
{ url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" },
{ url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" },
{ url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" },
{ url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" },
{ url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" },
{ url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" },
{ url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" },
{ url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" },
{ url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" },
{ url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" },
{ url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" },
{ url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" },
{ url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" },
{ url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" },
{ url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" },
{ url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" },
{ url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" },
{ url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" },
{ url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" },
{ url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" },
{ url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pillow"
version = "12.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" },
{ url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" },
{ url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" },
{ url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" },
{ url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" },
{ url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" },
{ url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" },
{ url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" },
{ url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" },
{ url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" },
{ url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" },
{ url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" },
{ url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" },
{ url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" },
{ url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" },
{ url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" },
{ url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" },
{ url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" },
{ url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" },
{ url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" },
{ url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" },
{ url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" },
{ url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" },
{ url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" },
{ url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" },
{ url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" },
{ url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" },
{ url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" },
{ url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" },
{ url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" },
{ url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" },
{ url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" },
{ url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" },
{ url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" },
{ url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" },
{ url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" },
{ url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" },
{ url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" },
{ url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" },
{ url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" },
{ url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" },
{ url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" },
{ url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" },
{ url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" },
{ url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" },
{ url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" },
{ url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" },
{ url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" },
{ url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" },
{ url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
]
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.12.4" version = "2.12.4"
@@ -238,23 +560,48 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
] ]
[[package]]
name = "pyparsing"
version = "3.2.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" },
]
[[package]] [[package]]
name = "pysatcalc" name = "pysatcalc"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "flask" }, { name = "flask" },
{ name = "matplotlib" },
{ name = "networkx" }, { name = "networkx" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "rich" }, { name = "rich" },
{ name = "scipy" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "flask", specifier = ">=3.0" }, { name = "flask", specifier = ">=3.0" },
{ name = "matplotlib", specifier = ">=3.10.7" },
{ name = "networkx", specifier = ">=3.5" }, { name = "networkx", specifier = ">=3.5" },
{ name = "pydantic", specifier = ">=2.12.4" }, { name = "pydantic", specifier = ">=2.12.4" },
{ name = "rich", specifier = ">=14.2.0" }, { name = "rich", specifier = ">=14.2.0" },
{ name = "scipy", specifier = ">=1.16.3" },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
] ]
[[package]] [[package]]
@@ -270,6 +617,66 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
] ]
[[package]]
name = "scipy"
version = "1.16.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856, upload-time = "2025-10-28T17:33:31.375Z" },
{ url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306, upload-time = "2025-10-28T17:33:36.516Z" },
{ url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371, upload-time = "2025-10-28T17:33:42.094Z" },
{ url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877, upload-time = "2025-10-28T17:33:48.483Z" },
{ url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103, upload-time = "2025-10-28T17:33:56.495Z" },
{ url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297, upload-time = "2025-10-28T17:34:04.722Z" },
{ url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756, upload-time = "2025-10-28T17:34:13.482Z" },
{ url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566, upload-time = "2025-10-28T17:34:22.384Z" },
{ url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877, upload-time = "2025-10-28T17:35:51.076Z" },
{ url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366, upload-time = "2025-10-28T17:35:59.014Z" },
{ url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931, upload-time = "2025-10-28T17:34:31.451Z" },
{ url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081, upload-time = "2025-10-28T17:34:39.087Z" },
{ url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244, upload-time = "2025-10-28T17:34:45.234Z" },
{ url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753, upload-time = "2025-10-28T17:34:51.793Z" },
{ url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912, upload-time = "2025-10-28T17:34:59.8Z" },
{ url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371, upload-time = "2025-10-28T17:35:08.173Z" },
{ url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477, upload-time = "2025-10-28T17:35:16.7Z" },
{ url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" },
{ url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" },
{ url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" },
{ url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469, upload-time = "2025-10-28T17:36:08.741Z" },
{ url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043, upload-time = "2025-10-28T17:36:16.599Z" },
{ url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952, upload-time = "2025-10-28T17:36:22.966Z" },
{ url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512, upload-time = "2025-10-28T17:36:29.731Z" },
{ url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639, upload-time = "2025-10-28T17:36:37.982Z" },
{ url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729, upload-time = "2025-10-28T17:36:46.547Z" },
{ url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251, upload-time = "2025-10-28T17:36:55.161Z" },
{ url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681, upload-time = "2025-10-28T17:37:04.1Z" },
{ url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423, upload-time = "2025-10-28T17:38:20.005Z" },
{ url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027, upload-time = "2025-10-28T17:38:24.966Z" },
{ url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379, upload-time = "2025-10-28T17:37:14.061Z" },
{ url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052, upload-time = "2025-10-28T17:37:21.709Z" },
{ url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183, upload-time = "2025-10-28T17:37:29.559Z" },
{ url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174, upload-time = "2025-10-28T17:37:36.306Z" },
{ url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852, upload-time = "2025-10-28T17:37:42.228Z" },
{ url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595, upload-time = "2025-10-28T17:37:48.102Z" },
{ url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269, upload-time = "2025-10-28T17:37:53.72Z" },
{ url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779, upload-time = "2025-10-28T17:37:59.393Z" },
{ url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128, upload-time = "2025-10-28T17:38:05.259Z" },
{ url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.15.0" version = "4.15.0"

View File

@@ -1,20 +1,68 @@
from enum import Enum
from typing import Dict
from pydantic import BaseModel, Field
from shared import Machine, Item
class Machines(Enum):
Smelter = Machine(name="Smelter")
Constructor = Machine(name="Constructor")
Assembler = Machine(name="Assembler")
Foundry = Machine(name="Foundry")
Manufacturer = Machine(name="Manufacturer")
Blender = Machine(name="Blender")
Refinery = Machine(name="Refinery")
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")
Screw = Item(name="Screw")
ReinforcedIronPlate = Item(name="Reinforced Iron Plate")
ModularFrame = Item(name="Modular Frame")
HeavyModularFrame = Item(name="Heavy Modular Frame")
EncasedIndustrialBeam = Item(name="Encased Industrial Beam")
SteelPipe = Item(name="Steel Pipe")
SteelBeam = Item(name="Steel Beam")
SteelIngot = Item(name="Steel Ingot")
Coal = Item(name="Coal")
FusedModularFrame = Item(name="Fused Modular Frame")
NitrogenGas = Item(name="Nitrogen Gas")
AluminumCasing = Item(name="Aluminum Casing")
AluminumIngot = Item(name="Aluminum Ingot")
Silica = Item(name="Silica")
AluminumScrap = Item(name="Aluminum Scrap")
AluminaSolution = Item(name="Alumina Solution")
Water = Item(name="Water")
RawQuartz = Item(name="Raw Quartz")
Bauxite = Item(name="Bauxite")
class RawResources(Enum):
Water = Items.Water
Coal = Items.Coal
IronOre = Items.IronOre
CopperOre = Items.CopperOre
Limestone = Items.Limestone
NitrogenGas = Items.NitrogenGas
RawQuartz = Items.RawQuartz
Bauxite = Items.Bauxite
class Recipe(BaseModel):
name: str
building: Machines
outputs: Dict[Items, float]
inputs: Dict[Items, float] = Field(default_factory=dict)
byproducts: Dict[Items, float] = Field(default_factory=dict)
class Recipes(Enum): class Recipes(Enum):
# Mining
IronOre = Recipe(
name="Iron Ore",
building=Machines.Miner,
outputs={Items.IronOre: 60.0},
)
CopperOre = Recipe(
name="Copper Ore",
building=Machines.Miner,
outputs={Items.CopperOre: 60.0},
)
Limestone = Recipe(
name="Limestone",
building=Machines.Miner,
outputs={Items.Limestone: 60.0},
)
# Smelting # Smelting
IronIngot = Recipe( IronIngot = Recipe(
name="Iron Ingot", name="Iron Ingot",
@@ -77,3 +125,116 @@ class Recipes(Enum):
Items.Screw: 60.0, Items.Screw: 60.0,
}, },
) )
ModularFrame = Recipe(
name="Modular Frame",
building=Machines.Assembler,
outputs={Items.ModularFrame: 2.0},
inputs={
Items.IronRod: 12.0,
Items.ReinforcedIronPlate: 3.0,
},
)
HeavyModularFrame = Recipe(
name="Heavy Modular Frame",
building=Machines.Manufacturer,
outputs={Items.HeavyModularFrame: 2.0},
inputs={
Items.SteelPipe: 40.0,
Items.Screw: 240.0,
Items.EncasedIndustrialBeam: 10.0,
Items.ModularFrame: 10.0,
},
)
EncasedIndustrialBeam = Recipe(
name="Encased Industrial Beam",
building=Machines.Assembler,
outputs={Items.EncasedIndustrialBeam: 6.0},
inputs={
Items.Concrete: 36.0,
Items.SteelBeam: 18.0,
},
)
SteelPipe = Recipe(
name="Steel Pipe",
building=Machines.Constructor,
outputs={Items.SteelPipe: 20.0},
inputs={
Items.SteelIngot: 30.0
},
)
SteelBeam = Recipe(
name="Steel Beam",
building=Machines.Constructor,
outputs={Items.SteelBeam: 15.0},
inputs={
Items.SteelIngot: 60.0
},
)
SteelIngot = Recipe(
name="Steel Ingot",
building=Machines.Foundry,
outputs={Items.SteelIngot: 45.0},
inputs={
Items.Coal: 45.0,
Items.IronOre: 45.0,
}
)
FusedModularFrame = Recipe(
name="Fused Modular Frame",
building=Machines.Blender,
outputs={Items.FusedModularFrame: 1.5},
inputs={
Items.NitrogenGas: 37.5,
Items.AluminumCasing: 75.0,
Items.HeavyModularFrame: 1.5,
},
)
AluminumCasing = Recipe(
name="Aluminum Casing",
building=Machines.Constructor,
outputs={Items.AluminumCasing: 60.0},
inputs={
Items.AluminumIngot: 90.0,
},
)
AluminumIngot = Recipe(
name="Aluminum Ingot",
building=Machines.Foundry,
outputs={Items.AluminumIngot: 60.0},
inputs={
Items.Silica: 75.0,
Items.AluminumScrap: 90.0,
}
)
AluminumScrap = Recipe(
name="Aluminum Scrap",
building=Machines.Refinery,
outputs={
Items.AluminumScrap: 360.0
},
inputs={
Items.Coal: 120.0,
Items.AluminaSolution: 240.0,
},
byproducts={Items.Water: 120.0}
)
Silica = Recipe(
name="Silica",
building=Machines.Constructor,
outputs={Items.Silica: 37.5},
inputs={
Items.RawQuartz: 22.5,
}
)
AluminaSolution = Recipe(
name="Alumina Solution",
building=Machines.Refinery,
outputs={
Items.AluminaSolution: 120.0
},
inputs={
Items.Water: 180.0,
Items.Bauxite: 120.0,
},
byproducts={Items.Silica: 50.0}
)