import datetime import re from typing import Optional from sqlalchemy import String, Select, select, Table, Column, ForeignKey from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): pass class Resource(Base): __tablename__ = "resources" id: Mapped[int] = mapped_column(primary_key=True) label: Mapped[str] = mapped_column(String(127)) wiki_url: Mapped[str] # FIXME: rename to uri recipes_populated_at: Mapped[Optional[datetime.datetime]] flows: Mapped[list["ResourceFlow"]] = relationship(back_populates="resource") @classmethod def by_label(cls, search: str) -> Select[tuple["Resource"]]: return select(cls).where(cls.label.ilike(search)) def __repr__(self): return ( f"Resource(id={self.id}, " f"label={self.label}, " f"wiki_url={self.wiki_url}, " f"recipes_populated_at={self.recipes_populated_at})" ) class Factory(Base): __tablename__ = "factories" id: Mapped[int] = mapped_column(primary_key=True) label: Mapped[str] = mapped_column(String(127)) wiki_url: Mapped[str] # FIXME: rename to uri @classmethod def by_label(cls, search: str) -> Select[tuple["Factory"]]: return select(cls).where(cls.label.ilike(search)) def __repr__(self): return f"Factory(id={self.id}, label={self.label}, wiki_url={self.wiki_url})" ingredients_table = Table( "recipe_ingredients", Base.metadata, Column("recipe_id", ForeignKey("recipes.id"), primary_key=True), Column("resource_flow_id", ForeignKey("resource_flows.id"), primary_key=True), ) results_table = Table( "recipe_results", Base.metadata, Column("recipe_id", ForeignKey("recipes.id"), primary_key=True), Column("resource_flow_id", ForeignKey("resource_flows.id"), primary_key=True), ) amount_number = re.compile(r"^\d*(\.\d+)?") time_number = re.compile(r"^(\d+) seconds?") class ResourceFlow(Base): __tablename__ = "resource_flows" id: Mapped[int] = mapped_column(primary_key=True) ingredient_in: Mapped[Optional["Recipe"]] = relationship(secondary=ingredients_table, back_populates="ingredients") result_of: Mapped[Optional["Recipe"]] = relationship(secondary=results_table, back_populates="results") resource_id: Mapped[int] = mapped_column(ForeignKey("resources.id")) resource: Mapped["Resource"] = relationship(back_populates="flows") amount: Mapped[str] time: Mapped[str] def amount_per_minute(self) -> float: per_crafting_step_str = amount_number.match(self.amount).group(0) per_crafting_step = float(per_crafting_step_str) crafting_time_seconds_str = time_number.match(self.time).group(1) crafting_time_seconds = float(crafting_time_seconds_str) return per_crafting_step * (60.0 / crafting_time_seconds) def describe(self) -> str: return f"{repr(str(self.resource.label))}x{self.amount}" def __repr__(self): return f"ResourceFlow(id={self.id}, resource_id={self.resource_id}, amount={self.amount}, time={self.time})" class Recipe(Base): __tablename__ = "recipes" id: Mapped[int] = mapped_column(primary_key=True) factory_id: Mapped[int] = mapped_column(ForeignKey("factories.id")) factory: Mapped["Factory"] = relationship() ingredients: Mapped[list["ResourceFlow"]] = relationship( secondary=ingredients_table, back_populates="ingredient_in" ) results: Mapped[list["ResourceFlow"]] = relationship(secondary=results_table, back_populates="result_of") def describe(self) -> str: def list_flows(flows: list["ResourceFlow"]) -> str: return ", ".join(map(ResourceFlow.describe, flows)) return ( f"in machine: {self.factory.label}, " f"ingredients: {list_flows(self.ingredients)}, " f"results: {list_flows(self.results)}" ) def __repr__(self): return f"Recipe(id={self.id}, factory={self.factory}, ingredients={self.ingredients}, results={self.results})"