Skip to content

phase2/negotiation_followup: $0 hold-reply never supersedes a dealer's real OTD; advisory uses the real best-competing OTD#42

Merged
ryanxwy merged 1 commit into
mainfrom
evolve/board-zero-otd
Jun 30, 2026
Merged

phase2/negotiation_followup: $0 hold-reply never supersedes a dealer's real OTD; advisory uses the real best-competing OTD#42
ryanxwy merged 1 commit into
mainfrom
evolve/board-zero-otd

Conversation

@ryanxwy

@ryanxwy ryanxwy commented Jun 30, 2026

Copy link
Copy Markdown
Owner

What

Two compounding negotiation-board correctness defects from live-e2e 2026-06-29-run2.

PIC-20260629r2-5 — a $0 hold-reply supersedes a dealer's real OTD. A "my $26,905 stands, I won't chase" reply re-extracted to a dealer_quotes row with otd_total=0; SQLite MIN()/nulls-last rank 0 as the cheapest, so the board best_otd + the latest-quote views showed $0 where a real OTD was held.

PIC-20260629r2-4 — "at or below best" advisory off by $49. The tone classifier was fed the strict itemized give-up BATNA, which dropped a cheaper bottom-line (non-itemized) competitor, so a quote $49 above the real best read "at or below best".

Fix

  • persist.ts — write-normalize otd_total <= 0 → null. A no-number reply is still recorded (provenance) but never persists a $0 quote.
  • migration 0006 — heal pre-existing $0 rows → NULL (data fix, no schema change; drizzle-kit generate stays no-op so db:check passes).
  • followupReads.ts — latest/competing reads skip otd_total <= 0; a new bestCompetingRealOtd (lowest real same-mode competing OTD, no itemization gate) drives the tone/display, while the strict itemized BATNA still governs the give_up_switch verdict.
  • negotiationProjection.ts — tone + batna_gap + best_competing_otd use the real baseline; MIN(NULLIF(otd_total,0)) heals the board roll-up.
  • negotiationAdvice.ts — the give_up_switch text cites the strict itemized gap; the tone wording cites the real gap.
  • giveUpProjection.ts — the board card's displayed gap mirrors the modal's real gap (verdict stays strict) so the two surfaces never disagree.

Verification

  • Fresh-context code review APPROVE + safety-invariant audit SAFE + parity-oracle check PARITY OK (both defects restore parity — the pre-fix behavior was the divergence). All separate agents from the fixer.
  • RUN_UI_FUNCTIONAL=1 bash scripts/green.shGREEN; db:check clean (drizzle-kit generate = "No schema changes"); migrator test applies 0006 cleanly.
  • Deterministic regressions: persist.test.ts ($0→null at write), negotiationProjection.test.ts (board best_otd ignores $0; advisory uses the real competing OTD; non-itemized-competitor case; currentOtd ignores a newer $0), giveUpProjection.test.ts (verdict strict, displayed gap real), negotiationAdvice.test.ts (give_up_switch cites the itemized gap, not the tone gap).
  • Paired LLM-seasoned cases registered (re-quotes-then-holds dealer; non-itemized cheaper front-runner).

…r's real OTD; advisory uses the real best-competing OTD

A no-fresh-number reply (hold / payment-only / come-onsite) extracted to
otd_total=0 and — because SQLite MIN()/nulls-last rank 0 as the cheapest —
superseded the dealer's real OTD on the board + latest-quote views
(PIC-20260629r2-5). Separately, the negotiation advisory fed its "at or below
best" tone the strict itemized give-up BATNA, so a quote $49 ABOVE a cheaper
non-itemized competitor read "at or below best" (PIC-20260629r2-4).

- persist.ts: write-normalize otd_total <= 0 -> null (a no-number reply is still
  recorded for provenance, but never persists a $0 quote).
- migration 0006: heal pre-existing $0 rows -> NULL (data fix, no schema change;
  drizzle-kit generate stays no-op so db:check passes).
- followupReads.ts: latest/competing reads skip otd_total <= 0; a new
  bestCompetingRealOtd (lowest real same-mode competing OTD, no itemization gate)
  drives the tone/display, while the strict itemized BATNA still governs the
  give_up_switch verdict.
- negotiationProjection.ts: tone + batna_gap + best_competing_otd use the real
  baseline; MIN(NULLIF(otd_total,0)) heals the board roll-up.
- negotiationAdvice.ts: the give_up_switch text cites the strict itemized gap;
  the tone wording cites the real gap.
- giveUpProjection.ts: the board card's displayed gap mirrors the modal's real
  gap (verdict stays strict) so the two surfaces never disagree.

Deterministic regressions across persist/projection/giveUp/advice. Parity
restored: the latest-quote selector ignores $0/null and the tone uses the lowest
real competing OTD, with the give_up_switch verdict still on the strict itemized BATNA.
@ryanxwy ryanxwy merged commit 60ae1db into main Jun 30, 2026
1 check passed
@ryanxwy ryanxwy deleted the evolve/board-zero-otd branch June 30, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant