Skip to content

Chat-flow analysis + promise-detection baseline (docs + WIP scaffold)#594

Draft
edwh wants to merge 11 commits into
masterfrom
plans/freegle-chat-flow
Draft

Chat-flow analysis + promise-detection baseline (docs + WIP scaffold)#594
edwh wants to merge 11 commits into
masterfrom
plans/freegle-chat-flow

Conversation

@edwh

@edwh edwh commented Jun 1, 2026

Copy link
Copy Markdown
Member

This branch carries two related pieces of work on understanding Freegle member-to-member chats. Docs + a work-in-progress ML scaffold; no production code paths are touched.


Part 1 — Empirical chat-conversation flowcharts (Offer & Wanted)

State machines of User2User chats, derived from the live DB (V2 live API tunnel) over ~40k new conversations in one month, read against ~260 full conversations. Offer and Wanted behave differently, so each has its own flow.

Key finding: Completed only closes the post — it does not prove the replier in this chat got the item. A bare Interested → Completed, or a two-sided chat ending "sorry, gone to someone else", is not a collection. Counting only genuine handover-to-this-replier:

Outcome OFFER WANTED
✅ Collected / received ~43% ~24%
❌ Gone elsewhere / declined ~29% ~6%*
👻 Ghosted ~11% ~38%
⏸️ Stalled ~15% ~30%
↩️ Reneged ~2% ~0%

Files: plans/freegle-chat-flow.md, …-offer.{png,dot} + …-offer-workflow.json, …-wanted.{png,dot} + …-wanted-workflow.json (ai-flower workflow definitions, % on every transition).


Part 2 — Promise-detection: linear baseline scaffold (WIP) + session brief

Detecting the promised state (the point an exchange is committed to) from natural-language dialogue, per the working-notes spec. This is the pragmatic §5.2 baseline: a linear classifier over TF-IDF word + char n-grams on windowed, speaker-tagged, PII-normalised spans, as Laravel artisan commands in iznik-batch.

  • plans/promise-detection-session-brief.md — full brief: spec interpretation, locked decisions (PHP/Rubix-ML, static CSV, address-as-signal not pure leakage, charset/utf8mb4), live-DB findings (~16.5% positive; role resolution), sample sizing, the dataset CSV contract, and the open alternatives (embedding classifier, DST/generative, zero-shot NLI) so another explorer can build a comparable model off the same span→label contract.
  • Code (iznik-batch/app/Services/Promise, …/Console/Commands/Promise): PiiNormaliser, DatasetExtractor, CharNgramTokenizer, DatasetReader, KeywordBaseline, FeaturePipeline, Evaluator (P/R/F1, ROC/PR-AUC, threshold sweep, promise-timing offsets), promise:extract + promise:train commands, 6 unit-test files (53 cases), the CSV CONTRACT.md + synthetic fixture, and rubix/ml in composer.

Status — runs end-to-end on real data; WIP, not merge-ready. Defects fixed along the way: real probabilities (the Rubix Pipeline::proba() double-transformed — replaced with fit-once/transform-only transformers), top n-grams implemented, and a CSV data-corruption bug (fputcsv/fgetcsv default \-escaping silently merged ~30% of rows on read — spans are full of \u.. artefacts + quotes; fixed with RFC-4180 escape: ''). Clean result on a 768-room sample (2,516 train / 640 test, split by conversation, 13% positive): linear F1 0.206 / ROC-AUC 0.591 / PR-AUC 0.198near chance, and the keyword baseline beats it on F1 (0.333). (An earlier "F1 0.357 / ROC-AUC 0.751" was an artefact of the corrupted data.)

Success criterion is cost-based, and the baseline fails it. A false positive (wrongly claiming a promise) is stated as 20× worse than a false negative. That makes the target precision ≥ ~95% (minimise expected cost 20·FP + 1·FN; fire only at P>~0.95); below ~95% precision a model is net-negative versus never firing. On clean data the linear baseline tops out ~33% precision (and ROC-AUC 0.591, near chance), so under 20:1 it's not deployable — and it isn't even a convincing proof-of-signal. It sets the bar (precision ≥95%) the embedding/DST alternatives must clear. See the brief for the full reasoning and design implications (two-stage triage / human-in-loop).

Remaining issues: Rubix's dense vectoriser OOMs on char-n-grams at scale (capped for now); char-grams dominate the feature importances and add noise (a word-grams-only pass is the cheap next experiment). This part is pushed to enable parallel exploration, not to merge as-is.


Branch hygiene + test-suite fixes

This branch had accumulated unrelated commits from several sessions (monitor-fsm, mail-retry, freegle tooling) — all of which had already landed in master — plus one duplicate mail commit. It has been rebased onto current master, dropping every one of those; it now contains only the chat-flow docs (Part 1) and the promise scaffold (Part 2).

The committed unit tests also carried PHPUnit-validity bugs that the rebase surfaced, now fixed so the suite runs cleanly:

  • assertEqual() (17 uses) — not a method in PHPUnit 11 → replaced with assertEquals().
  • assertStringNotContainsString() called with a single argument (4 uses) → missing $result haystack supplied.
  • the DatasetExtractor window/tolerance logic and a fixture/assertion drift (EvaluatorTest "random" ROC data, fixture row count, a KeywordBaseline acceptance phrase).
  • the \r\n\t-collapse test used digit+word input (Line 1 … Line 2) that the normaliser's address regex redacts to <ADDRESS>; switched to digit-free words.

Notes

  • PII-free: raw conversation samples (names, addresses, phones) were deliberately not persisted; only aggregate analysis, the synthetic fixture, and the PII-normalising pipeline are committed.
  • Independent of other in-flight work; branched off master.

🤖 Generated with Claude Code

@netlify

netlify Bot commented Jun 1, 2026

Copy link
Copy Markdown

Deploy Preview for golden-caramel-d2c3a7 canceled.

Name Link
🔨 Latest commit ed6f31f
🔍 Latest deploy log https://app.netlify.com/projects/golden-caramel-d2c3a7/deploys/6a53cf3831105b000834ee27

@FreegleGeeks FreegleGeeks added CI: Passed CI build passed CI: Running CI build is currently running and removed CI: Passed CI build passed labels Jun 1, 2026
@edwh edwh changed the title docs(plans): Freegle chat-conversation flowcharts (Offer & Wanted) Chat-flow analysis + promise-detection baseline (docs + WIP scaffold) Jun 1, 2026
@FreegleGeeks FreegleGeeks added the CI: Failed CI build failed label Jun 1, 2026
@edwh edwh force-pushed the plans/freegle-chat-flow branch from be5891d to 5833f71 Compare June 2, 2026 08:48
@FreegleGeeks FreegleGeeks added CI: Failed CI build failed CI: Passed CI build passed and removed CI: Failed CI build failed CI: Running CI build is currently running labels Jun 2, 2026
@edwh edwh added the Blocked PR is blocked and cannot be merged label Jun 3, 2026
@edwh edwh marked this pull request as draft June 3, 2026 08:44
@FreegleGeeks FreegleGeeks added CI: Running CI build is currently running CI: Passed CI build passed and removed CI: Passed CI build passed CI: Running CI build is currently running labels Jun 3, 2026
@FreegleGeeks FreegleGeeks added CI: Running CI build is currently running CI: Passed CI build passed and removed CI: Passed CI build passed CI: Running CI build is currently running labels Jun 10, 2026
edwh and others added 6 commits July 12, 2026 18:28
…Wanted)

State machines of User2User chats derived from ~40k new conversations on the
live DB over one month, split into separate Offer and Wanted flows (different
roles, events, and outcomes). Includes ai-flower workflow JSONs, Graphviz
sources, and rendered diagrams. PII-free aggregate analysis only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… session brief

PHP/Rubix-ML §5.2 baseline (TF-IDF word+char n-grams → logistic regression) over
windowed, speaker-tagged, PII-normalised chat spans, as Laravel artisan commands in
iznik-batch. Includes the dataset CSV contract, a synthetic fixture, extraction +
training/eval commands, pure unit tests (TDD), and plans/promise-detection-session-brief.md
so another explorer can try the embedding / DST alternatives off the same contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace Rubix Pipeline (proba double-transforms) with fit-once/transform-only
transformers so AUC/threshold/timing are meaningful; implement featureImportances-
based top n-grams with empirical positive-rate; wrap samples; serialize-persist.
Cap char-n-gram vocab to bound Rubix's dense matrix. First dev result (768 rooms):
linear F1 0.357 / ROC-AUC 0.751 / PR-AUC 0.458, beating the keyword baseline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dataset/model outputs

Record the FP=20x-FN cost target: success = precision >= ~95% (minimise 20*FP+1*FN);
the linear baseline (~64% precision) is net-negative vs never firing under it, so it
stands as a proof-of-signal baseline and the bar the embedding/DST tracks must clear.
Add storage/promise/.gitignore so the (PII-normalised) dataset CSV + model are never
committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng (data corruption)

fputcsv/fgetcsv used PHP's default backslash escaping. Spans are full of
backslashes (\u.. artefacts) and quotes, so the write/read round-trip broke:
fgetcsv merged rows whose fields ended on a stray escape, gluing several spans
into one record with the wrong label. The extractor wrote ~3146 rows but the
file read back as 1772 — ~30% of the dataset corrupted, invalidating the metrics
trained on it. Fix: escape: '' on both fputcsv and fgetcsv (double-quote quoting).
Verified: a 40-room probe now reads back exactly as many records as were written.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… near-chance

Re-ran on de-corrupted data: linear F1 0.206 / ROC-AUC 0.591 / PR-AUC 0.198 — near
chance and beaten by the keyword baseline. The earlier F1 0.357/ROC 0.751 were
artefacts of the merged-row corruption. Verdict stands and strengthens: not
deployable under 20:1; bar for embedding/DST is precision >= ~95%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
edwh and others added 5 commits July 12, 2026 18:29
…erance, fixture, ROC data)

- assertEqual -> assertEquals across DatasetExtractorTest + PiiNormaliserTest
  (assertEqual is not a PHPUnit 11 method -> 14 errors)
- DatasetExtractor: keep j=0 and the final turn (unless it IS the promise turn)
  within tolerance, and emit rows newest-first so spans include both speakers
- KeywordBaseline: treat 'thank you so much' as an acceptance signal
- dataset_fixture.csv: trim to the documented 22 data rows
- EvaluatorTest: use genuinely-interleaved scores for the ~random ROC case

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tring

Four single-arg calls (the \r/\n/\t collapse test + whitespace-only test)
threw ArgumentCountError under PHPUnit 11. Pass $result as the haystack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ps the address regex

normalise() also redacts '<number> <Word>' as <ADDRESS>, so 'Line 1 Line 2 Tabbed'
became 'Line <ADDRESS> <ADDRESS>'. Use digit-free words; the test is about \r\n\t
collapsing, not PII. Verified against the real normaliser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ESS>

The street-address pattern was /\b\d{1,4}\s+[A-Z][a-z]+.../i — with /i it matched
times/durations/quantities ('come at 6 pm', 'in 2 days', '3 chairs', '7 tonight'),
emitting fake <ADDRESS> tokens. Since a shared address is one of the strongest
promise signals (the keyword baseline fires on any <ADDRESS>), this injected false
positives into the dataset, the baseline and the reader.

Now require a real street suffix (Street/Road/Lane/...) with a capitalised street
name; bare number+word no longer matches. Verified against the failing examples and
real addresses; regression tests added.

NOTE: the extracted CSVs (dev/full) were built with the buggy normaliser and must be
re-extracted; model results derived from them are contaminated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he whole

chat_rooms set and times out the live-DB tunnel. Use a cheap PK-ordered scan
(id desc) bounded by --since/--max-rooms instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@edwh edwh force-pushed the plans/freegle-chat-flow branch from f5b7dd7 to ed6f31f Compare July 12, 2026 17:30
@FreegleGeeks FreegleGeeks added CI: Running CI build is currently running CI: Passed CI build passed and removed CI: Passed CI build passed CI: Running CI build is currently running labels Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Blocked PR is blocked and cannot be merged CI: Passed CI build passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants