Dynamic Pricing Engine v3 — Specification
From a demand equalizer to a revenue science instrument, with humans approving every move.
0Executive summary
Advertesting exists to answer one commercial question before a single dollar of construction risk is taken: at what prices does this building earn the most money while still selling on schedule? The v2 engine (accedev2) proved the mechanics — score-based evidence windows, statistically honest bias detection, full auditability — but it optimizes the wrong thing: it equalizes demand across segments. Equal demand is a proxy; the target is maximum revenue at target absorption speed.
v3 rebuilds on AIdev around four upgrades, in order of consequence:
- The objective becomes revenue × velocity. The engine estimates a real demand model — how choice probabilities move with price — and recommends the price vector that maximizes expected revenue subject to a sell-through constraint. Equalizing velocity falls out as a special case; it stops being the goal.
- All demand signals, one honest ballot. Every lead contributes exactly one ballot (preserving statistical independence), but the ballot is shaped by their whole journey: every selection, option depth, favorites, and comment sentiment — including the first negative demand signal.
- Humans approve every price move. The engine writes
Dynamic Pricing Recommendationrecords — which units or segments, which direction, how much, and exactly why — and prices change only on approval. Approvals, corrections, and declines become training data. - Simulation by personas, not random draws. The bench becomes a market simulator: budget-constrained buyer archetypes choosing by utility, which makes price elasticity, substitution, convergence to equilibrium, and mispricing-correction all testable — with a known ground truth to verify against.
| Component | v2 (accedev2) | v3 (AIdev) |
|---|---|---|
| Objective | Equalize demand share to inventory share | Maximize revenue subject to absorption target |
| Demand input | One best selection per lead | Full-journey ballots: selections + options + favorites + comment sentiment |
| Price changes | Automatic, capped | Recommendation → human approval → apply |
| Statistics | Per-segment z-tests on shares | z-screening kept + choice model, hierarchical shrinkage, causal price experiments |
| Simulation | Seeded multinomial draws | Utility-driven personas with budgets and behaviors; optional LLM-agent mode |
| Evidence cadence | Score-point windows, Kish n_eff, decision log | Carried over as-is |
| Console | Cloudflare Worker + Plotly | Carried over, + Recommendations, Demand Model, Optimizer, Personas tabs |
| accedev2 assets | Frozen in place as reference — nothing deleted, nothing further deployed there | |
1Objective & success metrics
1.1 The commercial objective, stated as math
For a project with units j = 1…J, price vector p, and a demand model giving each unit an expected absorption rate Dj(p) (demand responds to all prices — buyers substitute), the engine's target is:
The velocity constraint enters as a Lagrangian term — an urgency dial λ: at λ = 0 the engine prices for pure revenue; as λ grows it trades margin for speed. This dial is a business control, not a statistical one, and it surfaces in the console as an explicit revenue ↔ velocity frontier the team can drag along.
1.2 What "revenue" and "velocity" mean pre-market
Advertesting runs before real closings exist, so both quantities are model-based but concretely measurable:
- Expected revenue = Σ pj × P(unit j absorbed by horizon | demand model at current evidence). Reported with uncertainty bands, never as a bare number.
- Velocity = demand intensity per unit (ballot-weighted arrival rate), the pre-market proxy for time-to-sell. Modeled as Poisson arrival rates with conjugate (Gamma) updating, so early estimates are honest about their width.
- Headline KPI for the program: simulated revenue lift versus two baselines — a uniform price list and the v2 equalizer — at equal sell-through. This is the number that justifies the product: "pricing found +X% revenue at the same absorption."
2Data foundation — full-signal ballots
2.1 The independence invariant
Every statistical guarantee in the engine rests on one rule: one lead, one ballot. v2 enforced it crudely by keeping only the best selection. v3 keeps the invariant and drops the crudeness: the ballot's mass stays capped by the lead's engagement weight, but its distribution across units is shaped by everything the lead did.
2.2 Affinity model
- Selections (all of them): breadth is attribute information. A lead picking three corner units casts nearly their whole ballot into "corner" at the segment level — sharper, not noisier, than v2's single pick.
- Favorites (
Advertesting_Favorite__c): deliberate intent between "clicked" and "configured options"; its weight sits between the selection tiers and is a site-level setting. - Comments (
Advertesting_Unit_Comment__c+ the prod sentiment pipeline): the first signed signal. A negative comment is presence-of-dislike — different information from absence-of-interest, and precisely what the down direction has been missing. Price-mention comments ("too expensive") get their own tag over time: that is direct elasticity feedback in natural language. - Timing: every touch carries its timestamp and the prices in force at the time (
Price_at_Selection__c) — historical price context is what turns observational data into elasticity evidence (§3.3).
2.3 Calibration before trust
w_fav and w_com start at conservative defaults and are calibrated, not guessed: the Correlations workbench measures how favorites and sentiment actually predict deep engagement (e.g., "a favorite converts to an all-options selection 60% of the time" ⇒ w_fav ≈ 0.6 × w_all). Until calibrated, the new signals run in measure-only mode — visible everywhere, pricing nothing.
3Demand science
Three layers, each auditable, each feeding the next. The v2 screening layer survives intact as layer 0.
3.0 Screening (carried from v2)
Per-segment bias vs fair share, gated by z × SE on the window's effective sample size (Kish), with per-dimension z overrides and measure-only dimensions. This stays the fast diagnostic and the anomaly alarm — it is not the pricing brain anymore.
3.1 Choice model — the heart of v3
Demand is modeled as discrete choice: a lead selects among the units they can afford with probability rising in utility and falling in price (multinomial logit; the standard demand model of empirical economics):
What this buys, concretely: own-price elasticity (how much demand a +3% move costs), cross-price substitution (raising unit A pushes demand to lookalike B — cannibalization is finally modeled, not ignored), willingness-to-pay distributions per attribute (the hedonic β in dollars), and honest uncertainty on all of it.
3.2 Hierarchy and heterogeneity
- Hierarchical shrinkage (partial pooling): unit-level estimates borrow strength from their segments (unit ← typology ← attribute groups). This is the principled cure for the unit-sparsity problem we kept meeting (~0.7 votes per unit per batch): small-n units get stabilized estimates instead of silence or noise-chasing.
- Latent classes = personas: price sensitivity and tastes vary by buyer type. The estimator fits a small number of latent classes (mixed logit) — and these are mathematically the same objects as the simulation personas (§6). Fitting the model to real data is persona discovery; comparing fitted classes to authored personas closes the calibration loop.
3.3 Causal identification — the part most engines get wrong
- Exploration policy: Thompson-sampling-flavored — perturb more where the posterior on elasticity is widest, always inside the rails and the human-approval gate (perturbation schedules are themselves recommendations to approve).
- Always-valid monitoring: the team watches dashboards continuously, so sequential-testing corrections (alpha-spending / e-value style) back the "significant" labels, not fixed-horizon p-values that stop being valid the moment you peek.
- Multiple-comparison control: carried from v2 (per-dimension z), extended with false-discovery framing on the screening tables.
4Price optimization
- Optimizer: maximize expected revenue under the absorption constraint over the choice model, solved numerically (fixed-point / projected gradient — closed forms exist for homogeneous logit and validate the code). Outputs a full recommended price vector with per-unit predicted demand, revenue impact, and credible intervals.
- Cadence: optimization runs on the evidence cadence carried from v2 (every N score points of new deduped evidence, sliding K-window), plus on-demand from the console.
- Guardrails (all carried, all still needed): launch-price rails (±%), per-period step caps, direction-flip damping, and — new — the approval gate of §5 in front of every applied change. The engine never touches a price directly in v3's default mode.
- Granularity: the optimizer prices units (it must — substitution is unit-level), but recommendations are grouped by driver for human review: "these 7 Type-15B units +2.1% because…" — one decision, many units, full fan-out visible.
5Dynamic Pricing Recommendations — human-in-the-loop
5.1 Object: Dynamic_Pricing_Recommendation__c
| Field | Type | Purpose |
|---|---|---|
| Site / Run | lookup / number | Provenance: which evidence window produced it |
| Scope | picklist | Unit or Segment (segment recs fan out to member units) |
| Unit / Segment descriptor | lookup / text | Target: a unit, or "Floor_Plan_Type = Type 15B" |
| Current → Recommended price | currency ×2, % | The move, absolute and relative |
| Expected revenue Δ / velocity Δ | currency / number | Model-predicted consequences, with intervals |
| Confidence | percent + interval text | Posterior credibility, honestly stated |
| Reason | long text | The full evidence chain in plain language: demand stats, elasticity, substitution effects, which signals (selections / favorites / sentiment) drove it, model version |
| Status | picklist | Pending → Approved | Approved with Correction | Declined, then Applied / Expired |
| Corrected price + correction reason | currency + picklist/text | When humans adjust before approving — the most valuable label of all |
| Decline reason | picklist + text | Structured taxonomy (too aggressive / wrong direction / timing / data doubt / business override) |
| Reviewed by / at, Applied at | user / datetimes | Full audit trail |
| Feature snapshot | long text (JSON) | Frozen model inputs at recommendation time — the training-data payload |
5.2 Workflow
Engine writes recommendations → reviewers act in the console (or on the Salesforce record) → an apply job executes approved prices and stamps the price-change log with the recommendation reference. Nothing changes price without a human verdict; recommendations left unreviewed past their evidence window expire rather than execute stale. A per-site auto-apply switch exists for simulation runs only, so the bench keeps its speed.
5.3 Learning from the verdicts
Every reviewed recommendation is a labeled example, and the schema is designed for training from day one:
- Calibration: systematic corrections are measurable bias ("humans trim recommended decreases by ~40%") and feed straight back as adjustment multipliers.
- Constraint mining: clustered declines reveal unwritten rules ("never move penthouses more than 1% at a time") that become explicit, configurable guardrails.
- Approval model: with volume, a lightweight model of P(approve | rec features) pre-ranks and pre-filters what the engine proposes — the engine learns the team's judgment, visibly and auditable.
- Honest note: this loop needs tens-to-hundreds of verdicts before it teaches anything. Phase one is simply to log perfectly; learning activates when the data has mass.
6Persona simulation lab
6.1 Why personas, in one sentence each
- Elasticity becomes real: budget-capped personas re-decide after every repricing, so demand pushes back and convergence-to-equilibrium is finally testable.
- Correlated demand becomes real: a "growing family" wants 3bd ∩ quiet ∩ mid-floor as a bundle — exactly the entangled demand that stress-tests attribution.
- Mispricing detection becomes testable: "bargain hunters" chase price-below-value gaps; seed a deliberate pricing error and verify the engine finds and corrects it.
6.2 Config: Advertesting_Test_Persona__c
Per persona (site child): mix %, budget rule (price percentile band), price sensitivity α, utility terms in the familiar seed syntax (Corner_Unit__c=true:+3 …), and behavior rates — selections per lead, favorite propensity, comment propensity with a sentiment profile, option depth, VIP probability. Gabriel's six examples (affordable max, bargain hunters, growing families, downsizers, WFH singles, premium buyers) ship as the starter set; the flat "noise" persona reproduces today's silence-run baseline.
6.3 Generator
Each simulated lead: draw persona → filter to affordable units → choose by softmax over utility (a logit model — the same mathematical family the estimator fits, which is the point: known ground truth in, estimated parameters out, compare) → generate the journey: selections, options, favorites, comments with sentiment. An optional LLM-agent mode role-plays personas to produce realistic free-text comments through the actual sentiment pipeline — texture and pipeline testing, small batches, not the statistical workhorse.
6.4 Acceptance suite (the definition of "the engine works")
- Silence: flat persona ⇒ no recommendations beyond the false-alarm budget.
- Detection & attribution: seeded persona demand ⇒ the right dimensions flagged, entangled ones correctly shrunk.
- Mispricing correction: one unit priced −8% below hedonic value + bargain hunters ⇒ recommendation to raise it toward value.
- Convergence: with known utilities the true optimal price vector is computable ⇒ engine recommendations converge to it within tolerance, without oscillation.
- Revenue lift: engine pricing beats uniform pricing and the v2 equalizer on simulated revenue at equal absorption — the headline number.
7Architecture on AIdev
7.1 Separation of concerns
| Tier | Runs | Responsibilities |
|---|---|---|
| Salesforce (AIdev) | Apex + objects | Data capture (ballot signals), evidence bookkeeping (score cadence, decision log), screening layer, Recommendation records + approval workflow + apply job, persona/bench config, price-change audit. No heavy math in Apex — governor limits make it the wrong home for estimation. |
| Pricing Brain | Cloudflare Worker | Choice-model estimation, hierarchical fitting, optimizer, exploration scheduler. Reads via the Connected-App pattern proven on accedev2; writes Recommendations back through the API. Versioned models; every recommendation stamped with its model version. |
| Console v2 | Worker + Plotly SPA | Everything the team loved, plus: Recommendations (review / correct / approve / decline inline), Demand Model (fit diagnostics, elasticity curves, WTP by attribute), Optimizer (frontier explorer with the urgency dial, what-if pricing), Personas (mix + utility editors). Carried: Overview, Run Decisions, Trends, Correlations, Unit Moves, Price Changes, Bench, How It Works. |
7.2 AIdev readiness checklist (known sharp edges)
- AIdev was freshly refreshed (new org id): unmanaged metadata is wiped — everything deploys clean, nothing can be assumed present.
- Einstein / Generative AI enablement is dropped by refresh and, until re-enabled, breaks all Apex test runs and the comment-sentiment prompt template. First task of Phase 0.
- Comment sentiment package (trigger + queueable + template) redeploys from the prod-parity repo; favorites and comments objects exist from the prod clone.
- New Connected App for the Brain/console (key/secret minted in metadata, as before); permission set for the suite; FLS granted explicitly for every deployed field (the "No such column" lesson, now doctrine).
- Data bed for the bench: cloned MSH units exist in AIdev; bench leads are generated on demand by the loader. Real prod campaign data stays untouched in prod.
- accedev2: left intact as a frozen reference. Repos remain the source of truth for anything worth porting.
8Phasing & effort
| Phase | Delivers | Effort* |
|---|---|---|
| 0 | AIdev readiness: Einstein enablement, sentiment deploy, Connected App, permset, data bed | ½ day |
| 1 | Data foundation: ballot model (selections + favorites + sentiment), evidence cadence, screening engine, decision log — the lean v2 core rebuilt on full signals | 1–1½ days |
| 2 | Recommendation object + approval workflow + apply job + console Recommendations tab (engine runs recommend-only from day one) | 1 day |
| 3 | Persona lab: persona object, logit generator with journeys, bench v3, acceptance scenarios 1–3 | 1–1½ days |
| 4 | Pricing Brain: choice-model estimation, hierarchical shrinkage, optimizer + frontier UI, randomized exploration scheduler; acceptance scenarios 4–5 | 2–3 days |
| 5 | Feedback learning v1: verdict analytics, calibration reports, approval pre-ranking (activates once verdict volume exists) | 1 day |
*Working-session estimates for scoping, not commitments. Phases 1–3 produce a reviewable, demo-able system before the heavy science of Phase 4 lands; each phase ends with tests green and the console updated.
9Open questions for review
- Bench site on AIdev: run simulations against the cloned Mont-Saint-Hilaire inventory, or build a dedicated synthetic tower so the bench never shadows a real project?
- Approvers: who reviews recommendations (you, the analyst, both — any approval quorum)? Should Declined require a reason to save (I recommend yes — it's the training label)?
- Auto-apply policy: strictly recommend-only for real projects forever, or eventually threshold-gated auto-apply (e.g., |Δ| ≤ 1% and confidence ≥ 95%) once trust is earned? (v3 ships recommend-only; the switch exists only for simulations.)
- Exploration consent: randomized ±ε% price perturbations are scientifically essential (§3.3) — are you comfortable approving them as scheduled recommendation batches, and what ε feels acceptable for a live Advertesting campaign (I suggest ε ≤ 2%)?
- Absorption target: how do you want to express the velocity constraint pre-market — "every unit reaches X demand intensity within Y weeks", or a portfolio-level absorption curve?
- Labels & language: the Recommendation object will be seen by non-analyst colleagues — French labels / bilingual descriptions per your Salesforce component standards, or English-only like the console?
- LLM budget: agent-mode simulation and LLM-drafted recommendation reasons both consume API tokens — greenlight as optional features with a monthly cap, or defer?
10Glossary for reviewers
- Discrete choice / multinomial logit (MNL)
- The standard economic model of "a person picks one option from a menu": each option gets a utility score; higher utility and lower price raise its probability. Fitting it to Advertesting ballots yields elasticity, substitution, and willingness-to-pay.
- Hedonic pricing
- Decomposing a unit's value into the dollar value of its attributes (a corner is worth $X, a 4th floor $Y). The fitted β of the choice model, read in dollars.
- Elasticity (own- and cross-)
- How much demand changes when a price changes — for the unit itself (own) and for its substitutes (cross). The engine's steering wheel.
- Endogeneity
- The statistical trap where prices already depend on demand, so "price vs demand" correlations lie. Cured only by deliberately randomized price variation.
- Thompson sampling
- An exploration strategy: probe more where you know least, automatically balancing learning against earning.
- Hierarchical shrinkage / partial pooling
- Small-sample estimates (one unit) borrow strength from their group (its typology, its segment), preventing both noise-chasing and silence.
- Latent classes / mixed logit
- Letting the model discover that buyers come in types with different tastes and price sensitivities — the statistical mirror of the personas.
- Kish effective sample size (n_eff)
- What a weighted sample is really "worth" in unweighted observations; all significance bars are computed on it.
- Always-valid (sequential) inference
- Significance guarantees that survive continuous dashboard-watching, unlike classical p-values which assume you look exactly once.
- Revenue–velocity frontier
- The curve of best-achievable trade-offs between total revenue and sell-out speed; the urgency dial λ picks the operating point.