wavesimpro 1.10.0__tar.gz → 1.10.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/PKG-INFO +3 -2
  2. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/README.md +2 -1
  3. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/pyproject.toml +1 -1
  4. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/__init__.py +4 -2
  5. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/client.py +121 -172
  6. wavesimpro-1.10.2/src/wavesimpro/model.py +588 -0
  7. wavesimpro-1.10.2/src/wavesimpro/runs.py +955 -0
  8. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro.egg-info/PKG-INFO +3 -2
  9. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro.egg-info/SOURCES.txt +4 -1
  10. wavesimpro-1.10.2/tests/test_command_wait.py +64 -0
  11. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/setup.cfg +0 -0
  12. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/__main__.py +0 -0
  13. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/binary_utils.py +0 -0
  14. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/config.py +0 -0
  15. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/exceptions.py +0 -0
  16. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/execute.py +0 -0
  17. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/json_helper.py +0 -0
  18. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/py.typed +0 -0
  19. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/setup.py +0 -0
  20. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/simulate.py +0 -0
  21. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro/validate.py +0 -0
  22. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro.egg-info/dependency_links.txt +0 -0
  23. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro.egg-info/requires.txt +0 -0
  24. {wavesimpro-1.10.0 → wavesimpro-1.10.2}/src/wavesimpro.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavesimpro
3
- Version: 1.10.0
3
+ Version: 1.10.2
4
4
  Summary: WavesimPro Python API — cloud execution client for the Rayfos simulation platform
5
5
  Author: Rayfos
6
6
  Project-URL: Repository, https://github.com/rayfos/wavesimpro
@@ -67,7 +67,8 @@ field = wp.read_binary_field(run.download("output.bin"))
67
67
  `create_stl_primitive`, `create_gds_primitive` (with `create_gds_layer_config` /
68
68
  `create_gds_structure_override`), `create_voxels_model_primitive`.
69
69
  - Per-primitive sub-pixel flag (`subpixel=True/False` on every shape, STL and GDSII helper)
70
- to choose which shapes get smoothed interfaces.
70
+ to choose which shapes get smoothed interfaces. Off by default: a setup with no averaged
71
+ shape is voxelised exactly as before 1.10 and runs on every compute node.
71
72
  - Per-axis pixel sizes (`pixel_size=[dx, dy, dz]`) and sub-pixel permittivity averaging
72
73
  (`subpixel_factor=`), when the compute node supports them.
73
74
  - Preflight memory check and cost estimate before a run.
@@ -44,7 +44,8 @@ field = wp.read_binary_field(run.download("output.bin"))
44
44
  `create_stl_primitive`, `create_gds_primitive` (with `create_gds_layer_config` /
45
45
  `create_gds_structure_override`), `create_voxels_model_primitive`.
46
46
  - Per-primitive sub-pixel flag (`subpixel=True/False` on every shape, STL and GDSII helper)
47
- to choose which shapes get smoothed interfaces.
47
+ to choose which shapes get smoothed interfaces. Off by default: a setup with no averaged
48
+ shape is voxelised exactly as before 1.10 and runs on every compute node.
48
49
  - Per-axis pixel sizes (`pixel_size=[dx, dy, dz]`) and sub-pixel permittivity averaging
49
50
  (`subpixel_factor=`), when the compute node supports them.
50
51
  - Preflight memory check and cost estimate before a run.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wavesimpro"
7
- version = "1.10.0"
7
+ version = "1.10.2"
8
8
  description = "WavesimPro Python API — cloud execution client for the Rayfos simulation platform"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -6,7 +6,7 @@ supporting both cloud and local deployment modes, as well as direct interaction
6
6
  with RayfosMachineService via its REST API.
7
7
  """
8
8
 
9
- from .client import RayfosClient
9
+ from .client import RayfosClient, command_status_name, is_terminal_command_status
10
10
  from .config import ClientConfig
11
11
  from .exceptions import (
12
12
  RayfosAPIError,
@@ -54,10 +54,12 @@ from .json_helper import (
54
54
  parse_progress_data,
55
55
  )
56
56
 
57
- __version__ = "1.10.0"
57
+ __version__ = "1.10.2"
58
58
  __all__ = [
59
59
  # Client
60
60
  "RayfosClient",
61
+ "command_status_name",
62
+ "is_terminal_command_status",
61
63
  "ClientConfig",
62
64
  # Exceptions
63
65
  "RayfosAPIError",
@@ -77,6 +77,49 @@ def _make_progress_callback(total_bytes: int, desc: str):
77
77
  logger = logging.getLogger(__name__)
78
78
 
79
79
 
80
+
81
+ # CommandStatus enum (C# RayfosCommandModels) by value; the server sends the NAME in "status"
82
+ # (JsonStringEnumConverter). Older servers sent the integer in "commandStatus".
83
+ _COMMAND_STATUS_BY_INT = {
84
+ 0: 'New', 1: 'Assigned', 2: 'SentToMachine', 3: 'SendToMachineForceCancellation',
85
+ 4: 'SendToMachineForceDeleteTask', 5: 'RequestedForceCancellationInMachine',
86
+ 6: 'RequestedForceStopTaskInMachine', 7: 'Cancelled', 8: 'Deleted', 9: 'Running',
87
+ 10: 'Completed', 11: 'Failed', 12: 'FailedRemovedFromMachine',
88
+ }
89
+ _TERMINAL_STATUSES = {'Cancelled', 'Deleted', 'Completed', 'Failed', 'FailedRemovedFromMachine'}
90
+
91
+
92
+ def command_status_name(status: Dict[str, Any]) -> str:
93
+ """The command state name from a command-status dict, whatever the server version.
94
+
95
+ Reads ``status`` (current servers: the enum name, e.g. ``"Completed"``), falling back to the
96
+ legacy ``commandStatus`` / ``CommandStatus`` (integer or name). Returns ``"Unknown"`` when absent.
97
+ """
98
+ if not isinstance(status, dict):
99
+ return 'Unknown'
100
+ for key in ('status', 'Status', 'commandStatus', 'CommandStatus'):
101
+ raw = status.get(key)
102
+ if raw is None or raw == '':
103
+ continue
104
+ if isinstance(raw, bool):
105
+ continue
106
+ if isinstance(raw, int):
107
+ return _COMMAND_STATUS_BY_INT.get(raw, f'Unknown({raw})')
108
+ text = str(raw).strip()
109
+ if text.isdigit():
110
+ return _COMMAND_STATUS_BY_INT.get(int(text), f'Unknown({text})')
111
+ for name in _COMMAND_STATUS_BY_INT.values():
112
+ if name.lower() == text.lower():
113
+ return name
114
+ return text
115
+ return 'Unknown'
116
+
117
+
118
+ def is_terminal_command_status(name: str) -> bool:
119
+ """True for Completed, Failed, FailedRemovedFromMachine, Cancelled and Deleted."""
120
+ return name in _TERMINAL_STATUSES
121
+
122
+
80
123
  class RayfosClient:
81
124
  """
82
125
  Client for interacting with RayfosJobServer API
@@ -740,9 +783,9 @@ class RayfosClient:
740
783
 
741
784
  Example:
742
785
  >>> version = client.get_project_version(project_id, version_id)
743
- >>> if version.get('command') and version['command'].get('outputFiles'):
744
- ... for file in version['command']['outputFiles']:
745
- ... print(f"Output file: {file['originalFileName']}")
786
+ >>> for file_id in version.get('outputFileIds', []):
787
+ ... info = client.get_file_info(project_id, version_id, file_id)
788
+ ... print(f"Output file: {info.get('fileName')} ({info.get('fileSize')} bytes)")
746
789
  """
747
790
  return self._get(f"/api/projects/{project_id}/versions/{version_id}")
748
791
 
@@ -1287,7 +1330,7 @@ class RayfosClient:
1287
1330
  Returns:
1288
1331
  File metadata dictionary
1289
1332
  """
1290
- return self._get(f"/api/projects/{project_id}/versions/{version_id}/files/{file_id}")
1333
+ return self._get(f"/api/projects/{project_id}/versions/{version_id}/files/{file_id}/metadata")
1291
1334
 
1292
1335
  # ========================================================================
1293
1336
  # COMMAND OPERATIONS
@@ -1382,7 +1425,8 @@ class RayfosClient:
1382
1425
  Ignored when using a local machine service URL.
1383
1426
 
1384
1427
  Returns:
1385
- Command status dictionary with the created command ID and status
1428
+ Command status dictionary. The command id is under ``"id"`` and, for convenience,
1429
+ ``"commandId"``; the state is under ``"status"`` (read it with ``command_status_name``).
1386
1430
 
1387
1431
  Example:
1388
1432
  >>> command = client.create_command(project_id, version_id,
@@ -1390,9 +1434,17 @@ class RayfosClient:
1390
1434
  >>> print(f"Command created: {command['id']}")
1391
1435
  """
1392
1436
  if self.config.machine_service_url:
1393
- return self._create_command_machine_service(project_id, version_id)
1437
+ result = self._create_command_machine_service(project_id, version_id)
1394
1438
  else:
1395
- return self._create_command_cloud(project_id, version_id, engine_preference, use_gpu, preferred_machine_id)
1439
+ result = self._create_command_cloud(project_id, version_id, engine_preference, use_gpu, preferred_machine_id)
1440
+ # The server names the command id "id"; other API calls and version records use "commandId".
1441
+ # Return both so either spelling works.
1442
+ if isinstance(result, dict):
1443
+ cid = result.get('id') or result.get('commandId') or result.get('Id')
1444
+ if cid:
1445
+ result.setdefault('id', cid)
1446
+ result.setdefault('commandId', cid)
1447
+ return result
1396
1448
 
1397
1449
  def _create_command_machine_service(
1398
1450
  self,
@@ -1439,35 +1491,8 @@ class RayfosClient:
1439
1491
 
1440
1492
  @staticmethod
1441
1493
  def _parse_command_status(status_raw) -> str:
1442
- """
1443
- Parse commandStatus from C# enum (integer) to string name
1444
-
1445
- Args:
1446
- status_raw: Either integer enum value or string name
1447
-
1448
- Returns:
1449
- String name of the status
1450
- """
1451
- # CommandStatus enum from C# RayfosCommandModels
1452
- COMMAND_STATUS_MAP = {
1453
- 0: 'New',
1454
- 1: 'Assigned',
1455
- 2: 'SentToMachine',
1456
- 3: 'SendToMachineForceCancellation',
1457
- 4: 'SendToMachineForceDeleteTask',
1458
- 5: 'RequestedForceCancellationInMachine',
1459
- 6: 'RequestedForceStopTaskInMachine',
1460
- 7: 'Cancelled',
1461
- 8: 'Deleted',
1462
- 9: 'Running',
1463
- 10: 'Completed',
1464
- 11: 'Failed',
1465
- 12: 'FailedRemovedFromMachine'
1466
- }
1467
-
1468
- if isinstance(status_raw, int):
1469
- return COMMAND_STATUS_MAP.get(status_raw, f'Unknown({status_raw})')
1470
- return status_raw
1494
+ """Command state name from an integer (legacy ``commandStatus``) or a name (current ``status``)."""
1495
+ return command_status_name({"status": status_raw})
1471
1496
 
1472
1497
  @staticmethod
1473
1498
  def _parse_progress_data(progress_obj) -> Dict[str, Any]:
@@ -1542,16 +1567,16 @@ class RayfosClient:
1542
1567
  command_id: Command UUID
1543
1568
 
1544
1569
  Returns:
1545
- CommandStatusDto dictionary with status, progress, and output files
1546
- Note: commandStatus is returned as integer from C#, use _parse_command_status() to convert
1547
-
1570
+ Command-status dict: ``id``, ``status`` (enum name such as ``"Running"`` or
1571
+ ``"Completed"``), ``statusText``, ``progressValue``. Older servers sent
1572
+ the state as the integer ``commandStatus``; :func:`command_status_name` reads either.
1573
+
1548
1574
  Example:
1549
1575
  >>> status = client.get_command_status("command-uuid")
1550
- >>> status_name = client._parse_command_status(status['commandStatus'])
1551
- >>> print(f"Status: {status_name}")
1552
- >>> print(f"Progress: {status.get('progressValue', 0)}%")
1553
- >>> if status['commandStatus'] == 10: # Completed
1554
- ... print(f"Output files: {status.get('outputFileIds', [])}")
1576
+ >>> name = command_status_name(status) # "Running", "Completed", ...
1577
+ >>> print(f"{name}: {status.get('statusText')} ({status.get('progressValue', 0)}%)")
1578
+ >>> if name == "Completed":
1579
+ ... files = client.get_project_version(project_id, version_id)["outputFileIds"]
1555
1580
  """
1556
1581
  # Always call RayfosJobServer directly for command status (user endpoint)
1557
1582
  # Machine service is only for file operations, not command status
@@ -1561,113 +1586,73 @@ class RayfosClient:
1561
1586
  self,
1562
1587
  command_id: str,
1563
1588
  poll_interval: float = 2.0,
1564
- timeout: Optional[float] = None,
1589
+ timeout: Optional[float] = 24 * 3600,
1565
1590
  progress_callback: Optional[callable] = None
1566
1591
  ) -> Dict[str, Any]:
1567
1592
  """
1568
- Wait for a command to complete (synchronous execution)
1569
-
1570
- This method polls the command status until it reaches a terminal state
1571
- (Completed, Failed, Cancelled). Useful for blocking until simulation finishes.
1572
-
1593
+ Wait for a command to finish (synchronous execution).
1594
+
1595
+ Polls :meth:`get_command_status` until the command reaches a terminal state and returns the
1596
+ final status dict on ``Completed``. The state is read with :func:`command_status_name`, which
1597
+ understands both the current ``status`` field (enum name) and the legacy ``commandStatus``
1598
+ integer, so this works against every server version.
1599
+
1573
1600
  Args:
1574
- command_id: Command UUID to wait for
1575
- poll_interval: Seconds between status checks (default: 2.0)
1576
- timeout: Maximum seconds to wait before raising TimeoutError (default: None - no timeout)
1577
- progress_callback: Optional callback(status_dict) called on each status check
1578
-
1601
+ command_id: Command UUID (``create_command(...)["id"]``, also available as ``["commandId"]``)
1602
+ poll_interval: Seconds between status checks (default 2.0)
1603
+ timeout: Maximum seconds to wait (default 24 h; ``None`` waits forever)
1604
+ progress_callback: Optional ``callback(status_dict)`` called on each poll
1605
+
1579
1606
  Returns:
1580
- Final CommandStatusDto dictionary when command completes
1581
-
1607
+ Final command-status dict (``status == "Completed"``). To get the result files, list the
1608
+ version's ``outputFileIds`` (``get_project_version(project_id, version_id)["outputFileIds"]``,
1609
+ then ``download_file``); the ``outputFileIds`` field
1610
+ of the status is not filled by current servers.
1611
+
1582
1612
  Raises:
1583
- TimeoutError: If timeout is reached before completion
1584
- RayfosAPIError: If command fails or is cancelled
1585
-
1613
+ TimeoutError: the command did not finish within ``timeout`` seconds
1614
+ RayfosAPIError: the command failed, was cancelled or was deleted
1615
+
1586
1616
  Example:
1587
- >>> # Create command
1588
- >>> command = client.create_command(project_id, version_id, args_json)
1589
- >>>
1590
- >>> # Wait for completion with progress updates
1591
- >>> def show_progress(status):
1592
- ... print(f"Progress: {status.get('progressValue', 0)}% - {status.get('statusText', 'Running')}")
1593
- >>>
1594
- >>> final_status = client.wait_for_command_completion(
1595
- ... command['id'],
1596
- ... progress_callback=show_progress,
1597
- ... timeout=3600 # 1 hour timeout
1598
- ... )
1599
- >>> print(f"Command completed! Output files: {final_status.get('outputFileIds', [])}")
1617
+ >>> command = client.create_command(project_id, version_id)
1618
+ >>> final = client.wait_for_command_completion(command["id"], timeout=3600,
1619
+ ... progress_callback=lambda s: print(command_status_name(s), s.get("statusText")))
1600
1620
  """
1601
1621
  import time
1602
-
1603
- # CommandStatus enum values from C# (serialized as integers)
1604
- # New=0, Assigned=1, SentToMachine=2, etc.
1605
- COMMAND_STATUS = {
1606
- 0: 'New',
1607
- 1: 'Assigned',
1608
- 2: 'SentToMachine',
1609
- 3: 'SendToMachineForceCancellation',
1610
- 4: 'SendToMachineForceDeleteTask',
1611
- 5: 'RequestedForceCancellationInMachine',
1612
- 6: 'RequestedForceStopTaskInMachine',
1613
- 7: 'Cancelled',
1614
- 8: 'Deleted',
1615
- 9: 'Running',
1616
- 10: 'Completed',
1617
- 11: 'Failed',
1618
- 12: 'FailedRemovedFromMachine'
1619
- }
1620
-
1621
- # Terminal states (by integer value and name)
1622
- TERMINAL_STATUS_INTS = {7, 8, 10, 11, 12} # Cancelled, Deleted, Completed, Failed, FailedRemovedFromMachine
1623
- TERMINAL_STATUS_NAMES = {'Cancelled', 'Deleted', 'Completed', 'Failed', 'FailedRemovedFromMachine'}
1624
-
1622
+
1625
1623
  start_time = time.time()
1626
-
1627
1624
  while True:
1628
- # Check timeout
1629
- if timeout and (time.time() - start_time) > timeout:
1630
- raise TimeoutError(f"Command {command_id} did not complete within {timeout} seconds")
1631
-
1632
- # Get current status
1633
1625
  status = self.get_command_status(command_id)
1634
- command_status_raw = status.get('commandStatus')
1635
-
1636
- # Handle both integer (from C# enum) and string formats
1637
- if isinstance(command_status_raw, int):
1638
- command_status = COMMAND_STATUS.get(command_status_raw, f'Unknown({command_status_raw})')
1639
- is_terminal = command_status_raw in TERMINAL_STATUS_INTS
1640
- else:
1641
- command_status = command_status_raw
1642
- is_terminal = command_status in TERMINAL_STATUS_NAMES
1643
-
1644
- # Call progress callback if provided
1626
+ name = command_status_name(status)
1627
+
1645
1628
  if progress_callback:
1646
1629
  progress_callback(status)
1647
-
1648
- # Check if command reached terminal state
1649
- if is_terminal:
1650
- if command_status in ('Completed', 10):
1630
+
1631
+ if is_terminal_command_status(name):
1632
+ if name == 'Completed':
1651
1633
  return status
1652
- elif command_status in ('Failed', 'FailedRemovedFromMachine', 11, 12):
1653
- error_msg = status.get('statusText', 'Command failed')
1654
- raise RayfosAPIError(f"Command {command_id} failed: {error_msg}")
1655
- elif command_status in ('Cancelled', 7):
1634
+ detail = status.get('statusText') or ''
1635
+ if name in ('Failed', 'FailedRemovedFromMachine'):
1636
+ raise RayfosAPIError(f"Command {command_id} failed: {detail or name}")
1637
+ if name == 'Cancelled':
1656
1638
  raise RayfosAPIError(f"Command {command_id} was cancelled")
1657
- elif command_status in ('Deleted', 8):
1658
- raise RayfosAPIError(f"Command {command_id} was deleted")
1659
-
1660
- # Wait before next poll
1639
+ raise RayfosAPIError(f"Command {command_id} was deleted")
1640
+
1641
+ if timeout is not None and (time.time() - start_time) > timeout:
1642
+ raise TimeoutError(f"Command {command_id} did not finish within {timeout:.0f} s (last status: {name})")
1643
+
1661
1644
  time.sleep(poll_interval)
1662
-
1645
+
1663
1646
  def create_command_and_wait(
1664
1647
  self,
1665
1648
  project_id: str,
1666
1649
  version_id: str,
1667
1650
  local_machine_service_id: Optional[str] = None,
1668
1651
  poll_interval: float = 2.0,
1669
- timeout: Optional[float] = None,
1670
- progress_callback: Optional[callable] = None
1652
+ timeout: Optional[float] = 24 * 3600,
1653
+ progress_callback: Optional[callable] = None,
1654
+ engine_preference: str = 'Any',
1655
+ use_gpu: Optional[bool] = None,
1671
1656
  ) -> Dict[str, Any]:
1672
1657
  """
1673
1658
  Create a command and wait for it to complete (synchronous execution)
@@ -1706,8 +1691,11 @@ class RayfosClient:
1706
1691
  ... client.download_file(project_id, version_id, file_id, f"output/{file_id}")
1707
1692
  """
1708
1693
  # Create the command (async)
1709
- command_status = self.create_command(project_id, version_id, local_machine_service_id)
1710
- command_id = command_status.get('id')
1694
+ # local_machine_service_id pins the run to that compute node (it used to be passed, by
1695
+ # position, as the engine preference, which the server rejects).
1696
+ command_status = self.create_command(project_id, version_id, engine_preference=engine_preference,
1697
+ use_gpu=use_gpu, preferred_machine_id=local_machine_service_id)
1698
+ command_id = command_status.get('id') or command_status.get('commandId')
1711
1699
 
1712
1700
  if not command_id:
1713
1701
  raise RayfosAPIError("Failed to create command: no command ID returned")
@@ -1898,46 +1886,7 @@ class RayfosClient:
1898
1886
  )
1899
1887
  return self._handle_response(response)
1900
1888
 
1901
- def parse_command_status(self, status_value: int) -> str:
1902
- """
1903
- Parse commandStatus from C# enum (integer) to string name
1904
-
1905
- Args:
1906
- status_value: Integer status code from CommandStatusDto
1907
-
1908
- Returns:
1909
- Human-readable status name
1910
-
1911
- CommandStatus enum values from C# RayfosCommandModels:
1912
- 0 = New
1913
- 1 = Assigned
1914
- 2 = SentToMachine
1915
- 3 = SendToMachineForceCancellation
1916
- 4 = SendToMachineForceDeleteTask
1917
- 5 = RequestedForceCancellationInMachine
1918
- 6 = RequestedForceStopTaskInMachine
1919
- 7 = Cancelled
1920
- 8 = Deleted
1921
- 9 = Running
1922
- 10 = Completed
1923
- 11 = Failed
1924
- 12 = FailedRemovedFromMachine
1925
- """
1926
- status_map = {
1927
- 0: "New",
1928
- 1: "Assigned",
1929
- 2: "SentToMachine",
1930
- 3: "SendToMachineForceCancellation",
1931
- 4: "SendToMachineForceDeleteTask",
1932
- 5: "RequestedForceCancellationInMachine",
1933
- 6: "RequestedForceStopTaskInMachine",
1934
- 7: "Cancelled",
1935
- 8: "Deleted",
1936
- 9: "Running",
1937
- 10: "Completed",
1938
- 11: "Failed",
1939
- 12: "FailedRemovedFromMachine",
1940
- }
1941
-
1942
- return status_map.get(status_value, f"Unknown({status_value})")
1889
+ def parse_command_status(self, status_value) -> str:
1890
+ """Command state name from an integer (legacy ``commandStatus``) or a name (current ``status``)."""
1891
+ return command_status_name({"status": status_value})
1943
1892