clarity-api-sdk-python 0.3.36__tar.gz → 0.4.0__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 (61) hide show
  1. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/PKG-INFO +1 -1
  2. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/pyproject.toml +1 -1
  3. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/clarity_api_sdk_python.egg-info/PKG-INFO +1 -1
  4. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/clarity_api_sdk_python.egg-info/SOURCES.txt +2 -0
  5. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/api/sonar_wiz_api.py +121 -13
  6. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/api/sonar_wiz_async_api.py +124 -14
  7. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/cli/main.py +209 -59
  8. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/raw_file_device_mapping.py +43 -2
  9. clarity_api_sdk_python-0.4.0/src/cti/model/raw_file_state.py +21 -0
  10. clarity_api_sdk_python-0.4.0/src/cti/model/user_layer.py +125 -0
  11. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/tests/test_cli.py +225 -71
  12. clarity_api_sdk_python-0.4.0/tests/test_raw_file_device_mapping_model.py +110 -0
  13. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/tests/test_sdk_async_methods.py +122 -17
  14. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/tests/test_sdk_methods.py +97 -20
  15. clarity_api_sdk_python-0.3.36/src/cti/model/raw_file_state.py +0 -12
  16. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/README.md +0 -0
  17. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/setup.cfg +0 -0
  18. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/clarity_api_sdk_python.egg-info/dependency_links.txt +0 -0
  19. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/clarity_api_sdk_python.egg-info/entry_points.txt +0 -0
  20. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/clarity_api_sdk_python.egg-info/requires.txt +0 -0
  21. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/clarity_api_sdk_python.egg-info/top_level.txt +0 -0
  22. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/__init__.py +0 -0
  23. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/api/__init__.py +0 -0
  24. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/api/async_client.py +0 -0
  25. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/api/client.py +0 -0
  26. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/api/session.py +0 -0
  27. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/cli/__init__.py +0 -0
  28. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/cli/__main__.py +0 -0
  29. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/cli/client.py +0 -0
  30. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/logger/__init__.py +0 -0
  31. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/logger/logger.py +0 -0
  32. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/main.py +0 -0
  33. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/main_api.py +0 -0
  34. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/__init__.py +0 -0
  35. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/altitude_source.py +0 -0
  36. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/attitude_source.py +0 -0
  37. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/deferred_object_deletion.py +0 -0
  38. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/depth_source.py +0 -0
  39. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/device.py +0 -0
  40. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/device_type.py +0 -0
  41. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/final_product.py +0 -0
  42. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/hierarchy.py +0 -0
  43. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/layback_algorithm.py +0 -0
  44. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/layback_source.py +0 -0
  45. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/layback_type.py +0 -0
  46. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/organization.py +0 -0
  47. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/platform.py +0 -0
  48. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/platform_type.py +0 -0
  49. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/position_source.py +0 -0
  50. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/processing_job.py +0 -0
  51. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/processing_log.py +0 -0
  52. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/project.py +0 -0
  53. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/projection_option.py +0 -0
  54. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/raw_file.py +0 -0
  55. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/raw_file_configuration.py +0 -0
  56. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/s3.py +0 -0
  57. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/sidescan_ping_source.py +0 -0
  58. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/source.py +0 -0
  59. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/survey.py +0 -0
  60. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/target.py +0 -0
  61. {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.4.0}/src/cti/model/tow_system.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clarity-api-sdk-python
3
- Version: 0.3.36
3
+ Version: 0.4.0
4
4
  Summary: A Python SDK to connect to the CTI Clarity API server.
5
5
  Author-email: "Chesapeake Technology Inc." <support@chesapeaketech.com>
6
6
  Project-URL: Homepage, https://github.com/chesapeake-tech/clarity-api-sdk-python
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "clarity-api-sdk-python"
8
- version = "0.3.36"
8
+ version = "0.4.0"
9
9
  authors = [
10
10
  { name="Chesapeake Technology Inc.", email="support@chesapeaketech.com" },
11
11
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clarity-api-sdk-python
3
- Version: 0.3.36
3
+ Version: 0.4.0
4
4
  Summary: A Python SDK to connect to the CTI Clarity API server.
5
5
  Author-email: "Chesapeake Technology Inc." <support@chesapeaketech.com>
6
6
  Project-URL: Homepage, https://github.com/chesapeake-tech/clarity-api-sdk-python
@@ -51,6 +51,8 @@ src/cti/model/source.py
51
51
  src/cti/model/survey.py
52
52
  src/cti/model/target.py
53
53
  src/cti/model/tow_system.py
54
+ src/cti/model/user_layer.py
54
55
  tests/test_cli.py
56
+ tests/test_raw_file_device_mapping_model.py
55
57
  tests/test_sdk_async_methods.py
56
58
  tests/test_sdk_methods.py
@@ -99,6 +99,10 @@ from cti.model.target import TargetUpdate
99
99
  from cti.model.tow_system import TowSystem
100
100
  from cti.model.tow_system import TowSystemCreate
101
101
  from cti.model.tow_system import TowSystemUpdate
102
+ from cti.model.user_layer import UserLayer
103
+ from cti.model.user_layer import UserLayerCreate
104
+ from cti.model.user_layer import UserLayerUpdate
105
+ from cti.model.user_layer import UserLayerWithUpload
102
106
 
103
107
  from .client import ClarityApiClient
104
108
 
@@ -1975,13 +1979,34 @@ class SonarWizApi:
1975
1979
  response.raise_for_status()
1976
1980
  return ProcessingLog.model_validate(response.json())
1977
1981
 
1978
- def list_processing_logs(self) -> list[ProcessingLog]:
1979
- """List all processing logs.
1982
+ def list_processing_logs(
1983
+ self,
1984
+ *,
1985
+ raw_file_id: UUID | str | None = None,
1986
+ survey_id: UUID | str | None = None,
1987
+ processing_step: str | None = None,
1988
+ ) -> list[ProcessingLog]:
1989
+ """List processing logs, optionally filtered.
1990
+
1991
+ Filters compose: each provided filter narrows the result set.
1992
+
1993
+ Args:
1994
+ raw_file_id: Match only logs for this raw file.
1995
+ survey_id: Match only logs for this survey.
1996
+ processing_step: Match only logs with this processing step
1997
+ (e.g. ``"scan"``, ``"ingest"``, ``"products"``).
1980
1998
 
1981
1999
  Returns:
1982
- List of processing log instances.
2000
+ List of matching processing logs (empty list if none match).
1983
2001
  """
1984
- response = self._client.get("/api/v1/processing-logs")
2002
+ params: dict[str, str] = {}
2003
+ if raw_file_id is not None:
2004
+ params["raw_file_id"] = str(raw_file_id)
2005
+ if survey_id is not None:
2006
+ params["survey_id"] = str(survey_id)
2007
+ if processing_step is not None:
2008
+ params["processing_step"] = processing_step
2009
+ response = self._client.get("/api/v1/processing-logs", params=params or None)
1985
2010
  response.raise_for_status()
1986
2011
  return [ProcessingLog.model_validate(item) for item in response.json()]
1987
2012
 
@@ -2004,6 +2029,81 @@ class SonarWizApi:
2004
2029
  response.raise_for_status()
2005
2030
  return ProcessingLog.model_validate(response.json())
2006
2031
 
2032
+ # ── User Layers ──────────────────────────────────────────────────────
2033
+
2034
+ def create_user_layer(self, user_layer: UserLayerCreate) -> UserLayerWithUpload:
2035
+ """Create a new user layer and initiate upload.
2036
+
2037
+ Args:
2038
+ user_layer: User layer creation data.
2039
+
2040
+ Returns:
2041
+ Created user layer instance with upload information.
2042
+ """
2043
+ response = self._client.post(
2044
+ "/api/v1/user-layers/upload",
2045
+ json=user_layer.model_dump(mode="json"),
2046
+ )
2047
+ response.raise_for_status()
2048
+ return UserLayerWithUpload.model_validate(response.json())
2049
+
2050
+ def get_user_layer(self, user_layer_id: UUID) -> UserLayer:
2051
+ """Fetch a user layer by ID.
2052
+
2053
+ Args:
2054
+ user_layer_id: User layer UUID to fetch.
2055
+
2056
+ Returns:
2057
+ UserLayer instance.
2058
+ """
2059
+ response = self._client.get(f"/api/v1/user-layers/{user_layer_id}")
2060
+ response.raise_for_status()
2061
+ return UserLayer.model_validate(response.json())
2062
+
2063
+ def list_user_layers(self, project_id: UUID) -> list[UserLayer]:
2064
+ """List all user layers for a project.
2065
+
2066
+ Args:
2067
+ project_id: Project UUID to filter by.
2068
+
2069
+ Returns:
2070
+ List of user layer instances.
2071
+ """
2072
+ response = self._client.get(
2073
+ "/api/v1/user-layers",
2074
+ params={"project_id": str(project_id)},
2075
+ )
2076
+ response.raise_for_status()
2077
+ return [UserLayer.model_validate(item) for item in response.json()]
2078
+
2079
+ def update_user_layer(
2080
+ self, user_layer_id: UUID, user_layer: UserLayerUpdate
2081
+ ) -> UserLayer:
2082
+ """Update a user layer.
2083
+
2084
+ Args:
2085
+ user_layer_id: User layer UUID to update.
2086
+ user_layer: User layer update data.
2087
+
2088
+ Returns:
2089
+ Updated user layer instance.
2090
+ """
2091
+ response = self._client.patch(
2092
+ f"/api/v1/user-layers/{user_layer_id}",
2093
+ json=user_layer.model_dump(mode="json", exclude_none=True),
2094
+ )
2095
+ response.raise_for_status()
2096
+ return UserLayer.model_validate(response.json())
2097
+
2098
+ def delete_user_layer(self, user_layer_id: UUID) -> None:
2099
+ """Delete a user layer.
2100
+
2101
+ Args:
2102
+ user_layer_id: User layer UUID to delete.
2103
+ """
2104
+ response = self._client.delete(f"/api/v1/user-layers/{user_layer_id}")
2105
+ response.raise_for_status()
2106
+
2007
2107
  # ── Processing Jobs ──────────────────────────────────────────────────
2008
2108
 
2009
2109
  def create_job(self, job_data: ProcessingJobCreate) -> ProcessingJob:
@@ -2116,7 +2216,9 @@ class SonarWizApi:
2116
2216
  response.raise_for_status()
2117
2217
  configs = [RawFileConfiguration.model_validate(c) for c in response.json()]
2118
2218
  if not configs:
2119
- raise ValueError(f"No raw file configuration found for survey {survey_id}")
2219
+ raise ValueError(
2220
+ f"No raw file configuration found for survey {survey_id}"
2221
+ )
2120
2222
  raw_file_configuration_id = configs[0].raw_file_configuration_id
2121
2223
 
2122
2224
  # Initiate multipart upload
@@ -2156,7 +2258,9 @@ class SonarWizApi:
2156
2258
  f"/api/v1/rawfiles/{raw_file.raw_file_id}/upload-parts/{part_number}/url"
2157
2259
  )
2158
2260
  url_response.raise_for_status()
2159
- presigned_url = PartPresignedURL.model_validate(url_response.json()).url
2261
+ presigned_url = PartPresignedURL.model_validate(
2262
+ url_response.json()
2263
+ ).url
2160
2264
 
2161
2265
  # The server generates presigned URLs using its S3_ENDPOINT_URL,
2162
2266
  # which may be a Docker-internal hostname (e.g. minio:9000).
@@ -2210,23 +2314,27 @@ class SonarWizApi:
2210
2314
  )
2211
2315
  return completed
2212
2316
 
2213
- def trigger_processing(self, survey_id: UUID) -> ProcessingJob:
2214
- """Trigger processing for a survey.
2215
-
2216
- Creates a processing job and dispatches it via SQS.
2317
+ def process(self, survey_id: UUID, target_phase: str = "products") -> ProcessingJob:
2318
+ """Dispatch a pipeline run for a survey.
2217
2319
 
2218
2320
  Args:
2219
2321
  survey_id: Survey to process.
2322
+ target_phase: Pipeline phase to run up to. Defaults to "products"
2323
+ (full pipeline). "scan" runs only setup → scan. The engine's
2324
+ pull-chain resolves any required predecessor phases.
2220
2325
 
2221
2326
  Returns:
2222
2327
  The created ProcessingJob.
2223
2328
  """
2224
- response = self._client.post(f"/api/v1/ingest/{survey_id}")
2329
+ response = self._client.post(
2330
+ "/api/v1/process",
2331
+ json={"survey_id": str(survey_id), "target_phase": target_phase},
2332
+ )
2225
2333
  response.raise_for_status()
2226
2334
  data = response.json()
2227
2335
 
2228
- # The ingest endpoint returns a dict with job_id, not a full ProcessingJob.
2229
- # Fetch the full job record.
2336
+ # The /process endpoint returns a dict with job_id, not a full
2337
+ # ProcessingJob. Fetch the full job record.
2230
2338
  if job_id := data.get("job_id"):
2231
2339
  return self.get_job(job_id)
2232
2340
 
@@ -97,6 +97,10 @@ from cti.model.target import TargetUpdate
97
97
  from cti.model.tow_system import TowSystem
98
98
  from cti.model.tow_system import TowSystemCreate
99
99
  from cti.model.tow_system import TowSystemUpdate
100
+ from cti.model.user_layer import UserLayer
101
+ from cti.model.user_layer import UserLayerCreate
102
+ from cti.model.user_layer import UserLayerUpdate
103
+ from cti.model.user_layer import UserLayerWithUpload
100
104
 
101
105
  from .async_client import ClarityApiAsyncClient
102
106
 
@@ -2023,13 +2027,36 @@ class SonarWizAsyncApi:
2023
2027
  response.raise_for_status()
2024
2028
  return ProcessingLog.model_validate(response.json())
2025
2029
 
2026
- async def list_processing_logs(self) -> list[ProcessingLog]:
2027
- """List all processing logs.
2030
+ async def list_processing_logs(
2031
+ self,
2032
+ *,
2033
+ raw_file_id: UUID | str | None = None,
2034
+ survey_id: UUID | str | None = None,
2035
+ processing_step: str | None = None,
2036
+ ) -> list[ProcessingLog]:
2037
+ """List processing logs, optionally filtered.
2038
+
2039
+ Filters compose: each provided filter narrows the result set.
2040
+
2041
+ Args:
2042
+ raw_file_id: Match only logs for this raw file.
2043
+ survey_id: Match only logs for this survey.
2044
+ processing_step: Match only logs with this processing step
2045
+ (e.g. ``"scan"``, ``"ingest"``, ``"products"``).
2028
2046
 
2029
2047
  Returns:
2030
- List of processing log instances.
2048
+ List of matching processing logs (empty list if none match).
2031
2049
  """
2032
- response = await self._client.get("/api/v1/processing-logs")
2050
+ params: dict[str, str] = {}
2051
+ if raw_file_id is not None:
2052
+ params["raw_file_id"] = str(raw_file_id)
2053
+ if survey_id is not None:
2054
+ params["survey_id"] = str(survey_id)
2055
+ if processing_step is not None:
2056
+ params["processing_step"] = processing_step
2057
+ response = await self._client.get(
2058
+ "/api/v1/processing-logs", params=params or None
2059
+ )
2033
2060
  response.raise_for_status()
2034
2061
  return [ProcessingLog.model_validate(item) for item in response.json()]
2035
2062
 
@@ -2052,6 +2079,83 @@ class SonarWizAsyncApi:
2052
2079
  response.raise_for_status()
2053
2080
  return ProcessingLog.model_validate(response.json())
2054
2081
 
2082
+ # ── User Layers ──────────────────────────────────────────────────────
2083
+
2084
+ async def create_user_layer(
2085
+ self, user_layer: UserLayerCreate
2086
+ ) -> UserLayerWithUpload:
2087
+ """Create a new user layer and initiate upload.
2088
+
2089
+ Args:
2090
+ user_layer: User layer creation data.
2091
+
2092
+ Returns:
2093
+ Created user layer instance with upload information.
2094
+ """
2095
+ response = await self._client.post(
2096
+ "/api/v1/user-layers/upload",
2097
+ json=user_layer.model_dump(mode="json"),
2098
+ )
2099
+ response.raise_for_status()
2100
+ return UserLayerWithUpload.model_validate(response.json())
2101
+
2102
+ async def get_user_layer(self, user_layer_id: UUID) -> UserLayer:
2103
+ """Fetch a user layer by ID.
2104
+
2105
+ Args:
2106
+ user_layer_id: User layer UUID to fetch.
2107
+
2108
+ Returns:
2109
+ UserLayer instance.
2110
+ """
2111
+ response = await self._client.get(f"/api/v1/user-layers/{user_layer_id}")
2112
+ response.raise_for_status()
2113
+ return UserLayer.model_validate(response.json())
2114
+
2115
+ async def list_user_layers(self, project_id: UUID) -> list[UserLayer]:
2116
+ """List all user layers for a project.
2117
+
2118
+ Args:
2119
+ project_id: Project UUID to filter by.
2120
+
2121
+ Returns:
2122
+ List of user layer instances.
2123
+ """
2124
+ response = await self._client.get(
2125
+ "/api/v1/user-layers",
2126
+ params={"project_id": str(project_id)},
2127
+ )
2128
+ response.raise_for_status()
2129
+ return [UserLayer.model_validate(item) for item in response.json()]
2130
+
2131
+ async def update_user_layer(
2132
+ self, user_layer_id: UUID, user_layer: UserLayerUpdate
2133
+ ) -> UserLayer:
2134
+ """Update a user layer.
2135
+
2136
+ Args:
2137
+ user_layer_id: User layer UUID to update.
2138
+ user_layer: User layer update data.
2139
+
2140
+ Returns:
2141
+ Updated user layer instance.
2142
+ """
2143
+ response = await self._client.patch(
2144
+ f"/api/v1/user-layers/{user_layer_id}",
2145
+ json=user_layer.model_dump(mode="json", exclude_none=True),
2146
+ )
2147
+ response.raise_for_status()
2148
+ return UserLayer.model_validate(response.json())
2149
+
2150
+ async def delete_user_layer(self, user_layer_id: UUID) -> None:
2151
+ """Delete a user layer.
2152
+
2153
+ Args:
2154
+ user_layer_id: User layer UUID to delete.
2155
+ """
2156
+ response = await self._client.delete(f"/api/v1/user-layers/{user_layer_id}")
2157
+ response.raise_for_status()
2158
+
2055
2159
  # ── Processing Jobs ──────────────────────────────────────────────────
2056
2160
 
2057
2161
  async def create_job(self, job_data: ProcessingJobCreate) -> ProcessingJob:
@@ -2164,7 +2268,9 @@ class SonarWizAsyncApi:
2164
2268
  response.raise_for_status()
2165
2269
  configs = [RawFileConfiguration.model_validate(c) for c in response.json()]
2166
2270
  if not configs:
2167
- raise ValueError(f"No raw file configuration found for survey {survey_id}")
2271
+ raise ValueError(
2272
+ f"No raw file configuration found for survey {survey_id}"
2273
+ )
2168
2274
  raw_file_configuration_id = configs[0].raw_file_configuration_id
2169
2275
 
2170
2276
  # Initiate multipart upload
@@ -2206,7 +2312,9 @@ class SonarWizAsyncApi:
2206
2312
  f"/api/v1/rawfiles/{raw_file.raw_file_id}/upload-parts/{part_number}/url"
2207
2313
  )
2208
2314
  url_response.raise_for_status()
2209
- presigned_url = PartPresignedURL.model_validate(url_response.json()).url
2315
+ presigned_url = PartPresignedURL.model_validate(
2316
+ url_response.json()
2317
+ ).url
2210
2318
 
2211
2319
  # Upload directly to S3/MinIO (not through API client)
2212
2320
  put_response = await s3_client.put(presigned_url, content=chunk)
@@ -2246,27 +2354,29 @@ class SonarWizAsyncApi:
2246
2354
  )
2247
2355
  return completed
2248
2356
 
2249
- async def trigger_processing(
2250
- self, survey_id: UUID
2357
+ async def process(
2358
+ self, survey_id: UUID, target_phase: str = "products"
2251
2359
  ) -> ProcessingJob:
2252
- """Trigger processing for a survey.
2253
-
2254
- Creates a processing job and dispatches it via SQS.
2360
+ """Dispatch a pipeline run for a survey.
2255
2361
 
2256
2362
  Args:
2257
2363
  survey_id: Survey to process.
2364
+ target_phase: Pipeline phase to run up to. Defaults to "products"
2365
+ (full pipeline). "scan" runs only setup → scan. The engine's
2366
+ pull-chain resolves any required predecessor phases.
2258
2367
 
2259
2368
  Returns:
2260
2369
  The created ProcessingJob.
2261
2370
  """
2262
2371
  response = await self._client.post(
2263
- f"/api/v1/ingest/{survey_id}",
2372
+ "/api/v1/process",
2373
+ json={"survey_id": str(survey_id), "target_phase": target_phase},
2264
2374
  )
2265
2375
  response.raise_for_status()
2266
2376
  data = response.json()
2267
2377
 
2268
- # The ingest endpoint returns a dict with job_id, not a full ProcessingJob.
2269
- # Fetch the full job record.
2378
+ # The /process endpoint returns a dict with job_id, not a full
2379
+ # ProcessingJob. Fetch the full job record.
2270
2380
  if job_id := data.get("job_id"):
2271
2381
  return await self.get_job(job_id)
2272
2382