aps-agent/server/shipyard_synthetic/materials.py

126 lines
5.6 KiB
Python
Raw Normal View History

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