581 lines
22 KiB
Python
581 lines
22 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from datetime import timedelta
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from .config import DATASET_TYPE, GeneratorConfig
|
||
|
|
from .models import DatasetBundle, stable_id
|
||
|
|
from .registry import TABLE_SPECS
|
||
|
|
|
||
|
|
_MASTER_TABLES = frozenset(
|
||
|
|
{
|
||
|
|
"organizations",
|
||
|
|
"workshops",
|
||
|
|
"workcenters",
|
||
|
|
"resource-groups",
|
||
|
|
"resources",
|
||
|
|
"calendars",
|
||
|
|
"shifts",
|
||
|
|
"skills",
|
||
|
|
"teams",
|
||
|
|
"employees",
|
||
|
|
"suppliers",
|
||
|
|
"warehouses",
|
||
|
|
"locations",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
_WORKSHOPS = (
|
||
|
|
("HULL", "船体结构车间"),
|
||
|
|
("STEEL", "钢材预处理车间"),
|
||
|
|
("BLOCK", "分段制造车间"),
|
||
|
|
("OUTFIT", "舾装车间"),
|
||
|
|
("PIPE", "管系车间"),
|
||
|
|
("ELECTRICAL", "电气车间"),
|
||
|
|
("PAINT", "涂装车间"),
|
||
|
|
("DOCK", "船坞搭载车间"),
|
||
|
|
("QUAY", "码头调试车间"),
|
||
|
|
("LOGISTICS", "物流集配车间"),
|
||
|
|
("QUALITY", "质量检验中心"),
|
||
|
|
("COMMISSION", "系统调试中心"),
|
||
|
|
)
|
||
|
|
|
||
|
|
_RESOURCE_TYPES = (
|
||
|
|
("BUILDING", "厂房", "AREA", "m2", "HULL"),
|
||
|
|
("WORKSHOP", "车间", "AREA", "m2", "BLOCK"),
|
||
|
|
("WORKSTATION", "工位", "COUNT", "station", "OUTFIT"),
|
||
|
|
("JIG", "胎架", "COUNT", "set", "BLOCK"),
|
||
|
|
("PLATFORM", "装配平台", "AREA", "m2", "BLOCK"),
|
||
|
|
("BLOCK_YARD", "分段堆场", "AREA", "m2", "LOGISTICS"),
|
||
|
|
("DOCK", "船坞", "COUNT", "vessel", "DOCK"),
|
||
|
|
("SLIPWAY", "船台", "COUNT", "vessel", "DOCK"),
|
||
|
|
("BERTH", "码头泊位", "LENGTH", "m", "QUAY"),
|
||
|
|
("GANTRY_CRANE", "龙门吊", "WEIGHT", "t", "DOCK"),
|
||
|
|
("PORTAL_CRANE", "门座吊", "WEIGHT", "t", "QUAY"),
|
||
|
|
("MOBILE_CRANE", "汽车吊", "WEIGHT", "t", "LOGISTICS"),
|
||
|
|
("TRANSPORTER", "平板运输车", "WEIGHT", "t", "LOGISTICS"),
|
||
|
|
("CNC_CUTTER", "数控切割机", "HOURS", "h/day", "STEEL"),
|
||
|
|
("PLATE_BENDER", "弯板机", "HOURS", "h/day", "STEEL"),
|
||
|
|
("PIPE_BENDER", "弯管机", "HOURS", "h/day", "PIPE"),
|
||
|
|
("WELDER", "焊机", "HOURS", "h/day", "HULL"),
|
||
|
|
("SHOT_BLASTER", "抛丸设备", "AREA", "m2/day", "PAINT"),
|
||
|
|
("PAINT_BOOTH", "涂装房", "AREA", "m2/day", "PAINT"),
|
||
|
|
("NDT_EQUIPMENT", "探伤设备", "HOURS", "h/day", "QUALITY"),
|
||
|
|
("PRESSURE_TEST", "压力试验设备", "HOURS", "h/day", "QUALITY"),
|
||
|
|
("COMMISSION_EQUIPMENT", "调试设备", "HOURS", "h/day", "COMMISSION"),
|
||
|
|
("SHORE_POWER", "岸电设施", "POWER", "kW", "QUAY"),
|
||
|
|
)
|
||
|
|
|
||
|
|
_SKILL_NAMES = (
|
||
|
|
"船体装配",
|
||
|
|
"手工焊",
|
||
|
|
"自动焊",
|
||
|
|
"特种钢焊接",
|
||
|
|
"管系安装",
|
||
|
|
"管系焊接",
|
||
|
|
"电气安装",
|
||
|
|
"电气调试",
|
||
|
|
"涂装",
|
||
|
|
"起重指挥",
|
||
|
|
"起重机操作",
|
||
|
|
"平板车操作",
|
||
|
|
"无损检测",
|
||
|
|
"精度测量",
|
||
|
|
"密性试验",
|
||
|
|
"压力试验",
|
||
|
|
"主机调试",
|
||
|
|
"自动化调试",
|
||
|
|
"船舶试航",
|
||
|
|
)
|
||
|
|
|
||
|
|
_SUPPLIER_TYPES = (
|
||
|
|
"STEEL",
|
||
|
|
"MAIN_ENGINE",
|
||
|
|
"GENERATOR",
|
||
|
|
"PUMP_VALVE",
|
||
|
|
"ELECTRICAL",
|
||
|
|
"CABLE",
|
||
|
|
"NAVIGATION",
|
||
|
|
"DECK_MACHINERY",
|
||
|
|
"FIRE_SAFETY",
|
||
|
|
"LIFE_SAVING",
|
||
|
|
"PAINT",
|
||
|
|
"INSULATION",
|
||
|
|
"CAST_FORGE",
|
||
|
|
"OUTSOURCE_PIPE",
|
||
|
|
"OUTSOURCE_GALVANIZING",
|
||
|
|
"OUTSOURCE_MACHINING",
|
||
|
|
"OUTSOURCE_SURFACE",
|
||
|
|
)
|
||
|
|
|
||
|
|
_WAREHOUSE_TYPES = (
|
||
|
|
("STEEL_YARD", "钢材堆场"),
|
||
|
|
("PIPE", "管材仓库"),
|
||
|
|
("EQUIPMENT", "设备仓库"),
|
||
|
|
("ELECTRICAL", "电气仓库"),
|
||
|
|
("HAZMAT", "油漆危险品仓库"),
|
||
|
|
("CONSTANT_TEMP", "焊材恒温库"),
|
||
|
|
("OUTFITTING", "舾装件仓库"),
|
||
|
|
("CONSOLIDATION", "集配与委外收发仓库"),
|
||
|
|
)
|
||
|
|
|
||
|
|
_LOCATION_TYPES = (
|
||
|
|
("STEEL_YARD", "钢材堆场", 1),
|
||
|
|
("PIPE_STORAGE", "管材仓库", 2),
|
||
|
|
("EQUIPMENT_STORAGE", "设备仓库", 3),
|
||
|
|
("ELECTRICAL_STORAGE", "电气仓库", 4),
|
||
|
|
("PAINT_HAZMAT", "油漆危险品仓库", 5),
|
||
|
|
("WELDING_CONSUMABLE", "焊材恒温库", 6),
|
||
|
|
("OUTFITTING_STORAGE", "舾装件仓库", 7),
|
||
|
|
("SECTION_KITTING", "分段配套区", 8),
|
||
|
|
("PALLET_KITTING", "托盘集配区", 8),
|
||
|
|
("INSPECTION_PENDING", "待检区", 3),
|
||
|
|
("NONCONFORMING", "不合格品区", 3),
|
||
|
|
("OUTSOURCE_TRANSFER", "委外收发区", 8),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _set_owned_rows(
|
||
|
|
bundle: DatasetBundle,
|
||
|
|
table_name: str,
|
||
|
|
rows: list[dict[str, Any]],
|
||
|
|
*,
|
||
|
|
order_by: tuple[str, ...],
|
||
|
|
) -> None:
|
||
|
|
if table_name not in _MASTER_TABLES or table_name not in TABLE_SPECS:
|
||
|
|
raise ValueError(f"table is outside W69-MASTER ownership: {table_name}")
|
||
|
|
spec = TABLE_SPECS[table_name]
|
||
|
|
missing = [
|
||
|
|
(index, field)
|
||
|
|
for index, row in enumerate(rows)
|
||
|
|
for field in spec.required
|
||
|
|
if field not in row
|
||
|
|
]
|
||
|
|
if missing:
|
||
|
|
index, field = missing[0]
|
||
|
|
raise ValueError(f"{table_name}[{index}] missing required field {field}")
|
||
|
|
primary_keys = [row[spec.primary_key] for row in rows]
|
||
|
|
if len(primary_keys) != len(set(primary_keys)):
|
||
|
|
raise ValueError(f"duplicate primary key in {table_name}")
|
||
|
|
bundle.set_rows(
|
||
|
|
table_name,
|
||
|
|
sorted(rows, key=lambda row: tuple(str(row.get(key, "")) for key in order_by)),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _calendar_id(code: str) -> str:
|
||
|
|
return stable_id("calendar", code, prefix="CAL")
|
||
|
|
|
||
|
|
|
||
|
|
def _workshop_id(code: str) -> str:
|
||
|
|
return stable_id("workshop", code, prefix="WKS")
|
||
|
|
|
||
|
|
|
||
|
|
def _workcenter_id(code: str) -> str:
|
||
|
|
return stable_id("workcenter", code, prefix="WKC")
|
||
|
|
|
||
|
|
|
||
|
|
def _resource_group_id(resource_type: str) -> str:
|
||
|
|
return stable_id("resource-group", f"RG-SYN-{resource_type}", prefix="RGP")
|
||
|
|
|
||
|
|
|
||
|
|
def generate_master(bundle: DatasetBundle, config: GeneratorConfig) -> DatasetBundle:
|
||
|
|
"""Populate deterministic organization, resource, workforce and supplier tables."""
|
||
|
|
base_date = config.planning_base_date
|
||
|
|
horizon_end = config.planning_horizon_end
|
||
|
|
organization_code = "ORG-BH-SYN-001"
|
||
|
|
organization_id = stable_id("organization", organization_code, prefix="ORG")
|
||
|
|
organizations = [
|
||
|
|
{
|
||
|
|
"organizationId": organization_id,
|
||
|
|
"organizationCode": organization_code,
|
||
|
|
"name": "北海造船 APS 模拟组织-SYN",
|
||
|
|
"scenarioCode": "BEIHAI_SHIPYARD_APS",
|
||
|
|
"datasetType": DATASET_TYPE,
|
||
|
|
"timezone": config.timezone,
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
]
|
||
|
|
|
||
|
|
workshops: list[dict[str, Any]] = []
|
||
|
|
workcenters: list[dict[str, Any]] = []
|
||
|
|
center_ids_by_workshop: dict[str, list[str]] = {}
|
||
|
|
for workshop_index, (workshop_key, workshop_name) in enumerate(_WORKSHOPS, start=1):
|
||
|
|
workshop_code = f"WS-SYN-{workshop_index:02d}"
|
||
|
|
workshop_id = _workshop_id(workshop_code)
|
||
|
|
workshops.append(
|
||
|
|
{
|
||
|
|
"workshopId": workshop_id,
|
||
|
|
"workshopCode": workshop_code,
|
||
|
|
"organizationId": organization_id,
|
||
|
|
"name": f"{workshop_name}-SYN",
|
||
|
|
"workshopType": workshop_key,
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
center_ids_by_workshop[workshop_key] = []
|
||
|
|
for center_number in range(1, 3):
|
||
|
|
center_code = f"WC-SYN-{workshop_index:02d}-{center_number:02d}"
|
||
|
|
center_id = _workcenter_id(center_code)
|
||
|
|
center_ids_by_workshop[workshop_key].append(center_id)
|
||
|
|
workcenters.append(
|
||
|
|
{
|
||
|
|
"workcenterId": center_id,
|
||
|
|
"workcenterCode": center_code,
|
||
|
|
"workshopId": workshop_id,
|
||
|
|
"name": f"{workshop_name}{center_number}号工作中心-SYN",
|
||
|
|
"capacityMode": "FINITE",
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
calendar_defs = (
|
||
|
|
("CAL-SYN-DAY", "常日班日历-SYN", "SHIFT", [1, 2, 3, 4, 5]),
|
||
|
|
("CAL-SYN-TWO", "两班制日历-SYN", "SHIFT", [1, 2, 3, 4, 5, 6]),
|
||
|
|
("CAL-SYN-THREE", "三班制日历-SYN", "SHIFT", [1, 2, 3, 4, 5, 6]),
|
||
|
|
("CAL-SYN-OT", "周末加班日历-SYN", "OVERTIME", [6, 7]),
|
||
|
|
("CAL-SYN-HOLIDAY", "法定节假日历-SYN", "BLACKOUT", []),
|
||
|
|
("CAL-SYN-MAINT", "设备维护日历-SYN", "MAINTENANCE", []),
|
||
|
|
("CAL-SYN-WEATHER", "极端天气停工日历-SYN", "WEATHER", []),
|
||
|
|
("CAL-SYN-SUPPLIER", "供应商产能日历-SYN", "SUPPLIER", [1, 2, 3, 4, 5]),
|
||
|
|
)
|
||
|
|
calendars = [
|
||
|
|
{
|
||
|
|
"calendarId": _calendar_id(code),
|
||
|
|
"calendarCode": code,
|
||
|
|
"name": name,
|
||
|
|
"calendarType": calendar_type,
|
||
|
|
"timezone": config.timezone,
|
||
|
|
"effectiveFrom": base_date.isoformat(),
|
||
|
|
"effectiveTo": horizon_end.isoformat(),
|
||
|
|
"workingDays": working_days,
|
||
|
|
"blackoutDates": (
|
||
|
|
[
|
||
|
|
(base_date + timedelta(days=45)).isoformat(),
|
||
|
|
(base_date + timedelta(days=225)).isoformat(),
|
||
|
|
]
|
||
|
|
if calendar_type == "MAINTENANCE"
|
||
|
|
else []
|
||
|
|
),
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
for code, name, calendar_type, working_days in calendar_defs
|
||
|
|
]
|
||
|
|
|
||
|
|
shifts: list[dict[str, Any]] = []
|
||
|
|
shift_defs = (
|
||
|
|
("CAL-SYN-DAY", (("DAY", "08:00", "17:00"),)),
|
||
|
|
("CAL-SYN-TWO", (("A", "06:00", "14:00"), ("B", "14:00", "22:00"))),
|
||
|
|
(
|
||
|
|
"CAL-SYN-THREE",
|
||
|
|
(
|
||
|
|
("A", "06:00", "14:00"),
|
||
|
|
("B", "14:00", "22:00"),
|
||
|
|
("C", "22:00", "06:00"),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
("CAL-SYN-OT", (("OT", "08:00", "16:00"),)),
|
||
|
|
)
|
||
|
|
for calendar_code, definitions in shift_defs:
|
||
|
|
for shift_code, start_time, end_time in definitions:
|
||
|
|
code = f"SHIFT-SYN-{calendar_code.removeprefix('CAL-SYN-')}-{shift_code}"
|
||
|
|
shifts.append(
|
||
|
|
{
|
||
|
|
"shiftId": stable_id("shift", code, prefix="SFT"),
|
||
|
|
"shiftCode": code,
|
||
|
|
"calendarId": _calendar_id(calendar_code),
|
||
|
|
"name": f"{shift_code}班-SYN",
|
||
|
|
"startTime": start_time,
|
||
|
|
"endTime": end_time,
|
||
|
|
"breakMinutes": 60 if shift_code == "DAY" else 30,
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
resource_groups = [
|
||
|
|
{
|
||
|
|
"resourceGroupId": _resource_group_id(resource_type),
|
||
|
|
"resourceGroupCode": f"RG-SYN-{resource_type}",
|
||
|
|
"name": f"{resource_name}资源组-SYN",
|
||
|
|
"resourceType": resource_type,
|
||
|
|
"capacityType": capacity_type,
|
||
|
|
"unit": unit,
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
for resource_type, resource_name, capacity_type, unit, _ in _RESOURCE_TYPES
|
||
|
|
]
|
||
|
|
|
||
|
|
resources: list[dict[str, Any]] = []
|
||
|
|
lift_types = {"GANTRY_CRANE", "PORTAL_CRANE", "MOBILE_CRANE", "TRANSPORTER"}
|
||
|
|
for index in range(1, config.profile.equipment_resource_count + 1):
|
||
|
|
type_index = (index - 1) % len(_RESOURCE_TYPES)
|
||
|
|
resource_type, resource_name, capacity_type, unit, workshop_key = (
|
||
|
|
_RESOURCE_TYPES[type_index]
|
||
|
|
)
|
||
|
|
center_ids = center_ids_by_workshop[workshop_key]
|
||
|
|
workcenter_id = center_ids[
|
||
|
|
((index - 1) // len(_RESOURCE_TYPES)) % len(center_ids)
|
||
|
|
]
|
||
|
|
resource_code = f"RES-SYN-{index:03d}"
|
||
|
|
exclusive = capacity_type in {"COUNT", "WEIGHT", "LENGTH", "POWER"}
|
||
|
|
base_capacity = 1.0 if exclusive else float(8 + index % 3)
|
||
|
|
if resource_type == "BERTH":
|
||
|
|
base_capacity = float(260 + (index % 4) * 40)
|
||
|
|
elif resource_type in {"GANTRY_CRANE", "PORTAL_CRANE", "MOBILE_CRANE"}:
|
||
|
|
base_capacity = float(80 + (index % 8) * 70)
|
||
|
|
elif resource_type == "TRANSPORTER":
|
||
|
|
base_capacity = float(120 + (index % 6) * 80)
|
||
|
|
elif resource_type == "SHORE_POWER":
|
||
|
|
base_capacity = float(2500 + (index % 5) * 750)
|
||
|
|
resources.append(
|
||
|
|
{
|
||
|
|
"resourceId": stable_id("resource", resource_code, prefix="RES"),
|
||
|
|
"resourceCode": resource_code,
|
||
|
|
"resourceName": f"{resource_name}-SYN-{index:03d}",
|
||
|
|
"name": f"{resource_name}-SYN-{index:03d}",
|
||
|
|
"resourceType": resource_type,
|
||
|
|
"workcenterId": workcenter_id,
|
||
|
|
"resourceGroupId": _resource_group_id(resource_type),
|
||
|
|
"resourceGroup": f"RG-SYN-{resource_type}",
|
||
|
|
"capacityType": capacity_type,
|
||
|
|
"capacity": base_capacity,
|
||
|
|
"dailyCapacity": base_capacity,
|
||
|
|
"unit": unit,
|
||
|
|
"exclusive": exclusive,
|
||
|
|
"shiftCalendarId": _calendar_id(
|
||
|
|
"CAL-SYN-THREE" if type_index % 3 == 0 else "CAL-SYN-TWO"
|
||
|
|
),
|
||
|
|
"maintenanceCalendarId": _calendar_id("CAL-SYN-MAINT"),
|
||
|
|
"maintenanceBlackoutDates": [
|
||
|
|
(base_date + timedelta(days=45)).isoformat(),
|
||
|
|
(base_date + timedelta(days=225)).isoformat(),
|
||
|
|
],
|
||
|
|
"efficiency": round(0.82 + (index % 12) * 0.01, 2),
|
||
|
|
"utilizationUpperLimit": 0.9,
|
||
|
|
"minimumLoad": 0.0,
|
||
|
|
"maximumLoad": base_capacity,
|
||
|
|
"capabilityTags": [resource_type, "FINITE_CAPACITY", "SYNTHETIC"],
|
||
|
|
"maximumWeight": base_capacity if resource_type in lift_types else None,
|
||
|
|
"maximumLength": 360.0 if resource_type in {"DOCK", "BERTH"} else 45.0,
|
||
|
|
"maximumWidth": 72.0 if resource_type == "DOCK" else 28.0,
|
||
|
|
"maximumHeight": 75.0 if resource_type == "GANTRY_CRANE" else 18.0,
|
||
|
|
"location": f"场区-SYN-{(index - 1) % 12 + 1:02d}",
|
||
|
|
"transportZone": f"TZ-SYN-{(index - 1) % 6 + 1:02d}",
|
||
|
|
"compatibleOperationCodes": [resource_type],
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
skills = [
|
||
|
|
{
|
||
|
|
"skillCode": f"SK-SYN-{index:03d}",
|
||
|
|
"name": f"{name}-SYN",
|
||
|
|
"qualificationRequired": index in {4, 10, 11, 12, 13, 15, 16, 19},
|
||
|
|
"qualificationType": "SPECIAL_OPERATION"
|
||
|
|
if index in {4, 10, 11, 12, 13, 15, 16, 19}
|
||
|
|
else "INTERNAL",
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
for index, name in enumerate(_SKILL_NAMES, start=1)
|
||
|
|
]
|
||
|
|
|
||
|
|
teams: list[dict[str, Any]] = []
|
||
|
|
employees: list[dict[str, Any]] = []
|
||
|
|
employee_index = 0
|
||
|
|
for index in range(1, config.profile.team_count + 1):
|
||
|
|
team_code = f"TEAM-SYN-{index:03d}"
|
||
|
|
team_id = stable_id("team", team_code, prefix="TEAM")
|
||
|
|
skill_index = (index - 1) % len(_SKILL_NAMES) + 1
|
||
|
|
skill_codes = [
|
||
|
|
f"SK-SYN-{skill_index:03d}",
|
||
|
|
f"SK-SYN-{skill_index % len(_SKILL_NAMES) + 1:03d}",
|
||
|
|
]
|
||
|
|
crew_size = 6 + (index % 7)
|
||
|
|
workshop_position = (skill_index - 1) % len(_WORKSHOPS) + 1
|
||
|
|
shift_calendar_id = _calendar_id(
|
||
|
|
"CAL-SYN-TWO" if index % 3 else "CAL-SYN-THREE"
|
||
|
|
)
|
||
|
|
teams.append(
|
||
|
|
{
|
||
|
|
"teamId": team_id,
|
||
|
|
"teamCode": team_code,
|
||
|
|
"name": f"{_SKILL_NAMES[skill_index - 1]}班组-SYN-{index:03d}",
|
||
|
|
"workshopId": _workshop_id(f"WS-SYN-{workshop_position:02d}"),
|
||
|
|
"crewSize": crew_size,
|
||
|
|
"skillCodes": skill_codes,
|
||
|
|
"shiftCalendarId": shift_calendar_id,
|
||
|
|
"crossProjectSupport": list(config.project_codes),
|
||
|
|
"overtimeLimitHoursPerWeek": 12,
|
||
|
|
"continuousWorkLimitHours": 10,
|
||
|
|
"learningCurve": round(0.92 + (index % 5) * 0.015, 3),
|
||
|
|
"sisterShipEfficiencyFactor": 1.08 if index % 4 == 0 else 1.03,
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
for crew_position in range(1, crew_size + 1):
|
||
|
|
employee_index += 1
|
||
|
|
employee_code = f"EMP-SYN-{employee_index:04d}"
|
||
|
|
qualification_required = any(
|
||
|
|
int(code.rsplit("-", 1)[-1]) in {4, 10, 11, 12, 13, 15, 16, 19}
|
||
|
|
for code in skill_codes
|
||
|
|
)
|
||
|
|
employees.append(
|
||
|
|
{
|
||
|
|
"employeeId": stable_id("employee", employee_code, prefix="EMP"),
|
||
|
|
"employeeCode": employee_code,
|
||
|
|
"teamId": team_id,
|
||
|
|
"name": f"{_SKILL_NAMES[skill_index - 1]}工-模拟{employee_index:04d}",
|
||
|
|
"skillCodes": skill_codes,
|
||
|
|
"qualificationCode": f"CERT-SYN-{skill_index:03d}-{employee_index:04d}"
|
||
|
|
if qualification_required
|
||
|
|
else None,
|
||
|
|
"qualificationValidTo": (
|
||
|
|
base_date + timedelta(days=730 + crew_position)
|
||
|
|
).isoformat(),
|
||
|
|
"availableShiftCalendarId": shift_calendar_id,
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
suppliers: list[dict[str, Any]] = []
|
||
|
|
outsource_supplier_types = tuple(
|
||
|
|
supplier_type
|
||
|
|
for supplier_type in _SUPPLIER_TYPES
|
||
|
|
if supplier_type.startswith("OUTSOURCE_")
|
||
|
|
)
|
||
|
|
for index in range(1, config.profile.supplier_count + 1):
|
||
|
|
supplier_code = f"SUP-SYN-{index:03d}"
|
||
|
|
if config.profile.supplier_count < 14 and index <= len(
|
||
|
|
outsource_supplier_types
|
||
|
|
):
|
||
|
|
supplier_type = outsource_supplier_types[index - 1]
|
||
|
|
else:
|
||
|
|
supplier_type = _SUPPLIER_TYPES[(index - 1) % len(_SUPPLIER_TYPES)]
|
||
|
|
is_outsource_supplier = supplier_type.startswith("OUTSOURCE_")
|
||
|
|
capability_codes = (
|
||
|
|
[supplier_type.removeprefix("OUTSOURCE_")] if is_outsource_supplier else []
|
||
|
|
)
|
||
|
|
transport_outbound_days = 1 if is_outsource_supplier else 1 + index % 4
|
||
|
|
transport_inbound_days = 1 if is_outsource_supplier else 1 + (index + 1) % 4
|
||
|
|
blackout_dates = sorted(
|
||
|
|
{
|
||
|
|
(base_date + timedelta(days=31 + index)).isoformat(),
|
||
|
|
(base_date + timedelta(days=181 + index * 2)).isoformat(),
|
||
|
|
}
|
||
|
|
)
|
||
|
|
suppliers.append(
|
||
|
|
{
|
||
|
|
"supplierId": stable_id("supplier", supplier_code, prefix="SUP"),
|
||
|
|
"supplierCode": supplier_code,
|
||
|
|
"name": f"供应商-SYN-{index:03d}",
|
||
|
|
"supplierName": f"供应商-SYN-{index:03d}",
|
||
|
|
"supplierType": supplier_type,
|
||
|
|
"suppliedMaterialGroups": [supplier_type],
|
||
|
|
"outsourceCapabilityCodes": capability_codes,
|
||
|
|
"outsourceOperationCodes": list(capability_codes),
|
||
|
|
"standardLeadTimeDays": 12 + (index * 7) % 90,
|
||
|
|
"transportLeadTimeDays": transport_outbound_days
|
||
|
|
+ transport_inbound_days,
|
||
|
|
"transportOutboundDays": transport_outbound_days,
|
||
|
|
"transportInboundDays": transport_inbound_days,
|
||
|
|
"inspectionLeadTimeDays": (
|
||
|
|
1 if is_outsource_supplier else 1 + index % 5
|
||
|
|
),
|
||
|
|
"monthlyCapacity": (
|
||
|
|
600.0 + (index % 10) * 175.0
|
||
|
|
if is_outsource_supplier
|
||
|
|
else 5_000_000.0 + (index % 10) * 250_000.0
|
||
|
|
),
|
||
|
|
"monthlyCapacityUnit": (
|
||
|
|
"OPERATION" if is_outsource_supplier else "PCS"
|
||
|
|
),
|
||
|
|
"blackoutDates": blackout_dates,
|
||
|
|
"onTimeDeliveryRate": round(0.82 + (index % 15) * 0.01, 2),
|
||
|
|
"qualityPassRate": round(0.9 + (index % 9) * 0.01, 2),
|
||
|
|
"riskLevel": "HIGH"
|
||
|
|
if index % 13 == 0
|
||
|
|
else "MEDIUM"
|
||
|
|
if index % 5 == 0
|
||
|
|
else "LOW",
|
||
|
|
"region": f"模拟区域-SYN-{(index - 1) % 8 + 1:02d}",
|
||
|
|
"approved": True if is_outsource_supplier else index % 13 != 0,
|
||
|
|
"blackoutCalendar": _calendar_id("CAL-SYN-HOLIDAY"),
|
||
|
|
"minimumOrderValue": float(10000 + (index % 6) * 5000),
|
||
|
|
"capacityCalendar": _calendar_id("CAL-SYN-SUPPLIER"),
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
warehouses: list[dict[str, Any]] = []
|
||
|
|
warehouse_ids: dict[int, str] = {}
|
||
|
|
for index, (warehouse_type, warehouse_name) in enumerate(_WAREHOUSE_TYPES, start=1):
|
||
|
|
warehouse_code = f"WH-SYN-{index:03d}"
|
||
|
|
warehouse_id = stable_id("warehouse", warehouse_code, prefix="WH")
|
||
|
|
warehouse_ids[index] = warehouse_id
|
||
|
|
warehouses.append(
|
||
|
|
{
|
||
|
|
"warehouseId": warehouse_id,
|
||
|
|
"warehouseCode": warehouse_code,
|
||
|
|
"name": f"{warehouse_name}-SYN",
|
||
|
|
"warehouseType": warehouse_type,
|
||
|
|
"organizationId": organization_id,
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
locations = []
|
||
|
|
for index, (location_type, location_name, warehouse_index) in enumerate(
|
||
|
|
_LOCATION_TYPES, start=1
|
||
|
|
):
|
||
|
|
location_code = f"LOC-SYN-{index:03d}"
|
||
|
|
quality_statuses = ["AVAILABLE", "ALLOCATED", "RESERVED"]
|
||
|
|
if location_type == "INSPECTION_PENDING":
|
||
|
|
quality_statuses = ["QUALITY_HOLD"]
|
||
|
|
elif location_type == "NONCONFORMING":
|
||
|
|
quality_statuses = ["BLOCKED"]
|
||
|
|
locations.append(
|
||
|
|
{
|
||
|
|
"locationId": stable_id("location", location_code, prefix="LOC"),
|
||
|
|
"locationCode": location_code,
|
||
|
|
"warehouseId": warehouse_ids[warehouse_index],
|
||
|
|
"locationType": location_type,
|
||
|
|
"name": f"{location_name}-SYN",
|
||
|
|
"qualityStatusAllowed": quality_statuses,
|
||
|
|
"status": "ACTIVE",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
table_rows = {
|
||
|
|
"organizations": organizations,
|
||
|
|
"workshops": workshops,
|
||
|
|
"workcenters": workcenters,
|
||
|
|
"resource-groups": resource_groups,
|
||
|
|
"resources": resources,
|
||
|
|
"calendars": calendars,
|
||
|
|
"shifts": shifts,
|
||
|
|
"skills": skills,
|
||
|
|
"teams": teams,
|
||
|
|
"employees": employees,
|
||
|
|
"suppliers": suppliers,
|
||
|
|
"warehouses": warehouses,
|
||
|
|
"locations": locations,
|
||
|
|
}
|
||
|
|
order_fields = {
|
||
|
|
"organizations": ("organizationCode",),
|
||
|
|
"workshops": ("workshopCode",),
|
||
|
|
"workcenters": ("workcenterCode",),
|
||
|
|
"resource-groups": ("resourceGroupCode",),
|
||
|
|
"resources": ("resourceCode",),
|
||
|
|
"calendars": ("calendarCode",),
|
||
|
|
"shifts": ("shiftCode",),
|
||
|
|
"skills": ("skillCode",),
|
||
|
|
"teams": ("teamCode",),
|
||
|
|
"employees": ("employeeCode",),
|
||
|
|
"suppliers": ("supplierCode",),
|
||
|
|
"warehouses": ("warehouseCode",),
|
||
|
|
"locations": ("locationCode",),
|
||
|
|
}
|
||
|
|
for table_name, rows in table_rows.items():
|
||
|
|
_set_owned_rows(bundle, table_name, rows, order_by=order_fields[table_name])
|
||
|
|
return bundle
|