Users needed reassurance that unavailable quotes and long AI evidence are normal states, not broken systems. This adds a v1.5.5 upgrade announcement, softens market-unavailable copy, surfaces a one-line recommendation reason, and makes mobile cards prioritize observed temperature, expected high, peak timing, AI expansion, and a separate market line.
Constraint: Keep existing dashboard data contracts and avoid backend schema changes.
Rejected: Hide unavailable market rows entirely | users still need to know weather evidence remains usable without a quote.
Confidence: high
Scope-risk: moderate
Reversibility: clean
Tested: npm run build
Not-tested: Browser visual QA on physical mobile devices.
Users need to know whether a city can be acted on before reading the full explanation. City cards now expose METAR or official-observation freshness, model update timing, market quote freshness, and AI state in a dedicated trust block, while the calendar view groups rows into action-oriented timing buckets with a single reason per city.
Constraint: Keep the existing card and calendar data contracts; derive freshness and action reasons from fields already present in the frontend payload.
Rejected: Add another long explanatory paragraph | it would repeat the same trust problem instead of making the first glance clearer.
Confidence: high
Scope-risk: moderate
Reversibility: clean
Tested: npm run build
Not-tested: Live browser visual QA with stale METAR and delayed quote examples.
Users saw fast-rule evidence, partial streams, DeepSeek completion, and fallback states as one blended AI status. The city evidence panel now names the current stage directly so a loading stream reads as fast judgment complete, a successful response reads as AI bulletin read complete, and incomplete responses explain that rule evidence is being used.
Constraint: Keep the existing fast evidence path visible while DeepSeek streams in.
Rejected: Label fallback as an AI failure | that incorrectly implies the card is broken even when rule evidence is valid.
Confidence: high
Scope-risk: narrow
Reversibility: clean
Tested: npm run build
Not-tested: Manual browser timing of partial stream transitions.
City decision cards already had enough evidence, but the first screen still forced users to read the longer explanation before seeing why a card mattered. The header now caps status chips at three high-priority signals and uses product-facing labels for observed breakouts, stale METARs, AI loading, missing market prices, strong model agreement, and next-report waits.
Constraint: The card should stay lightweight and avoid another explanatory section.
Rejected: Keep six mixed freshness chips in the header | too much noise for first-glance scanning.
Confidence: high
Scope-risk: narrow
Reversibility: clean
Tested: npm run build
Not-tested: Browser visual QA across all city states.
Qingdao needs the same airport-settlement path as the other Wunderground-backed APAC cities, so the registry, aliases, timezone, prewarm, official links, market focus, and tests now point to ZSQD / Qingdao Jiaodong International Airport.
Constraint: User supplied Wunderground Qingdao/ZSQD settlement URL.
Rejected: Add a partial registry-only entry | it would show in APIs without frontend links, prewarm coverage, or alias support.
Confidence: high
Scope-risk: narrow
Reversibility: clean
Tested: pytest tests/test_country_networks.py tests/test_web_observability.py::test_cities_endpoint_includes_new_wunderground_cities -q
Tested: npm run build
Not-tested: Live Wunderground fetch for ZSQD in production.
The decision card now exposes deterministic guard state, data freshness, AI readiness and market sync directly in the card header so users can understand whether they are looking at fresh evidence, a fallback, or a stale/exception case before reading the full explanation.
Constraint: Keep the first useful read available while DeepSeek airport/HKO details are still streaming.\nRejected: Add another expanded evidence panel | header-level badges are faster to scan and avoid increasing card depth.\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nTested: npm run build\nNot-tested: Browser visual QA on production data.
The scan terminal service had grown into a 3.5k-line file that mixed endpoint orchestration, AI provider calls, fallback copy, JSON repair, and deterministic evidence guards. This extracts the city-AI helper layer into a focused module while preserving the old private names through imports for existing tests and callers.
Constraint: Keep behavior unchanged after the previous evidence-guard fixes and avoid a broad service rewrite.
Rejected: Split every scan-terminal concern at once | too much regression risk for this maintenance pass.
Confidence: high
Scope-risk: narrow
Tested: pytest tests/test_web_observability.py -q
Tested: npm run build
DeepSeek can return a polished city forecast that conflicts with already-computed stale-observation, observed-break, or peak-window evidence. The completion path now carries the deterministic fallback guard state and overwrites only the critical fields when provider text or numbers contradict those local facts.
Constraint: Airport-read latency optimization keeps provider output narrow, so backend completion remains the authority for final highs and evidence conflicts.
Rejected: Trust provider final wording when present | it can reintroduce stale METAR anchors or miss observed high breaks.
Confidence: high
Scope-risk: narrow
Tested: pytest tests/test_web_observability.py -q
Tested: npm run build
City-card fallback reads now stop using stale METAR or official observations as strong live anchors. A stale observation no longer forces high/low revisions, and both backend and browser AI cache keys include the observation fingerprint so updated report times, receipt times, temperatures, or stale status invalidate old AI text.
Constraint: Cached city AI reads must not survive a material observation update
Rejected: Let stale METAR trigger observed-break revisions | stale reports can be older than the active temperature path
Confidence: high
Scope-risk: moderate
Tested: pytest tests/test_web_observability.py -q
Tested: npm run build
Fallback city-card reads now distinguish three cases that previously collapsed into the generic fast-evidence copy: observed highs above the model path, observed highs still lagging after the peak window, and low observations before the peak window that should wait for confirmation rather than down-revise immediately. The same pass removes three unused private helpers from the scan terminal service.
Constraint: Fallback output must be useful before the full AI airport-bulletin read returns
Rejected: Treat any low latest METAR as a down-revision | early-day observations can be below the forecast before the peak window
Rejected: Keep unused helper wrappers | they were unreferenced and added noise to an already large module
Confidence: high
Scope-risk: moderate
Tested: pytest tests/test_web_observability.py -q
Tested: npm run build
Fast evidence mode should not say DEB and models support the center when the latest METAR has already exceeded that center or the model upper edge. The fallback now treats the live observation as a lower bound for the daily high and explains the upward revision pressure.
Constraint: Fallback output must remain useful before the full AI bulletin read returns
Rejected: Keep the original DEB center until AI completes | it can be lower than an already-observed temperature
Confidence: high
Scope-risk: narrow
Tested: pytest tests/test_web_observability.py::test_city_ai_fallback_revises_up_when_latest_metar_breaks_above_models tests/test_web_observability.py::test_city_ai_fallback_reasoning_identifies_fast_evidence_mode tests/test_web_observability.py::test_city_ai_stream_request_only_asks_provider_for_observation_read -q
Tested: npm run build
The city card only needs the provider to interpret the latest METAR or official observation. Deterministic fields such as the high-temperature center, model cluster note and fallback risks are already available server-side, so the stream request now asks DeepSeek for only the observation read and concise reasoning.
Constraint: City cards still need a complete payload for both Chinese and English UI modes
Rejected: Keep generating the full decision schema in the stream | too much model output for every card
Rejected: Retry failed streams by default | it can double latency and the fallback can use partial streamed text
Confidence: high
Scope-risk: moderate
Tested: pytest tests/test_web_observability.py::test_city_ai_stream_request_only_asks_provider_for_observation_read tests/test_web_observability.py::test_city_ai_fallback_reasoning_identifies_fast_evidence_mode tests/test_web_observability.py::test_city_ai_partial_json_trims_dangling_taf_clause tests/test_web_observability.py::test_city_ai_schema_completion_trims_dangling_taf_clause -q
Tested: npm run build
The city decision fallback path is generated when the full DeepSeek city-airport read has not completed, so the reasoning copy now labels the state as fast evidence mode instead of saying the AI read is normal.
Constraint: Fallback output may use only DEB, model cluster, and latest observation evidence
Rejected: Keep 'AI read normal' wording | it implies a completed AI interpretation when the fallback path is active
Confidence: high
Scope-risk: narrow
Tested: pytest tests/test_web_observability.py::test_city_ai_fallback_reasoning_identifies_fast_evidence_mode tests/test_web_observability.py::test_city_ai_partial_json_trims_dangling_taf_clause tests/test_web_observability.py::test_city_ai_schema_completion_trims_dangling_taf_clause -q
The calendar local-time and AI airport-read fixes are a numbered product release rather than a temporary snapshot, so the changelog and package/version metadata now use 1.5.5 consistently.
Constraint: Existing snapshot tag was already pushed before the numbered release correction
Rejected: Keep snapshot label in changelog | user clarified this should be the 1.5.5 version
Confidence: high
Scope-risk: narrow
Tested: Reviewed git diff for CHANGELOG, VERSION, frontend package metadata
Not-tested: No runtime tests; metadata-only version correction
The release snapshot is no longer unreleased, so the changelog now names the published calendar-local-time tag and records the AI wording and actionable-window behavior.
Constraint: Existing snapshot tag was already pushed before this documentation correction
Confidence: high
Scope-risk: narrow
Tested: Documentation-only change reviewed with git diff
Not-tested: No runtime tests; changelog-only update