import math from dataclasses import dataclass, field from typing import List, Optional @dataclass class FluteProfile: name: str height_mm: float factor: float # Direct input now pitch_mm: float = 0.0 # Optional/Reference only now def __post_init__(self): pass # No calculation needed @dataclass class PaperGrade: code: str gsm: int rate: float rct_cd_N: float # Ring Crush CD in Newtons (N) rct_md_N: float # Ring Crush MD in Newtons (N) bursting_strength_kpa: float # kPa ash_pct: float moisture_pct: float @dataclass class FactoryConfig: wastage_process_pct: float cost_conversion_per_kg: float cost_fixed_setup: float margin_pct: float process_efficiency_pct: float currency: str available_reel_sizes: List[int] # Value-Add Costs (optional processes) cost_printing_per_1000: float = 0.0 cost_printing_plate: float = 0.0 cost_uv_per_1000: float = 0.0 cost_lamination_per_1000: float = 0.0 cost_die_cutting_per_1000: float = 0.0 cost_die_frame: float = 0.0 ect_conversion_factor: float = 0.85 @dataclass class LayerInput: layer_type: str # "Liner" or "Flute" paper: PaperGrade flute_profile: Optional[FluteProfile] = None deckle_mm: Optional[int] = None # User provided reel size for this layer @dataclass class CartonSpecification: length_mm: float width_mm: float height_mm: float layers: List[LayerInput] ply_type: str # "3 Ply", "5 Ply", "2+1 (Bleach Card)" stitch_allowance_mm: float = 40.0 # Process Flags has_printing: bool = False has_uv: bool = False has_lamination: bool = False has_die_cutting: bool = False