Compare commits
9 Commits
58c83f6140
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 67ade78747 | |||
| 9f4ff856e6 | |||
| 47bef8cbe4 | |||
| adee0f5ffb | |||
| 9096d38f78 | |||
| f9e1ad0768 | |||
| 11fe895274 | |||
| df2bf7058c | |||
| bdd0d26acb |
3
.idea/dictionaries/project.xml
generated
3
.idea/dictionaries/project.xml
generated
@@ -4,9 +4,12 @@
|
||||
<w>aurovite</w>
|
||||
<w>callanite</w>
|
||||
<w>caterium</w>
|
||||
<w>heatsink</w>
|
||||
<w>kyalite</w>
|
||||
<w>larrussite</w>
|
||||
<w>quickwire</w>
|
||||
<w>siterite</w>
|
||||
<w>tailings</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
6
.idea/ruff.xml
generated
Normal file
6
.idea/ruff.xml
generated
Normal 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>
|
||||
915
main.py
915
main.py
@@ -1,14 +1,18 @@
|
||||
from collections import defaultdict, deque
|
||||
from enum import Enum
|
||||
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
|
||||
from flask import Flask, render_template, request
|
||||
from pydantic import BaseModel, Field
|
||||
from plus import Items, Machines, Recipes, Recipe
|
||||
from flask import Flask, render_template, request, jsonify
|
||||
from pydantic import BaseModel, Field, computed_field, BeforeValidator
|
||||
from plus import Items, Machines, Recipes, Recipe, RawResources, MachineOrder
|
||||
# from vanilla import Items, Machines, Recipes, Recipe, RawResources
|
||||
from rich import print
|
||||
import matplotlib.pyplot as plt
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
|
||||
# Helpers to map item names to safe query parameter keys
|
||||
def _slugify(name: str) -> str:
|
||||
s = ''.join(ch.lower() if ch.isalnum() else '_' for ch in name)
|
||||
@@ -25,57 +29,263 @@ def _slugify(name: str) -> str:
|
||||
prev_us = False
|
||||
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):
|
||||
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))
|
||||
|
||||
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:
|
||||
if not targets:
|
||||
return {}
|
||||
recipe_map: Dict[Items, list[Recipes]] = defaultdict(list)
|
||||
for r in Recipes:
|
||||
for o in r.value.outputs:
|
||||
recipe_map[o].append(r.value)
|
||||
# print(recipe_map)
|
||||
demand = defaultdict(float)
|
||||
for target in targets:
|
||||
demand[target] = targets[target]
|
||||
production = defaultdict(float)
|
||||
# add demands to production
|
||||
# find a recipe for that demand
|
||||
# add inputs to demand
|
||||
queue = deque(targets)
|
||||
g = nx.DiGraph()
|
||||
production_queue = []
|
||||
raw_resources = defaultdict(float)
|
||||
production_chain = defaultdict(float)
|
||||
while queue:
|
||||
item = queue.popleft()
|
||||
recipe = get_recipe(item, recipe_map)
|
||||
if recipe is None:
|
||||
raw_resources[item] += demand[item] - raw_resources[item]
|
||||
continue
|
||||
@computed_field
|
||||
@property
|
||||
def production_level(self) -> float:
|
||||
levels = []
|
||||
for out, quantity in recipe.outputs.items():
|
||||
if out in demand:
|
||||
target_quantity = demand[out] - production[out]
|
||||
if target_quantity > 0:
|
||||
levels.append(target_quantity / quantity)
|
||||
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
|
||||
|
||||
|
||||
|
||||
class ProductionChain:
|
||||
def __init__(self, preferred_recipes: Optional[Dict[Items, Recipe]] = None):
|
||||
self.item_to_recipies: Dict[Items, list[Recipes]] = defaultdict(list)
|
||||
for r in Recipes:
|
||||
for o in r.value.outputs:
|
||||
self.item_to_recipies[o].append(r.value)
|
||||
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:
|
||||
levels.append(0)
|
||||
production_level = max(levels) if max(levels) > 0 else 0
|
||||
production_chain[recipe.name] = production_level
|
||||
for out, quantity in recipe.outputs.items():
|
||||
production[out] += production_level * quantity
|
||||
for inp, quantity in recipe.inputs.items():
|
||||
queue.append(inp)
|
||||
demand[inp] += production_level * quantity
|
||||
print(demand, production, raw_resources, production_chain)
|
||||
return production
|
||||
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)
|
||||
for target in targets:
|
||||
demand[target] = targets[target]
|
||||
|
||||
queue = deque(targets)
|
||||
production = defaultdict(float)
|
||||
raw_resources = defaultdict(float)
|
||||
production_chain = defaultdict(float)
|
||||
while queue:
|
||||
item = queue.popleft()
|
||||
if item == Items.Silica:
|
||||
print("silica")
|
||||
recipe = self.get_recipe(item)
|
||||
if item in RawResources:
|
||||
raw_resources[item] += demand[item] - raw_resources[item]
|
||||
continue
|
||||
if item in production:
|
||||
if production[item] == demand[item]:
|
||||
continue
|
||||
|
||||
levels = []
|
||||
|
||||
for out, quantity in recipe.outputs.items():
|
||||
if out in demand:
|
||||
target_quantity = demand[out] - production[out]
|
||||
levels.append(target_quantity / quantity)
|
||||
|
||||
production_level = max(levels)
|
||||
if production_level == 0:
|
||||
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():
|
||||
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():
|
||||
queue.append(inp)
|
||||
demand[inp] += production_level * quantity
|
||||
|
||||
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]]:
|
||||
@@ -232,6 +442,18 @@ def compute_chain(targets: Dict[Items, float], preferred_by_output: Optional[Dic
|
||||
unused_byproducts[item.value.name] = unused
|
||||
|
||||
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"])
|
||||
@@ -241,13 +463,77 @@ def index():
|
||||
name_to_item = {i.value.name: i for i in Items}
|
||||
|
||||
result = None
|
||||
error = None
|
||||
selected_item = item_names[0] if item_names else ""
|
||||
selected_rate = 60.0
|
||||
error_msgs: List[str] = []
|
||||
|
||||
# Read from query parameters for bookmarkable URLs
|
||||
item_name = request.args.get("item") or selected_item
|
||||
rate_str = request.args.get("rate")
|
||||
# --------------------
|
||||
# Parse targets (support multiple products)
|
||||
# --------------------
|
||||
# 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 ""
|
||||
|
||||
# Parse per-item recipe overrides from query params recipe_for_<slug(item)>
|
||||
@@ -272,23 +558,9 @@ def index():
|
||||
if value in candidates:
|
||||
overrides[item_enum] = value
|
||||
|
||||
rate = None
|
||||
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
|
||||
# Candidate recipes for the (first) selected item (single-target convenience)
|
||||
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:
|
||||
for r in Recipes:
|
||||
recipe = r.value
|
||||
@@ -312,61 +584,180 @@ def index():
|
||||
# Compute and also prepare per-item override options based on resulting chain
|
||||
overrides_ui: List[dict] = []
|
||||
|
||||
if not error and item_name and rate is not None:
|
||||
item_obj = name_to_item.get(item_name)
|
||||
if item_obj is None:
|
||||
error = "Unknown item selected."
|
||||
else:
|
||||
targets = {item_obj: rate}
|
||||
raw, steps, outputs, unused = compute_chain(targets, preferred_by_output=preferred)
|
||||
result = {
|
||||
"targets": {item_name: rate},
|
||||
"raw": raw,
|
||||
"steps": steps,
|
||||
"outputs": outputs,
|
||||
"unused": unused,
|
||||
}
|
||||
if targets:
|
||||
# Translate preferred mapping (Items -> recipe name) into objects and pass to ProductionChain
|
||||
preferred_recipes_obj: Optional[Dict[Items, Recipe]] = None
|
||||
if preferred:
|
||||
name_to_recipe = {r.value.name: r.value for r in Recipes}
|
||||
preferred_recipes_obj = {}
|
||||
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)
|
||||
|
||||
# Collect unique output items from steps
|
||||
unique_items = []
|
||||
seen = set()
|
||||
for s in steps:
|
||||
item_nm = s.get("item")
|
||||
if item_nm and item_nm not in seen:
|
||||
seen.add(item_nm)
|
||||
unique_items.append(item_nm)
|
||||
# For each item, compute candidate recipes and current selection
|
||||
for item_nm in unique_items:
|
||||
item_enum2 = name_to_item.get(item_nm)
|
||||
if not item_enum2:
|
||||
continue
|
||||
candidates = []
|
||||
for r in Recipes:
|
||||
rec = r.value
|
||||
if item_enum2 in rec.outputs:
|
||||
candidates.append({
|
||||
"name": rec.name,
|
||||
"building": rec.building.value.name,
|
||||
})
|
||||
if len(candidates) <= 1:
|
||||
continue # only show when alternates exist
|
||||
candidates.sort(key=lambda x: (x["name"]))
|
||||
sel = None
|
||||
if preferred and item_enum2 in preferred:
|
||||
sel = preferred[item_enum2]
|
||||
slug = _slugify(item_nm)
|
||||
overrides_ui.append({
|
||||
"item_name": item_nm,
|
||||
"slug": slug,
|
||||
"options": candidates,
|
||||
"selected": sel or "",
|
||||
# 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 reset query (clear overrides)
|
||||
reset_query = f"?item={selected_item}&rate={selected_rate}"
|
||||
# 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 = {
|
||||
"targets": result_targets,
|
||||
"raw": raw,
|
||||
"steps": steps,
|
||||
"outputs": outputs,
|
||||
"unused": unused,
|
||||
"excess": excess,
|
||||
}
|
||||
|
||||
# Collect unique output items from steps for override options
|
||||
unique_items = []
|
||||
seen = set()
|
||||
for s in steps:
|
||||
item_nm = s.get("item")
|
||||
if item_nm and item_nm not in seen:
|
||||
seen.add(item_nm)
|
||||
unique_items.append(item_nm)
|
||||
for item_nm in unique_items:
|
||||
item_enum2 = name_to_item.get(item_nm)
|
||||
if not item_enum2:
|
||||
continue
|
||||
candidates = []
|
||||
for r in Recipes:
|
||||
rec = r.value
|
||||
if item_enum2 in rec.outputs:
|
||||
candidates.append({
|
||||
"name": rec.name,
|
||||
"building": rec.building.value.name,
|
||||
})
|
||||
if len(candidates) <= 1:
|
||||
continue
|
||||
candidates.sort(key=lambda x: (x["name"]))
|
||||
sel = None
|
||||
if preferred and item_enum2 in preferred:
|
||||
sel = preferred[item_enum2]
|
||||
slug = _slugify(item_nm)
|
||||
overrides_ui.append({
|
||||
"item_name": item_nm,
|
||||
"slug": slug,
|
||||
"options": candidates,
|
||||
"selected": sel or "",
|
||||
})
|
||||
|
||||
# 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}"
|
||||
if selected_recipe:
|
||||
reset_query += f"&recipe={selected_recipe}"
|
||||
|
||||
# Combine error messages
|
||||
error = " ".join(error_msgs) if error_msgs else None
|
||||
|
||||
return render_template(
|
||||
"index.html",
|
||||
items=item_names,
|
||||
@@ -378,6 +769,281 @@ def index():
|
||||
selected_recipe=selected_recipe,
|
||||
overrides_ui=overrides_ui,
|
||||
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__":
|
||||
# For local dev: python main.py
|
||||
# app.run(host="0.0.0.0", port=5000, debug=True)
|
||||
compute_chain2({Items.ModularFrame: 45.0})
|
||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||
# 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
495
plus.py
@@ -1,15 +1,9 @@
|
||||
from enum import Enum
|
||||
from typing import Dict
|
||||
|
||||
from typing import Dict, Tuple
|
||||
from shared import Machine, Item
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
|
||||
class Machine(BaseModel):
|
||||
name: str
|
||||
|
||||
class Machines(Enum):
|
||||
Miner = Machine(name="Miner")
|
||||
Smelter = Machine(name="Smelter")
|
||||
@@ -18,6 +12,24 @@ class Machines(Enum):
|
||||
Sorter = Machine(name="Sorter")
|
||||
Crusher = Machine(name="Crusher")
|
||||
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):
|
||||
IronIngot = Item(name="Iron Ingot")
|
||||
@@ -76,15 +88,55 @@ class Items(Enum):
|
||||
Rotor = Item(name="Rotor")
|
||||
BronzeFrame = Item(name="Bronze Frame")
|
||||
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):
|
||||
model_config = ConfigDict(frozen=True)
|
||||
name: str # Human-friendly name
|
||||
building: Machines # e.g., "Smelter", "Constructor"
|
||||
outputs: Dict[Items, float] # Produced item name
|
||||
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):
|
||||
# Crusher
|
||||
@@ -103,9 +155,9 @@ class Recipes(Enum):
|
||||
building=Machines.Crusher,
|
||||
outputs={
|
||||
Items.CrushedLarrussite: 40.0,
|
||||
Items.CrushedGangue: 20.0,
|
||||
},
|
||||
inputs={Items.LarrussiteOre: 60.0},
|
||||
byproducts={Items.CrushedGangue: 20.0,}
|
||||
)
|
||||
CrushedCallanite = Recipe(
|
||||
name="Crushed Callanite",
|
||||
@@ -125,6 +177,17 @@ class Recipes(Enum):
|
||||
},
|
||||
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
|
||||
CoarseSand = Recipe(
|
||||
name="Coarse Sand",
|
||||
@@ -142,7 +205,14 @@ class Recipes(Enum):
|
||||
},
|
||||
inputs={Items.CrushedMagnesium: 80.0},
|
||||
)
|
||||
|
||||
CarbonPowder = Recipe(
|
||||
name="Carbon Powder",
|
||||
building=Machines.Crusher,
|
||||
outputs={
|
||||
Items.CarbonPowder: 40.0
|
||||
},
|
||||
inputs={Items.Coal: 80.0},
|
||||
)
|
||||
# Sorter
|
||||
# - Simple Sorting
|
||||
CrushedIron = Recipe(
|
||||
@@ -151,9 +221,11 @@ class Recipes(Enum):
|
||||
outputs={
|
||||
Items.CrushedIron: 90.0,
|
||||
Items.CrushedTin: 60.0,
|
||||
Items.CrushedGangue: 40.0,
|
||||
},
|
||||
inputs={Items.CrushedSiterite: 120.0},
|
||||
byproducts={
|
||||
Items.CrushedGangue: 40.0,
|
||||
},
|
||||
)
|
||||
CrushedCopper = Recipe(
|
||||
name="Crushed Copper",
|
||||
@@ -161,19 +233,57 @@ class Recipes(Enum):
|
||||
outputs={
|
||||
Items.CrushedCopper: 96.0,
|
||||
Items.CrushedMagnesium: 80.0,
|
||||
Items.CrushedGangue: 40.0,
|
||||
},
|
||||
inputs={Items.CrushedCallanite: 120.0},
|
||||
byproducts={
|
||||
Items.CrushedGangue: 40.0,
|
||||
},
|
||||
)
|
||||
CrushedZinc = Recipe(
|
||||
name="Crushed Zinc",
|
||||
building=Machines.Sorter,
|
||||
outputs={
|
||||
Items.CrushedZinc: 96.0,
|
||||
Items.CrushedMagnesium: 48.0,
|
||||
Items.CrushedGangue: 60.0,
|
||||
},
|
||||
inputs={Items.CrushedLarrussite: 120.0},
|
||||
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
|
||||
# - smelting
|
||||
@@ -195,6 +305,12 @@ class Recipes(Enum):
|
||||
outputs={Items.TinIngot: 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
|
||||
ImpureIronIngot = Recipe(
|
||||
name="Impure Iron Ingot",
|
||||
@@ -217,8 +333,8 @@ class Recipes(Enum):
|
||||
ImpureCateriumIngot = Recipe(
|
||||
name="Impure Caterium Ingot",
|
||||
building=Machines.Smelter,
|
||||
inputs={Items.CrushedAurovite: 40.0},
|
||||
outputs={Items.CateriumIngot: 24.0},
|
||||
inputs={Items.CrushedAurovite: 40.0},
|
||||
)
|
||||
ZincIngot = Recipe(
|
||||
name="Zinc Ingot",
|
||||
@@ -233,6 +349,12 @@ class Recipes(Enum):
|
||||
outputs={Items.Glass: 20.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
|
||||
# - Standard Parts
|
||||
IronPlate = Recipe(
|
||||
@@ -307,6 +429,18 @@ class Recipes(Enum):
|
||||
outputs={Items.ZincPlates: 15.0},
|
||||
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
|
||||
IronWire = Recipe(
|
||||
name="Iron Wire",
|
||||
@@ -351,7 +485,25 @@ class Recipes(Enum):
|
||||
outputs={Items.Concrete: 15.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
|
||||
# - Building Parts
|
||||
SolarCell = Recipe(
|
||||
@@ -383,6 +535,35 @@ class Recipes(Enum):
|
||||
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
|
||||
ThermalSilica = Recipe(
|
||||
name="Thermal Silica",
|
||||
@@ -393,6 +574,15 @@ class Recipes(Enum):
|
||||
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
|
||||
# - Building Parts
|
||||
SmoothBeltDrive = Recipe(
|
||||
@@ -404,6 +594,24 @@ class Recipes(Enum):
|
||||
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
|
||||
TinPlate = Recipe(
|
||||
name="Tin Plate",
|
||||
@@ -468,6 +676,25 @@ class Recipes(Enum):
|
||||
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(
|
||||
name="AI Limiter",
|
||||
building=Machines.Assembler,
|
||||
@@ -477,3 +704,231 @@ class Recipes(Enum):
|
||||
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()
|
||||
@@ -5,7 +5,9 @@ description = "Web-based Satisfactory production calculator in Python (Flask)"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"flask>=3.0",
|
||||
"matplotlib>=3.10.7",
|
||||
"networkx>=3.5",
|
||||
"pydantic>=2.12.4",
|
||||
"rich>=14.2.0",
|
||||
"scipy>=1.16.3",
|
||||
]
|
||||
|
||||
12
shared.py
Normal file
12
shared.py
Normal 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
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
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; }
|
||||
.completed td:not(.done-col) { text-decoration: line-through; color: var(--muted); }
|
||||
.done-col { width: 1%; white-space: nowrap; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -33,19 +35,39 @@
|
||||
<h1>Satisfactory Production Calculator</h1>
|
||||
<p>Compute buildings and raw inputs for a target output rate (items per minute).</p>
|
||||
|
||||
<form class="card" method="get">
|
||||
<div class="row">
|
||||
<div>
|
||||
<label for="item">Product</label>
|
||||
<select id="item" name="item" required>
|
||||
{% for it in items %}
|
||||
<option value="{{ it }}" {% if selected_item and it==selected_item %}selected{% endif %}>{{ it }}</option>
|
||||
<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 %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rate">Target rate (items/min)</label>
|
||||
<input id="rate" name="rate" type="number" step="0.01" min="0" value="{{ selected_rate }}" required>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="mt">
|
||||
<button type="button" onclick="addRow()">Add product</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,11 +86,12 @@
|
||||
{% if result.raw %}
|
||||
<table>
|
||||
<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>
|
||||
<tbody>
|
||||
{% for item,rate in result.raw.items() %}
|
||||
<tr>
|
||||
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
|
||||
<td>{{ item }}</td>
|
||||
<td class="mono">{{ '%.2f'|format(rate) }}</td>
|
||||
</tr>
|
||||
@@ -82,8 +105,11 @@
|
||||
{% if overrides_ui and overrides_ui|length > 0 %}
|
||||
<h3 class="mt">Recipe overrides</h3>
|
||||
<form method="get" class="card" style="background: transparent; border:0; padding:0;">
|
||||
<input type="hidden" name="item" value="{{ selected_item }}">
|
||||
<input type="hidden" name="rate" value="{{ selected_rate }}">
|
||||
{# 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 %}
|
||||
{% if selected_recipe %}<input type="hidden" name="recipe" value="{{ selected_recipe }}">{% endif %}
|
||||
<table>
|
||||
<thead>
|
||||
@@ -120,8 +146,10 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Output Item</th>
|
||||
<th class="done-col">Done</th>
|
||||
<th>Recipe</th>
|
||||
<th>Inputs</th>
|
||||
<th>Output Items</th>
|
||||
<th>Building</th>
|
||||
<th class="mono">Target rate</th>
|
||||
<th class="mono">Per-building output</th>
|
||||
@@ -132,9 +160,33 @@
|
||||
<tbody>
|
||||
{% for s in result.steps %}
|
||||
<tr>
|
||||
<td>{{ s.item }}</td>
|
||||
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></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 class="mono">{{ '%.2f'|format(s.target_rate) }}</td>
|
||||
<td class="mono">{{ '%.2f'|format(s.per_building_output) }}</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>
|
||||
{% 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>
|
||||
{% if result.unused and result.unused|length > 0 %}
|
||||
<table>
|
||||
<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>
|
||||
<tbody>
|
||||
{% for item,rate in result.unused.items() %}
|
||||
<tr>
|
||||
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
|
||||
<td>{{ item }}</td>
|
||||
<td class="mono">{{ '%.2f'|format(rate) }}</td>
|
||||
</tr>
|
||||
@@ -170,8 +243,45 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Tiny helper to maintain selected item after submit if not handled by server
|
||||
// (we already set it via Jinja, this is just progressive enhancement)
|
||||
// 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);
|
||||
// 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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
228
templates/new.html
Normal file
228
templates/new.html
Normal 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
407
uv.lock
generated
@@ -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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "flask"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "itsdangerous"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "markdown-it-py"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "mdurl"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pydantic"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pysatcalc"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "flask" },
|
||||
{ name = "matplotlib" },
|
||||
{ name = "networkx" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "rich" },
|
||||
{ name = "scipy" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "flask", specifier = ">=3.0" },
|
||||
{ name = "matplotlib", specifier = ">=3.10.7" },
|
||||
{ name = "networkx", specifier = ">=3.5" },
|
||||
{ name = "pydantic", specifier = ">=2.12.4" },
|
||||
{ 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]]
|
||||
@@ -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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
|
||||
193
vanilla.py
193
vanilla.py
@@ -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):
|
||||
# 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
|
||||
IronIngot = Recipe(
|
||||
name="Iron Ingot",
|
||||
@@ -77,3 +125,116 @@ class Recipes(Enum):
|
||||
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}
|
||||
)
|
||||
Reference in New Issue
Block a user