|
1 | | -from typing import Any, Iterable, List, Union |
| 1 | +from typing import Any, Iterable, List, Union, Optional |
2 | 2 |
|
3 | 3 | import dotenv |
4 | 4 | import httpx |
@@ -54,6 +54,14 @@ def _dt_to_ms(dt: Optional[datetime]) -> Optional[int]: |
54 | 54 | dt = dt.replace(tzinfo=timezone.utc) |
55 | 55 | return int(dt.timestamp() * 1000) |
56 | 56 |
|
| 57 | +def _dt_to_iso_z(dt: Optional[datetime]) -> Optional[str]: |
| 58 | + if not dt: |
| 59 | + return None |
| 60 | + if dt.tzinfo is None: |
| 61 | + dt = dt.replace(tzinfo=timezone.utc) |
| 62 | + return dt.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") |
| 63 | + |
| 64 | + |
57 | 65 |
|
58 | 66 | def _format_checkin_date(dt: Optional[datetime]) -> Optional[str]: |
59 | 67 | """Match example Rockd checkin date strings like 'October 19, 2023'.""" |
@@ -279,22 +287,41 @@ def multiple_spot_to_fieldsite( |
279 | 287 | return out |
280 | 288 |
|
281 | 289 |
|
282 | | -def spot_to_checkin(spot: Union[dict, List[dict]] = Body(...)) -> list[dict]: |
283 | | - """Pipeline: Spot JSON (FeatureCollections) or FieldSites -> Checkin list.""" |
284 | | - # already a single FieldSite-shaped dict |
285 | | - if isinstance(spot, dict) and "location" in spot: |
286 | | - fieldsites: List[FieldSite] = [spot] |
287 | | - # already a list of FieldSite-shaped dicts |
288 | | - elif ( |
289 | | - isinstance(spot, list) |
290 | | - and spot |
291 | | - and isinstance(spot[0], dict) |
292 | | - and "location" in spot[0] |
293 | | - ): |
294 | | - fieldsites = spot |
295 | | - else: |
| 290 | +def spot_to_checkin(spot: Union[dict, List[dict]] = Body(...)) -> Union[dict, list[dict]]: |
| 291 | + """Pipeline: Spot JSON (FeatureCollections) or FieldSites -> Checkin(s). |
| 292 | + Output rule: |
| 293 | + - dict output only when the input is a single object representing a single spot/fieldsite |
| 294 | + (Feature OR FeatureCollection with exactly 1 feature OR single FieldSite dict) |
| 295 | + AND exactly one checkin is produced. |
| 296 | + - otherwise list output |
| 297 | + """ |
| 298 | + #multiple fieldsites |
| 299 | + if isinstance(spot, list): |
| 300 | + if spot and isinstance(spot[0], dict) and "location" in spot[0]: |
| 301 | + return multiple_fieldsite_to_rockd_checkin(spot) |
296 | 302 | fieldsites = multiple_spot_to_fieldsite(spot) |
297 | | - return multiple_fieldsite_to_rockd_checkin(fieldsites) |
| 303 | + return multiple_fieldsite_to_rockd_checkin(fieldsites) |
| 304 | + #single fieldsite object |
| 305 | + if isinstance(spot, dict) and "location" in spot: |
| 306 | + checkins = multiple_fieldsite_to_rockd_checkin([spot]) |
| 307 | + return checkins[0] if len(checkins) == 1 else checkins |
| 308 | + #determine whether this is a single spot |
| 309 | + is_single_spot_payload = False |
| 310 | + if isinstance(spot, dict): |
| 311 | + t = spot.get("type") |
| 312 | + if t == "Feature": |
| 313 | + is_single_spot_payload = True |
| 314 | + elif t == "FeatureCollection": |
| 315 | + feats = spot.get("features") |
| 316 | + if isinstance(feats, list) and len(feats) == 1: |
| 317 | + is_single_spot_payload = True |
| 318 | + fieldsites = multiple_spot_to_fieldsite(spot) |
| 319 | + checkins = multiple_fieldsite_to_rockd_checkin(fieldsites) |
| 320 | + if is_single_spot_payload and len(checkins) == 1: |
| 321 | + return checkins[0] |
| 322 | + return checkins |
| 323 | + |
| 324 | + |
298 | 325 |
|
299 | 326 |
|
300 | 327 | # ___________________________________CHECKIN - FS - SPOT____________________________________ |
@@ -332,7 +359,12 @@ def checkin_to_fieldsite(checkin: dict) -> FieldSite: |
332 | 359 | if planar: |
333 | 360 | observations.append(Observation(data=planar)) |
334 | 361 | created = _parse_date_time(checkin.get("created")) or datetime.now(timezone.utc) |
335 | | - updated = _parse_date_time(checkin.get("added")) or created |
| 362 | + |
| 363 | + updated = (_parse_date_time(checkin.get("updated")) |
| 364 | + or _parse_date_time(checkin.get("added")) |
| 365 | + or created |
| 366 | + ) |
| 367 | + |
336 | 368 | return FieldSite( |
337 | 369 | id=int(cid), |
338 | 370 | location=Location(latitude=float(lat), longitude=float(lng)), |
@@ -368,12 +400,12 @@ def fieldsite_to_rockd_checkin(fs: FieldSite) -> dict: |
368 | 400 | updated = fs.updated or created |
369 | 401 | d: dict = { |
370 | 402 | "checkin_id": fs.id, |
371 | | - "spot_id": fs.id, # <-- added |
| 403 | + "spot_id": fs.id, |
372 | 404 | "notes": fs.notes, |
373 | 405 | "lat": fs.location.latitude, |
374 | 406 | "lng": fs.location.longitude, |
375 | | - "created": _format_checkin_date(created), |
376 | | - "added": _format_checkin_date(updated), |
| 407 | + "created": _dt_to_iso_z(created), |
| 408 | + "updated": _dt_to_iso_z(updated), |
377 | 409 | "observations": [], |
378 | 410 | } |
379 | 411 | if fs.photos: |
@@ -498,8 +530,10 @@ async def convert_field_site( |
498 | 530 | return multiple_checkin_to_fieldsite(payload) |
499 | 531 | if key == ("fieldsite", "checkin"): |
500 | 532 | if isinstance(payload, list): |
| 533 | + if len(payload) == 1: |
| 534 | + return fieldsite_to_rockd_checkin(payload[0]) |
501 | 535 | return multiple_fieldsite_to_rockd_checkin(payload) |
502 | | - return [fieldsite_to_rockd_checkin(payload)] |
| 536 | + return fieldsite_to_rockd_checkin(payload) |
503 | 537 | if key == ("fieldsite", "spot"): |
504 | 538 | if isinstance(payload, list): |
505 | 539 | return multiple_fieldsite_to_spot(payload) |
|
0 commit comments