126 lines
5.6 KiB
Python
126 lines
5.6 KiB
Python
|
|
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
|