fix(cli): keep --json output machine-parseable - #1
Open
dacrypt wants to merge 3 commits into
Open
Conversation
Per-session power curve + curve stats from TeslaMate Postgres samples.
Plan: Option A-revised (Phase 1 only) — ralplan consensus approved.
- core/models/charge.py: ChargeSample, ChargeCurve, ChargeCurveStats; add process_id to ChargingSession
- core/backends/teslaMate.py: get_charge_curve (deterministic ROW_NUMBER stride), get_curve_stats (full-table aggregates), get_charging_process_end_date
- api/routes/teslaMate.py: GET /charging/{id}/curve|stats with 503/404 + conditional Cache-Control immutable
- ui/pages/ChargeSessions.tsx: list + sheet modal, dual-axis Recharts LineChart, stat cards
- ui/App.tsx: IonTab "Carga" + lazy route /charge/sessions
- infra/teslamate_stack.py: pin teslamate/grafana@sha256:e02d1f0...
- tests: golden fixture (1200 samples) + 14 unit tests + DDL schema sentinel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…se 2+3
Un-defers Phase 2 (OOTB doctor) and Phase 3 (curiosidades) from the
ralplan plan. Triggers met: user requested completion of all phases.
Phase 2 — TeslaMate OOTB hardening:
- core/diagnostics/teslamate_doctor.py (new): 8 read-only checks (docker
daemon, stack containers, db reachable, DSN host correctness, car
registered, recent charge data, Grafana reachable, schema columns).
run_doctor() never raises; each check wrapped to fail soft.
- cli/commands/teslaMate.py: `tesla teslaMate doctor [--json]` exit code
= number of failed checks.
- infra/teslamate_stack.py: install() auto-syncs Tesla tokens from keyring
at end (silent on failure); returns dsn_host_reachable + tokens_synced.
- api/routes/teslaMate.py: GET /doctor returns DoctorReport.
- ui/pages/Settings.tsx: "Diagnóstico" panel with run button + per-check
rows (✓/✗) and yellow hints for failures.
Phase 3 — Charge session curiosidades:
- core/models/charge.py: ChargeEnrichment with RankInfo (90-day rank
20→80%), PreconditioningInfo (early-power signature heuristic),
SharedStallInfo (mid-session power-halving detection), AbrpCost (local
fallback estimate via geofence cost_per_unit or cfg.cost_per_kwh).
- core/backends/teslaMate.py: get_charge_curve_enrichment + 4 helpers,
each wrapped in _safe() to degrade gracefully on partial data.
- core/providers/impl/abrp.py: estimate_charge_cost operation; widened
is_available() to work without user_token when cost_per_kwh > 0.
- api/routes/teslaMate.py: GET /charging/{id}/enrichment with 503/cache
semantics matching curve/stats endpoints.
- ui/pages/ChargeSessions.tsx: "Curiosidades de esta sesión" section
with up to 4 conditional cards (rank / preconditioning / shared stall
/ ABRP cost). Each card omitted when its data is null.
Tests: +23 new (2013 total). Ruff clean. UI build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JSON payloads were emitted with `console.print(json.dumps(...))` on the
human-facing Rich console. That console does two things to the payload:
1. Wraps it to the console width — 80 columns whenever stdout is not a TTY,
i.e. every `tesla --json ... | jq` invocation. The wrap inserts a literal
newline inside string values, producing an unparseable document.
2. Parses square brackets in values as Rich markup, silently dropping them —
or raising MarkupError and killing the command on a value like "[/close]".
Repro before this change:
tesla --json providers status | jq .
# jq: parse error: Invalid string: control characters ... must be escaped
COLUMNS=200 tesla --json providers status | jq . # works — width-dependent
Add a dedicated `json_console` (soft_wrap, markup=False, highlight=False) plus
a `write_json()` helper, and route all 93 JSON emission sites through it. The
`console.print_json()` call sites were already safe — Rich's own JSON renderer
neither wraps nor parses markup — and are left alone.
Verified: every `--json` command now parses clean when piped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
--jsonoutput was emitted viaconsole.print(json.dumps(...))on the human-facing Rich console. Rich then does two things that corrupt the payload:| jqinvocation. The wrap inserts a literal newline inside string values.[...]in values as markup — silently dropping it, or raisingMarkupErrorand killing the command outright on a value like[/close].Repro (before)
Any value containing an unmatched closing tag crashed the process:
Fix
Add a dedicated console for machine-readable output and route every JSON emission site through it:
console.print(json.dumps(...))/console.print(x.model_dump_json(...))sites migrated across 21 files.console.print_json()sites were already safe (Rich's own JSON renderer neither wraps nor parses markup) and are left untouched.Verification
Before → after, piping each
--jsoncommand into a strict JSON parser:providers statusdata sourcesconfig showalerts/events list/config validate/providers capabilities/automations listruff checkcleanTests added
tests/test_output_json_integrity.py— 9 tests covering both failure modes against a deliberately narrowed (40-col) console: long-value wrapping forrender_dict/render_table/render_model/render_success, and markup-like values ([bold],[/close],[red]x[/red],[dim]x) round-tripping intact.🤖 Generated with Claude Code