from __future__ import annotations from collections import defaultdict from .config import DATASET_TYPE, GeneratorConfig from .models import DatasetBundle, stable_id MATERIAL_GROUPS = ( "STEEL_PLATE", "SECTION_STEEL", "PIPE", "VALVE", "PUMP", "CABLE", "ELECTRICAL", "HVAC", "PAINT", "WELDING", "OUTFITTING", "MACHINERY", ) def _material_identity(index: int) -> tuple[str, str]: code = f"MAT-SYN-{index:05d}" return code, stable_id("material", code, prefix="MAT") def generate_materials(bundle: DatasetBundle, config: GeneratorConfig) -> DatasetBundle: p = config.profile materials: list[dict] = [] inventory: list[dict] = [] allocations: list[dict] = [] substitutes: list[dict] = [] receipts: list[dict] = [] projects = [stable_id("project", code, prefix="PRJ") for code in config.project_codes] for index in range(1, p.material_count + 1): code, material_id = _material_identity(index) group = MATERIAL_GROUPS[(index - 1) % len(MATERIAL_GROUPS)] bucket = (index - 1) % 20 if bucket < 5: sourcing = "MAKE" elif bucket < 15: sourcing = "BUY" elif bucket < 18: sourcing = "OUTSOURCE" elif bucket == 18: sourcing = "OWNER_SUPPLIED" else: sourcing = "BUY" fulfillment = ("STOCK", "TRANSFER", "PLANNED_RECEIPT", "NEW_SUPPLY")[(index - 1) % 4] if index % 97 == 0: fulfillment = "DESIGN_PENDING" routing_id = None if sourcing == "MAKE": routing_no = ((index - 1) % p.routing_count) + 1 routing_code = f"RT-SYN-{routing_no:04d}" routing_id = stable_id("routing", routing_code, prefix="RTG") supplier_id = None if sourcing in {"BUY", "OUTSOURCE"}: supplier_no = ((index - 1) % p.supplier_count) + 1 supplier_code = f"SUP-SYN-{supplier_no:03d}" supplier_id = stable_id("supplier", supplier_code, prefix="SUP") materials.append({ "materialId": material_id, "materialCode": code, "name": f"{group}-模拟物料-{index:05d}", "materialGroup": group, "sourcingMode": sourcing, "fulfillmentMode": fulfillment, "unit": "KG" if group in {"STEEL_PLATE", "SECTION_STEEL", "PIPE"} else "EA", "unitWeightKg": round(0.5 + (index % 240) * 0.75, 3), "leadTimeDays": 0 if sourcing == "MAKE" else 10 + index % 90, "safetyStock": index % 7, "moq": 1 + index % 20, "orderMultiple": 1 + index % 10, "yieldRate": round(0.94 + (index % 6) * 0.01, 3), "scrapRate": round(0.01 + (index % 4) * 0.005, 3), "routingId": routing_id, "primarySupplierId": supplier_id, "designStatus": "PENDING" if fulfillment == "DESIGN_PENDING" else "RELEASED", "datasetType": DATASET_TYPE, }) if index % 5 != 0: warehouse_no = ((index - 1) % 8) + 1 warehouse_code = f"WH-SYN-{warehouse_no:03d}" inventory_id = stable_id("inventory", material_id, warehouse_code, prefix="INV") quantity = round(2 + (index * 17) % 20, 3) inventory.append({ "inventoryId": inventory_id, "materialId": material_id, "warehouseId": stable_id("warehouse", warehouse_code, prefix="WH"), "batchNo": f"BATCH-SYN-{index:06d}", "quantity": quantity, "qualityStatus": "RELEASED" if index % 23 else "HOLD", "projectRestricted": bool(index % 11 == 0), "expiryDate": None if group not in {"PAINT", "WELDING"} else f"2027-{(index % 12)+1:02d}-28", }) if index % 3 == 0: project_id = projects[(index - 1) % len(projects)] allocations.append({ "allocationId": stable_id("allocation", inventory_id, project_id, prefix="ALLOC"), "inventoryId": inventory_id, "projectId": project_id, "quantity": round(quantity * 0.25, 3), "releaseAllowed": bool(index % 6 == 0), }) if index > 1 and index % 20 == 0: _, substitute_id = _material_identity(index - 1) substitutes.append({ "substituteId": stable_id("substitute", material_id, substitute_id, prefix="SUB"), "materialId": material_id, "substituteMaterialId": substitute_id, "conversionFactor": 1.0, "approved": True, }) if supplier_id and index % 4 == 0: receipts.append({ "plannedReceiptId": stable_id("receipt", material_id, index, prefix="PRC"), "materialId": material_id, "supplierId": supplier_id, "quantity": 30 + index % 300, "availableDate": (config.planning_base_date.replace(day=1)).isoformat(), "trusted": bool(index % 12 != 0), "status": "CONFIRMED" if index % 12 != 0 else "UNCONFIRMED", }) bundle.set_rows("materials", materials) bundle.set_rows("inventory", inventory) bundle.set_rows("inventory-allocations", allocations) bundle.set_rows("substitutes", substitutes) bundle.set_rows("planned-receipts", receipts) bundle.artifacts["materialClassification"] = dict( sorted(defaultdict(int, {mode: sum(1 for row in materials if row["sourcingMode"] == mode) for mode in {row["sourcingMode"] for row in materials}}).items()) ) return bundle