Error visibility and agent tool cleanup

Exchange-rate fetchers log every source failure instead of silently
passing (BE-18); the rate tool exposes fetched_at so staleness is
visible (BE-20). Agent: unbound-session failures raise a clear
RuntimeError (BE-12); net worth converts via get_crc_multipliers with
last-known fallbacks instead of a hardcoded 600 CRC / 1.08 EUR guess,
and reports accounts it cannot convert rather than inventing numbers
(BE-24); budget tool enforces MIN/MAX_YEAR (BE-16); municipal receipts
gain offset paging (BE-17); category analytics prefetches names; the
module docstring pins the read-only tool policy (SEC-09). Note: the
refresh loop never swallowed CancelledError (BaseException since 3.8) —
ARCH-08 was a false positive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-06-10 15:23:21 -06:00
parent 82a7bfc4c5
commit 82f10a5d7c
3 changed files with 49 additions and 28 deletions

View File

@@ -61,8 +61,8 @@ def _fetch_bccr_rate(indicator: int, date_str: str) -> float | None:
for datos in root.iter():
if datos.tag.endswith("NUM_VALOR"):
return float(datos.text.strip().replace(",", "."))
except Exception:
pass
except Exception as e:
logger.warning("BCCR rate fetch failed (indicator %s): %s", indicator, e)
return None
@@ -91,8 +91,8 @@ def _fetch_exchangerate_api() -> tuple[float, float] | None:
crc = data["rates"].get("CRC")
if crc:
return _mid_to_buy_sell(float(crc))
except Exception:
pass
except Exception as e:
logger.warning("ExchangeRate-API fetch failed: %s", e)
return None
@@ -106,7 +106,8 @@ def _fetch_currency_api() -> tuple[float, float] | None:
crc = data.get("usd", {}).get("crc")
if crc:
return _mid_to_buy_sell(float(crc))
except Exception:
except Exception as e:
logger.warning("currency-api fetch failed (%s): %s", url, e)
continue
return None
@@ -120,8 +121,8 @@ def _fetch_floatrates() -> tuple[float, float] | None:
crc_data = data.get("crc")
if crc_data and "rate" in crc_data:
return _mid_to_buy_sell(float(crc_data["rate"]))
except Exception:
pass
except Exception as e:
logger.warning("FloatRates fetch failed: %s", e)
return None
@@ -199,8 +200,8 @@ def _fetch_fiat_crc_mid(code: str) -> float | None:
x = data["rates"].get(code)
if crc and x:
return float(crc) / float(x)
except Exception:
pass
except Exception as e:
logger.warning("%s/CRC fiat rate fetch failed: %s", code, e)
return None
@@ -220,8 +221,8 @@ def _fetch_crypto_crc(code: str) -> float | None:
price = data.get(coin_id, {}).get("crc")
if price:
return float(price)
except Exception:
pass
except Exception as e:
logger.warning("CoinGecko %s/CRC fetch failed: %s", code, e)
return None