sinks: read summary.imported as truth for partial-persist detection
The /import response returns activities=[input echo with errors annotated]
— its length equals input size regardless of actual persistence. The
summary{total,imported,skipped,duplicates} block is the authoritative
signal. When imported<total, raise with errorMessage + skipped + duplicates
counts.
This commit is contained in:
parent
4e2da87637
commit
74b2179c83
1 changed files with 24 additions and 9 deletions
|
|
@ -221,20 +221,35 @@ class WealthfolioSink:
|
|||
real = await self._request("POST", _IMPORT_REAL, json={"activities": valid_rows})
|
||||
real.raise_for_status()
|
||||
raw = real.json()
|
||||
# Two observed response shapes:
|
||||
# - {activities:[...], importRunId:"...", summary:{total,imported,skipped,...}}
|
||||
# - bare list (older builds)
|
||||
if isinstance(raw, dict) and "activities" in raw:
|
||||
got = raw["activities"]
|
||||
assert isinstance(got, list)
|
||||
summary = raw.get("summary") if isinstance(raw.get("summary"), dict) else None
|
||||
elif isinstance(raw, list):
|
||||
got = raw
|
||||
summary = None
|
||||
else:
|
||||
got = []
|
||||
# Silent-drop detection: if we sent N valid rows but got 0 back, something
|
||||
# is silently rejecting them (usually a date-format or asset-resolution
|
||||
# quirk that check() didn't catch). Raise so the pipeline records failure
|
||||
# instead of marking the rows as synced when they never landed.
|
||||
if valid_rows and not got:
|
||||
# Also surface any per-row `errors` or `warnings` from the check step
|
||||
# — those are often the best hint about why /import dropped them.
|
||||
summary = None
|
||||
# Summary.imported is THE truth. The `activities` field echoes input
|
||||
# with errors annotated — its length equals input even when zero
|
||||
# actually persisted.
|
||||
if summary is not None:
|
||||
imported_n = int(summary.get("imported", 0))
|
||||
total_n = int(summary.get("total", len(valid_rows)))
|
||||
if imported_n < total_n:
|
||||
err_msg = summary.get("errorMessage") or "no errorMessage"
|
||||
skipped = int(summary.get("skipped", 0))
|
||||
dupes = int(summary.get("duplicates", 0))
|
||||
raise ImportValidationError(
|
||||
f"Wealthfolio /import persisted {imported_n}/{total_n} "
|
||||
f"(skipped={skipped} duplicates={dupes}). "
|
||||
f"errorMessage: {err_msg}"
|
||||
)
|
||||
# Legacy silent-drop guard for no-summary responses.
|
||||
elif valid_rows and not got:
|
||||
first_warn = next(
|
||||
(r.get("warnings") for r in checked if isinstance(r, dict) and r.get("warnings")),
|
||||
None,
|
||||
|
|
@ -242,6 +257,6 @@ class WealthfolioSink:
|
|||
raise ImportValidationError(
|
||||
f"Wealthfolio /import silently dropped all {len(valid_rows)} rows. "
|
||||
f"First checked row: {checked[0] if checked else 'none'}. "
|
||||
f"First warning (if any): {first_warn}"
|
||||
f"First warning: {first_warn}"
|
||||
)
|
||||
return got
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue