clarity-api-sdk-python 0.3.36__tar.gz → 0.3.38__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.
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/PKG-INFO +1 -1
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/pyproject.toml +1 -1
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/PKG-INFO +1 -1
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/api/sonar_wiz_api.py +17 -9
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/api/sonar_wiz_async_api.py +16 -10
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/cli/main.py +180 -55
- clarity_api_sdk_python-0.3.38/src/cti/model/raw_file_state.py +21 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/tests/test_cli.py +225 -71
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/tests/test_sdk_async_methods.py +44 -17
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/tests/test_sdk_methods.py +42 -20
- clarity_api_sdk_python-0.3.36/src/cti/model/raw_file_state.py +0 -12
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/README.md +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/setup.cfg +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/SOURCES.txt +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/dependency_links.txt +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/entry_points.txt +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/requires.txt +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/top_level.txt +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/api/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/api/async_client.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/api/client.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/api/session.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/cli/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/cli/__main__.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/cli/client.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/logger/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/logger/logger.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/main.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/main_api.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/altitude_source.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/attitude_source.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/deferred_object_deletion.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/depth_source.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/device.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/device_type.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/final_product.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/hierarchy.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/layback_algorithm.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/layback_source.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/layback_type.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/organization.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/platform.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/platform_type.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/position_source.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/processing_job.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/processing_log.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/project.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/projection_option.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/raw_file.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/raw_file_configuration.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/raw_file_device_mapping.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/s3.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/sidescan_ping_source.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/source.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/survey.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/model/target.py +0 -0
- {clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/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.
|
|
3
|
+
Version: 0.3.38
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clarity-api-sdk-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.38
|
|
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
|
{clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/api/sonar_wiz_api.py
RENAMED
|
@@ -2116,7 +2116,9 @@ class SonarWizApi:
|
|
|
2116
2116
|
response.raise_for_status()
|
|
2117
2117
|
configs = [RawFileConfiguration.model_validate(c) for c in response.json()]
|
|
2118
2118
|
if not configs:
|
|
2119
|
-
raise ValueError(
|
|
2119
|
+
raise ValueError(
|
|
2120
|
+
f"No raw file configuration found for survey {survey_id}"
|
|
2121
|
+
)
|
|
2120
2122
|
raw_file_configuration_id = configs[0].raw_file_configuration_id
|
|
2121
2123
|
|
|
2122
2124
|
# Initiate multipart upload
|
|
@@ -2156,7 +2158,9 @@ class SonarWizApi:
|
|
|
2156
2158
|
f"/api/v1/rawfiles/{raw_file.raw_file_id}/upload-parts/{part_number}/url"
|
|
2157
2159
|
)
|
|
2158
2160
|
url_response.raise_for_status()
|
|
2159
|
-
presigned_url = PartPresignedURL.model_validate(
|
|
2161
|
+
presigned_url = PartPresignedURL.model_validate(
|
|
2162
|
+
url_response.json()
|
|
2163
|
+
).url
|
|
2160
2164
|
|
|
2161
2165
|
# The server generates presigned URLs using its S3_ENDPOINT_URL,
|
|
2162
2166
|
# which may be a Docker-internal hostname (e.g. minio:9000).
|
|
@@ -2210,23 +2214,27 @@ class SonarWizApi:
|
|
|
2210
2214
|
)
|
|
2211
2215
|
return completed
|
|
2212
2216
|
|
|
2213
|
-
def
|
|
2214
|
-
"""
|
|
2215
|
-
|
|
2216
|
-
Creates a processing job and dispatches it via SQS.
|
|
2217
|
+
def process(self, survey_id: UUID, target_phase: str = "products") -> ProcessingJob:
|
|
2218
|
+
"""Dispatch a pipeline run for a survey.
|
|
2217
2219
|
|
|
2218
2220
|
Args:
|
|
2219
2221
|
survey_id: Survey to process.
|
|
2222
|
+
target_phase: Pipeline phase to run up to. Defaults to "products"
|
|
2223
|
+
(full pipeline). "scan" runs only setup → scan. The engine's
|
|
2224
|
+
pull-chain resolves any required predecessor phases.
|
|
2220
2225
|
|
|
2221
2226
|
Returns:
|
|
2222
2227
|
The created ProcessingJob.
|
|
2223
2228
|
"""
|
|
2224
|
-
response = self._client.post(
|
|
2229
|
+
response = self._client.post(
|
|
2230
|
+
"/api/v1/process",
|
|
2231
|
+
json={"survey_id": str(survey_id), "target_phase": target_phase},
|
|
2232
|
+
)
|
|
2225
2233
|
response.raise_for_status()
|
|
2226
2234
|
data = response.json()
|
|
2227
2235
|
|
|
2228
|
-
# The
|
|
2229
|
-
# Fetch the full job record.
|
|
2236
|
+
# The /process endpoint returns a dict with job_id, not a full
|
|
2237
|
+
# ProcessingJob. Fetch the full job record.
|
|
2230
2238
|
if job_id := data.get("job_id"):
|
|
2231
2239
|
return self.get_job(job_id)
|
|
2232
2240
|
|
{clarity_api_sdk_python-0.3.36 → clarity_api_sdk_python-0.3.38}/src/cti/api/sonar_wiz_async_api.py
RENAMED
|
@@ -2164,7 +2164,9 @@ class SonarWizAsyncApi:
|
|
|
2164
2164
|
response.raise_for_status()
|
|
2165
2165
|
configs = [RawFileConfiguration.model_validate(c) for c in response.json()]
|
|
2166
2166
|
if not configs:
|
|
2167
|
-
raise ValueError(
|
|
2167
|
+
raise ValueError(
|
|
2168
|
+
f"No raw file configuration found for survey {survey_id}"
|
|
2169
|
+
)
|
|
2168
2170
|
raw_file_configuration_id = configs[0].raw_file_configuration_id
|
|
2169
2171
|
|
|
2170
2172
|
# Initiate multipart upload
|
|
@@ -2206,7 +2208,9 @@ class SonarWizAsyncApi:
|
|
|
2206
2208
|
f"/api/v1/rawfiles/{raw_file.raw_file_id}/upload-parts/{part_number}/url"
|
|
2207
2209
|
)
|
|
2208
2210
|
url_response.raise_for_status()
|
|
2209
|
-
presigned_url = PartPresignedURL.model_validate(
|
|
2211
|
+
presigned_url = PartPresignedURL.model_validate(
|
|
2212
|
+
url_response.json()
|
|
2213
|
+
).url
|
|
2210
2214
|
|
|
2211
2215
|
# Upload directly to S3/MinIO (not through API client)
|
|
2212
2216
|
put_response = await s3_client.put(presigned_url, content=chunk)
|
|
@@ -2246,27 +2250,29 @@ class SonarWizAsyncApi:
|
|
|
2246
2250
|
)
|
|
2247
2251
|
return completed
|
|
2248
2252
|
|
|
2249
|
-
async def
|
|
2250
|
-
self, survey_id: UUID
|
|
2253
|
+
async def process(
|
|
2254
|
+
self, survey_id: UUID, target_phase: str = "products"
|
|
2251
2255
|
) -> ProcessingJob:
|
|
2252
|
-
"""
|
|
2253
|
-
|
|
2254
|
-
Creates a processing job and dispatches it via SQS.
|
|
2256
|
+
"""Dispatch a pipeline run for a survey.
|
|
2255
2257
|
|
|
2256
2258
|
Args:
|
|
2257
2259
|
survey_id: Survey to process.
|
|
2260
|
+
target_phase: Pipeline phase to run up to. Defaults to "products"
|
|
2261
|
+
(full pipeline). "scan" runs only setup → scan. The engine's
|
|
2262
|
+
pull-chain resolves any required predecessor phases.
|
|
2258
2263
|
|
|
2259
2264
|
Returns:
|
|
2260
2265
|
The created ProcessingJob.
|
|
2261
2266
|
"""
|
|
2262
2267
|
response = await self._client.post(
|
|
2263
|
-
|
|
2268
|
+
"/api/v1/process",
|
|
2269
|
+
json={"survey_id": str(survey_id), "target_phase": target_phase},
|
|
2264
2270
|
)
|
|
2265
2271
|
response.raise_for_status()
|
|
2266
2272
|
data = response.json()
|
|
2267
2273
|
|
|
2268
|
-
# The
|
|
2269
|
-
# Fetch the full job record.
|
|
2274
|
+
# The /process endpoint returns a dict with job_id, not a full
|
|
2275
|
+
# ProcessingJob. Fetch the full job record.
|
|
2270
2276
|
if job_id := data.get("job_id"):
|
|
2271
2277
|
return await self.get_job(job_id)
|
|
2272
2278
|
|
|
@@ -18,10 +18,14 @@ app = typer.Typer(
|
|
|
18
18
|
no_args_is_help=True,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
list_app = typer.Typer(
|
|
21
|
+
list_app = typer.Typer(
|
|
22
|
+
help="List resources (surveys, projects, jobs).", no_args_is_help=True
|
|
23
|
+
)
|
|
22
24
|
app.add_typer(list_app, name="list")
|
|
23
25
|
|
|
24
|
-
delete_app = typer.Typer(
|
|
26
|
+
delete_app = typer.Typer(
|
|
27
|
+
help="Delete resources (rawfile, survey, project).", no_args_is_help=True
|
|
28
|
+
)
|
|
25
29
|
app.add_typer(delete_app, name="delete")
|
|
26
30
|
|
|
27
31
|
|
|
@@ -45,10 +49,21 @@ def _get_error_detail(e: httpx.HTTPStatusError) -> str:
|
|
|
45
49
|
def upload(
|
|
46
50
|
file: Path = typer.Argument(..., help="Path to sonar file (.xtf, .jsf, etc.)"),
|
|
47
51
|
survey: UUID = typer.Option(..., "--survey", "-s", help="Survey ID to upload to"),
|
|
48
|
-
config_id: UUID | None = typer.Option(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
config_id: UUID | None = typer.Option(
|
|
53
|
+
None,
|
|
54
|
+
"--config",
|
|
55
|
+
"-c",
|
|
56
|
+
help="Raw file configuration ID (default: survey's default)",
|
|
57
|
+
),
|
|
58
|
+
no_mappings: bool = typer.Option(
|
|
59
|
+
False, "--no-mappings", help="Skip automatic device mapping creation"
|
|
60
|
+
),
|
|
61
|
+
sidescan_stream: str = typer.Option(
|
|
62
|
+
"0", "--sidescan-stream", help="Source stream identifier for sidescan device"
|
|
63
|
+
),
|
|
64
|
+
gnss_stream: str = typer.Option(
|
|
65
|
+
"1", "--gnss-stream", help="Source stream identifier for GNSS device"
|
|
66
|
+
),
|
|
52
67
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
53
68
|
) -> None:
|
|
54
69
|
"""Upload a sonar file to a survey.
|
|
@@ -75,7 +90,9 @@ def upload(
|
|
|
75
90
|
typer.echo(f"Error: {_get_error_detail(e)}", err=True)
|
|
76
91
|
raise typer.Exit(1)
|
|
77
92
|
|
|
78
|
-
typer.echo(
|
|
93
|
+
typer.echo(
|
|
94
|
+
f"Uploaded: {result.raw_file_id} ({result.file_name}, {result.file_size} bytes)"
|
|
95
|
+
)
|
|
79
96
|
|
|
80
97
|
# Auto-create device mappings
|
|
81
98
|
if not no_mappings:
|
|
@@ -104,30 +121,45 @@ def upload(
|
|
|
104
121
|
else:
|
|
105
122
|
continue
|
|
106
123
|
|
|
107
|
-
api.create_raw_file_device_mapping(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
api.create_raw_file_device_mapping(
|
|
125
|
+
RawFileDeviceMappingCreate(
|
|
126
|
+
raw_file_id=result.raw_file_id,
|
|
127
|
+
device_id=device.device_id,
|
|
128
|
+
source_stream_identifier=stream_id,
|
|
129
|
+
input_srid=4326,
|
|
130
|
+
input_timezone=tz,
|
|
131
|
+
)
|
|
132
|
+
)
|
|
114
133
|
typer.echo(f"Mapped: {device.name} → stream {stream_id}")
|
|
115
134
|
mappings_created += 1
|
|
116
135
|
|
|
117
136
|
if mappings_created == 0:
|
|
118
|
-
typer.echo(
|
|
137
|
+
typer.echo(
|
|
138
|
+
"Warning: no sidescan/GNSS devices found on survey platform — no mappings created",
|
|
139
|
+
err=True,
|
|
140
|
+
)
|
|
119
141
|
except httpx.HTTPStatusError as e:
|
|
120
142
|
if e.response.status_code == 404:
|
|
121
|
-
typer.echo(
|
|
143
|
+
typer.echo(
|
|
144
|
+
"Warning: no platform found on survey — skipping device mappings. Run 'ftm init' first.",
|
|
145
|
+
err=True,
|
|
146
|
+
)
|
|
122
147
|
else:
|
|
123
|
-
typer.echo(
|
|
148
|
+
typer.echo(
|
|
149
|
+
f"Warning: failed to create device mappings: {_get_error_detail(e)}",
|
|
150
|
+
err=True,
|
|
151
|
+
)
|
|
124
152
|
|
|
125
153
|
_output(
|
|
126
154
|
{
|
|
127
155
|
"raw_file_id": str(result.raw_file_id),
|
|
128
156
|
"file_name": result.file_name,
|
|
129
157
|
"file_size": result.file_size,
|
|
130
|
-
"state":
|
|
158
|
+
"state": (
|
|
159
|
+
result.state.value
|
|
160
|
+
if hasattr(result.state, "value")
|
|
161
|
+
else str(result.state)
|
|
162
|
+
),
|
|
131
163
|
},
|
|
132
164
|
output_json,
|
|
133
165
|
)
|
|
@@ -136,16 +168,25 @@ def upload(
|
|
|
136
168
|
@app.command()
|
|
137
169
|
def process(
|
|
138
170
|
survey: UUID = typer.Argument(..., help="Survey ID to process"),
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
target_phase: str = typer.Option(
|
|
172
|
+
"products",
|
|
173
|
+
"--target-phase",
|
|
174
|
+
help="Pipeline phase to run up to. 'products' (default) runs the full pipeline; 'scan' runs scan only.",
|
|
175
|
+
),
|
|
176
|
+
wait: bool = typer.Option(
|
|
177
|
+
False, "--wait", "-w", help="Wait for processing to complete"
|
|
178
|
+
),
|
|
179
|
+
timeout: float = typer.Option(
|
|
180
|
+
300, "--timeout", "-t", help="Wait timeout in seconds"
|
|
181
|
+
),
|
|
141
182
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
142
183
|
) -> None:
|
|
143
184
|
"""Trigger processing for a survey."""
|
|
144
185
|
api = get_api()
|
|
145
|
-
typer.echo(f"Triggering processing for survey {survey}...")
|
|
186
|
+
typer.echo(f"Triggering processing for survey {survey} (target: {target_phase})...")
|
|
146
187
|
|
|
147
188
|
try:
|
|
148
|
-
job = api.
|
|
189
|
+
job = api.process(survey_id=survey, target_phase=target_phase)
|
|
149
190
|
except httpx.HTTPStatusError as e:
|
|
150
191
|
typer.echo(f"Error: {_get_error_detail(e)}", err=True)
|
|
151
192
|
raise typer.Exit(1)
|
|
@@ -234,12 +275,27 @@ def status(
|
|
|
234
275
|
def init(
|
|
235
276
|
project: str = typer.Option(..., "--project", "-p", help="Project name"),
|
|
236
277
|
srid: str = typer.Option(..., "--srid", help="Projected CRS (e.g. EPSG:32618)"),
|
|
237
|
-
timezone: str = typer.Option(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
278
|
+
timezone: str = typer.Option(
|
|
279
|
+
"Etc/UTC", "--timezone", "-tz", help="IANA timezone (default: Etc/UTC)"
|
|
280
|
+
),
|
|
281
|
+
org: str = typer.Option(
|
|
282
|
+
"Default Organization",
|
|
283
|
+
"--org",
|
|
284
|
+
"-o",
|
|
285
|
+
help="Organization name (found or created)",
|
|
286
|
+
),
|
|
287
|
+
survey_name: Optional[str] = typer.Option(
|
|
288
|
+
None, "--survey", "-s", help="Survey name (default: project name)"
|
|
289
|
+
),
|
|
290
|
+
description: Optional[str] = typer.Option(
|
|
291
|
+
None, "--description", "-d", help="Survey description"
|
|
292
|
+
),
|
|
293
|
+
platform_type: str = typer.Option(
|
|
294
|
+
"Towed Body", "--platform-type", help="Platform type name (from seed data)"
|
|
295
|
+
),
|
|
296
|
+
platform_name: str = typer.Option(
|
|
297
|
+
"Platform", "--platform-name", help="Platform name"
|
|
298
|
+
),
|
|
243
299
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
244
300
|
) -> None:
|
|
245
301
|
"""Initialize a new project and survey for processing.
|
|
@@ -307,7 +363,10 @@ def init(
|
|
|
307
363
|
pt = next((p for p in pt_list if p.name == platform_type), None)
|
|
308
364
|
if not pt:
|
|
309
365
|
available = ", ".join(p.name for p in pt_list)
|
|
310
|
-
typer.echo(
|
|
366
|
+
typer.echo(
|
|
367
|
+
f"Error: platform type '{platform_type}' not found. Available: {available}",
|
|
368
|
+
err=True,
|
|
369
|
+
)
|
|
311
370
|
raise typer.Exit(1)
|
|
312
371
|
except httpx.HTTPStatusError as e:
|
|
313
372
|
typer.echo(f"Error listing platform types: {_get_error_detail(e)}", err=True)
|
|
@@ -338,21 +397,43 @@ def init(
|
|
|
338
397
|
sidescan_dt = dt_by_enum.get("sidescan_sonar")
|
|
339
398
|
gnss_dt = dt_by_enum.get("gnss_gps_position_sensor")
|
|
340
399
|
if not sidescan_dt or not gnss_dt:
|
|
341
|
-
typer.echo(
|
|
400
|
+
typer.echo(
|
|
401
|
+
"Error: required device types (sidescan_sonar, gnss_gps_position_sensor) not found in seed data",
|
|
402
|
+
err=True,
|
|
403
|
+
)
|
|
342
404
|
raise typer.Exit(1)
|
|
343
405
|
|
|
344
|
-
device_defaults = dict(
|
|
406
|
+
device_defaults = dict(
|
|
407
|
+
channel=0,
|
|
408
|
+
offset_x=0,
|
|
409
|
+
offset_y=0,
|
|
410
|
+
offset_z=0,
|
|
411
|
+
offset_heading=0,
|
|
412
|
+
offset_pitch=0,
|
|
413
|
+
offset_roll=0,
|
|
414
|
+
latency=0,
|
|
415
|
+
)
|
|
345
416
|
try:
|
|
346
|
-
ss_device = api.create_device(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
417
|
+
ss_device = api.create_device(
|
|
418
|
+
DeviceCreate(
|
|
419
|
+
platform_id=new_platform.platform_id,
|
|
420
|
+
device_type_id=sidescan_dt.device_type_id,
|
|
421
|
+
name="Sidescan",
|
|
422
|
+
is_towed=True,
|
|
423
|
+
**device_defaults,
|
|
424
|
+
)
|
|
425
|
+
)
|
|
350
426
|
typer.echo(f"Created device: Sidescan ({ss_device.device_id})")
|
|
351
427
|
|
|
352
|
-
gnss_device = api.create_device(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
428
|
+
gnss_device = api.create_device(
|
|
429
|
+
DeviceCreate(
|
|
430
|
+
platform_id=new_platform.platform_id,
|
|
431
|
+
device_type_id=gnss_dt.device_type_id,
|
|
432
|
+
name="GNSS",
|
|
433
|
+
is_towed=False,
|
|
434
|
+
**device_defaults,
|
|
435
|
+
)
|
|
436
|
+
)
|
|
356
437
|
typer.echo(f"Created device: GNSS ({gnss_device.device_id})")
|
|
357
438
|
except httpx.HTTPStatusError as e:
|
|
358
439
|
typer.echo(f"Error creating device: {_get_error_detail(e)}", err=True)
|
|
@@ -379,7 +460,9 @@ def init(
|
|
|
379
460
|
|
|
380
461
|
@list_app.command("surveys")
|
|
381
462
|
def list_surveys(
|
|
382
|
-
project_name: Optional[str] = typer.Option(
|
|
463
|
+
project_name: Optional[str] = typer.Option(
|
|
464
|
+
None, "--project", "-p", help="Filter by project name"
|
|
465
|
+
),
|
|
383
466
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
384
467
|
) -> None:
|
|
385
468
|
"""List surveys."""
|
|
@@ -396,18 +479,35 @@ def list_surveys(
|
|
|
396
479
|
raise typer.Exit(0)
|
|
397
480
|
|
|
398
481
|
if output_json:
|
|
399
|
-
typer.echo(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
482
|
+
typer.echo(
|
|
483
|
+
json.dumps(
|
|
484
|
+
[
|
|
485
|
+
{
|
|
486
|
+
"survey_id": str(s.survey_id),
|
|
487
|
+
"name": s.name,
|
|
488
|
+
"project_id": str(s.project_id),
|
|
489
|
+
"geodesy_srid": s.geodesy_srid,
|
|
490
|
+
"timezone": s.timezone_name,
|
|
491
|
+
"created": str(s.created_date),
|
|
492
|
+
}
|
|
493
|
+
for s in surveys
|
|
494
|
+
],
|
|
495
|
+
indent=2,
|
|
496
|
+
default=str,
|
|
497
|
+
)
|
|
498
|
+
)
|
|
403
499
|
else:
|
|
404
500
|
for s in surveys:
|
|
405
|
-
typer.echo(
|
|
501
|
+
typer.echo(
|
|
502
|
+
f" {s.survey_id} {s.name} (srid={s.geodesy_srid}, tz={s.timezone_name})"
|
|
503
|
+
)
|
|
406
504
|
|
|
407
505
|
|
|
408
506
|
@list_app.command("projects")
|
|
409
507
|
def list_projects(
|
|
410
|
-
org_name: Optional[str] = typer.Option(
|
|
508
|
+
org_name: Optional[str] = typer.Option(
|
|
509
|
+
None, "--org", "-o", help="Filter by organization name"
|
|
510
|
+
),
|
|
411
511
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
412
512
|
) -> None:
|
|
413
513
|
"""List projects."""
|
|
@@ -424,10 +524,21 @@ def list_projects(
|
|
|
424
524
|
raise typer.Exit(0)
|
|
425
525
|
|
|
426
526
|
if output_json:
|
|
427
|
-
typer.echo(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
527
|
+
typer.echo(
|
|
528
|
+
json.dumps(
|
|
529
|
+
[
|
|
530
|
+
{
|
|
531
|
+
"project_id": str(p.project_id),
|
|
532
|
+
"name": p.project_name,
|
|
533
|
+
"organization_id": str(p.organization_id),
|
|
534
|
+
"created": str(p.created_date),
|
|
535
|
+
}
|
|
536
|
+
for p in projects
|
|
537
|
+
],
|
|
538
|
+
indent=2,
|
|
539
|
+
default=str,
|
|
540
|
+
)
|
|
541
|
+
)
|
|
431
542
|
else:
|
|
432
543
|
for p in projects:
|
|
433
544
|
typer.echo(f" {p.project_id} {p.project_name}")
|
|
@@ -447,16 +558,30 @@ def list_jobs(
|
|
|
447
558
|
raise typer.Exit(0)
|
|
448
559
|
|
|
449
560
|
if output_json:
|
|
450
|
-
typer.echo(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
561
|
+
typer.echo(
|
|
562
|
+
json.dumps(
|
|
563
|
+
[
|
|
564
|
+
{
|
|
565
|
+
"job_id": str(j.job_id),
|
|
566
|
+
"status": j.status,
|
|
567
|
+
"job_type": j.job_type,
|
|
568
|
+
"created_at": str(j.created_at),
|
|
569
|
+
"completed_at": str(j.completed_at) if j.completed_at else None,
|
|
570
|
+
}
|
|
571
|
+
for j in result.jobs
|
|
572
|
+
],
|
|
573
|
+
indent=2,
|
|
574
|
+
default=str,
|
|
575
|
+
)
|
|
576
|
+
)
|
|
454
577
|
else:
|
|
455
578
|
for j in result.jobs:
|
|
456
579
|
typer.echo(f" {j.job_id} {j.status} {j.job_type} {j.created_at}")
|
|
457
580
|
|
|
458
581
|
|
|
459
|
-
def _delete_resource(
|
|
582
|
+
def _delete_resource(
|
|
583
|
+
path: str, resource_label: str, resource_id: UUID, yes: bool
|
|
584
|
+
) -> None:
|
|
460
585
|
"""Confirm and DELETE a resource by its API path. Exits non-zero on error."""
|
|
461
586
|
if not yes and not typer.confirm(f"Delete {resource_label} {resource_id}?"):
|
|
462
587
|
typer.echo("Aborted.")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Raw file state"""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RawFileState(str, Enum):
|
|
7
|
+
"""Status of a raw file through upload, scan, and ingest.
|
|
8
|
+
|
|
9
|
+
The legal transition graph is enforced server-side; this enum is a
|
|
10
|
+
passthrough carrying the value over the wire. See clarity-server
|
|
11
|
+
`model.raw_file_state` for the authoritative transition rules.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
UPLOADING = "uploading"
|
|
15
|
+
READY = "ready"
|
|
16
|
+
ERROR_UPLOAD = "error_upload"
|
|
17
|
+
SCANNING = "scanning"
|
|
18
|
+
SCANNED = "scanned"
|
|
19
|
+
SCAN_ERROR = "scan_error"
|
|
20
|
+
ERROR_INGEST = "error_ingest"
|
|
21
|
+
INGEST_COMPLETE = "ingest_complete"
|