# Venezuela Price Index — Public Methodology

Sections 0-8 of the working methodology, published in full. The panel changelog,
source probe log and collector implementation detail are not published.

The accompanying series (`vpi_daily.csv` / `.json`) is published **the same
day**, as soon as the day's collection is complete. There is no calendar buffer.

Figures struck through below were **retracted on 2026-08-23** and must not be
cited. They are shown struck rather than deleted so the correction is auditable.

---

**Version:** 0.6.0 (2026-10-03) · **Status:** Phase 3 — validation run complete (gate 1 passed), backtest pending BCV's July print
**Base:** 2026-07-03 = 100 (VES and USD series)

> **Restatement notice (2026-10-01, per §8) — `overall_ves` and the VES personal-care series on 3 dates from 2026-09-29; the USD headline does not move.** Two Farmatodo personal-care listings began posting **Bs 2,00** in stock on 2026-09-29 against their own level of Bs 5.762 and Bs 7.340, and the index chained the fall. A print **10x or more from the listing's own median, in a listing that jumped 10x or more between two consecutive prints**, is now treated as a placeholder (§3.3, `scripts/suspect_prints.py`): dropped at the read layer like an out-of-stock price, so the day is missing and carries forward. Applied to the full history it removes **18 prints across 5 listings** (the two creams, and three farmaciasaas breads already excluded by hand on 2026-09-21). At 2026-10-01 `overall_ves` moves **129,1945 → 129,2315** (+0,029%); VES personal care +0,19% on the same 3 dates. The only daily print that changes is **2026-09-29, +0,0826% → +0,1113%**; no post quoted it. `overall_usd` is unchanged. Nothing is deleted and no raw price row is modified. Panel Changelog entry dated 2026-10-01.

> **Restatement notice (2026-09-21b, per §8) — `overall_ves` and the VES personal-care series on 15 dates from 2026-09-07; the USD headline does not move.** A shelf price is accepted only when `list_price / price_ves` sits inside [0,2 … 5,0] (§3.1), and when it does not, the rule **fell back to the transaction price**. Seventeen Farmatodo personal-care listings post a transaction price short by a factor of a thousand (a thousands separator read as a decimal point: `offerPrice` 3,215 beside a `fullPrice` of 3.536, with `unitPrice` 3.048,28 and `taxes` 487,72 internally consistent at the higher level), so at a ratio of 1.100 the band discarded the number that held and chained the one that collapsed. Those listings only entered the chain on **2026-09-18**, when the Farmatodo stock fix replaced a warehouse total with the count of stores holding each item and stopped dropping them as out of stock, so the contamination was applied retroactively to 2026-09-07 and no published issue was built on it. The repair multiplies the transaction price back by 1.000 when, and only when, doing so puts the ratio **inside** the band, and it is gated on the list price and the leg being in the **same currency** (`LIST_PRICE_CURRENCY`), because `list_price` is stored in each source's native currency and an ungated version read a bolívar list price as a dollar one. Applied to the full history. At 2026-09-21 `overall_ves` moves **127,3931 → 127,6108** (+0,171%) and VES personal care +1,13% at worst; the only daily print that changes is **2026-09-07, +0,0705% → +0,2289%**, and it was never published. `overall_usd` is **byte-identical** (Farmatodo posts no dollar price). The restatement **restores** agreement with two figures issue Nº11 distributed and the live series had since drifted away from: weekly VES **+2,8%** and personal care **+2,6%** for 7–13 September. Nothing is deleted and no raw price row is modified. Panel Changelog entry dated 2026-09-21b.

> **Restatement notice (2026-09-21, per §8) — the four USD category series and the two FX-derived USD series; neither headline moves.** The official rate is now keyed to **the date it is in force**, not to the date it was scraped (§4.2). BCV publishes the rate for the next banking day, so 28 of the 133 official rows this project holds were collected on a day the rate they carry was not yet in force — every Friday and Saturday in the table, plus four Mondays reading the Tuesday print. Keying them by collection date put a rate on days it was not in force, by a median of **0,60%** and up to **2,16%** (2026-07-03), which showed up as a fake weekend step in every series that divides bolívares by the rate. Applied to the full history. **`overall_ves` and `overall_usd` are byte-identical before and after** (verified by diff — the USD headline has been the native-USD chain since 2026-08-10 and never touches this rate). What moves, on 78 dates from 2026-07-04: `overall_usd_fx_derived` **98,480 → 97,760** at 2026-09-21 (−0,73%), `overall_usd_all_categories` 98,437 → 97,752, and the four USD category series — personal care **97,447 → 96,127** (−1,35%), OTC pharma 100,318 → 99,185, household 100,921 → 100,040, food 97,822 → 97,360. The artifact this removes is measurable: mean absolute daily change on the weekend-and-Monday transitions of `overall_usd_fx_derived` falls from **1,37× the weekday rate to 0,74×**, which is what a series with no new prices and no new rate on those days should look like. Nothing is deleted and no raw price row is modified. A claim about the official rate on a date BCV published none for is now **refused** rather than certified against the next print. Panel Changelog entry dated 2026-09-21.

> **Restatement notice (2026-09-18, per §8) — the stockout rate from 2026-09-04, and the VES headline on 14 dates.** Farmatodo's collector read a warehouse stock total that is zero almost everywhere, so the chain was published as ~100% out of stock. It now reads the number of stores holding each item. The published `stockout_rate` falls from **39,6% to 22,3%** on 2026-09-18 (every date from 2026-09-04 restated), and because out-of-stock rows are excluded from price relatives (§3.1), the VES headline moves by at most 0,2% on 14 dates from 2026-09-05: **126,79 → 127,01** on 2026-09-18. The USD headline does not move. See the 2026-09-18 changelog entry.

> **Restatement notice (2026-09-17, per §8) — both series, 13 dates, plus two category series restored.** **The repricing gate is withdrawn and replaced by a liveness gate (§3.4), and no source is excluded any more.** The old gate dropped a source when fewer than 20% of its SKUs had *changed price* in 30 days. That selects sources on the outcome the index measures, and the bias runs one way — a store holding its prices is a store posting zero inflation, and removing it leaves only the stores that moved. The replacement drops a catalogue only when **nothing in it moves at all**: price, in-stock flag and discount are checked together against a fixed floor, so a live store that simply is not raising prices stays in and its flat prices count as what they are. On the evidence, every source the old gate was excluding is live: one had repriced most of its catalogue and was held out on a judgeability technicality rather than any measurement, one is slow but moving, and one was failing a shelf-price test on a list-price field it does not keep up to date — a data-mapping defect, logged as open work, not rigidity. Applied to the full history. **Both headlines move on 13 dates from 2026-09-05**, the first date the newly-chained sources have data: at 2026-09-17 `overall_ves` **127.0081 → 126.6969** (−0.245%) and `overall_usd` **97.2588 → 97.3428** (+0.086%); VES food 126.191 → 125.919, household 129.093 → 128.992, OTC 126.498 → 126.302, personal care 128.532 → 127.777. The VES headline moves **down** — the direction the bias predicted. Two category series that the old gate had ended or never started come back: **`alcoholic_beverages`** (USD, restored, 101.24 at 2026-09-17) and **`appliances_household_equip`** (USD, 77 dates, 97.53). Each is carried by a single chain, which is why the old gate ended one and never started the other; both sit outside the headline by construction. Nothing is deleted and no raw price row is modified. Per-chain repricing breadth is still measured and still published — it is now a finding about Venezuelan retail rather than a filter on it. Panel Changelog entry dated 2026-09-17.

> **Restatement notice (2026-09-16, per §8) — SUPERSEDED 2026-09-17, plazas is back in the chain.** One source was excluded from the index chain on 2026-09-16 under the repricing gate, restating the USD series over 12 dates. **That decision was reversed on 2026-09-17** when the gate itself was withdrawn — see the notice above. The operative figures behind the withdrawn decision are not republished here; the reversal, not the arithmetic of a rule we no longer use, is what a reader needs.

> **Restatement notice (2026-08-10, per §8).** The published index history has been **restated** by the Day-38 audit: a source-eligibility gate (§3.4) and a native-USD headline (§4.1) were introduced, both applied to the full history. The VES headline for Jul 3 → Aug 2 moves from **+3.31% to +13.39%**; the USD headline for Jul 3 → Aug 10 moves from **−8.38% to −1.44%**. Nothing was deleted and no raw price row was modified — the change is in which sources are *chained*, not in what is *collected*. Every figure published before this date under the old construction is superseded. Rationale, evidence, and the pre-registered consequences for the §6 backtest are in the Panel Changelog entry dated 2026-08-10.


> **Restatement notice (2026-09-04, per §8) — USD series only.** The USD chains (`overall_usd`, `categories_usd_native`, and the secondary `overall_usd_fx_derived` / `overall_usd_all_categories`) are now computed **two-stage**: a Jevons relative per source within each category, combined across sources with weight ∝ √(common SKUs). Applied to the full history. At 2026-09-04 the USD headline moves from **98.25 to 97.70** (base 100 = 2026-07-03); native-USD food from 98.86 to 97.78, household from 100.05 to 100.67, personal care 92.62 to 92.60, OTC pharma unchanged. **The VES headline and every VES category series are byte-identical before and after** (verified by diff). Rationale, evidence and alternatives: §2.1, §4.1 and the Panel Changelog entry dated 2026-09-04.


> **Restatement notice (2026-09-01, per §8) — VES and USD, issued late on 2026-09-08.** From 2026-09-01 the index chains the **posted shelf price**, not the price actually paid. `price_ves` is the promotional price whenever a promotion is running, so the published headline had been moving with how many promotions the day's scrape happened to CATCH: on one fixed 267-SKU Locatel basket, 15–18 Aug, the shelf price rose smoothly (+1.24%, +0.25%, +0.71%) while the paid price whipsawed +2.84% / −4.14% / +0.49%, tracking promo incidence (11.9 → 3.0 → 25.8 → 28.1%); on 2026-08-31 one chain went from 0 to 426 promoted rows overnight and took 5.16% off the headline in a day. Applied to the full history. Measured at the time (commit `86a1d86`, 2026-08-31 19:47): the maximum daily move falls from **5.16% to 2.02%** and the full period reads **+19.7% rather than +1.0%**. Recomputed 2026-09-08 on two closed windows: the 30-day VES change to 2026-08-22 moves from **+4.67% to +5.82%**, and the 7-day annualised pace at 2026-08-24 from **7.95% to 6.59%**. Transaction price is not discarded — it is a paid companion measure built by the same machinery (`--basis transaction`). Every figure published before 2026-09-01 under the old construction is superseded. **This notice is late**: the rebase shipped on 2026-08-31 without it, which is itself a §8 failure and is recorded in the Panel Changelog entry dated 2026-09-08 (night).
---

## 0. What this index is — and is not

The VPI is a **daily index of posted online retail prices for consumer goods in Venezuela**. It follows the Billion Prices Project playbook: scrape a fixed panel of SKUs from online retailers every day, compute a chained index from price relatives, and aggregate with official CPI category weights ([Cavallo 2013, *Journal of Monetary Economics*](https://www.sciencedirect.com/science/article/abs/pii/S0304393212000967); [working-paper PDF](https://www.hbs.edu/ris/download.aspx?name=Cavallo_Alberto_Online+and+Official+Price+Indexes+Measuring+Argentinas+Inflation.pdf); [Cavallo & Rigobon 2016, *JEP*](https://www.hbs.edu/ris/Publication%20Files/Cavallo_Alberto_J7_The%20Billion%20Prices%20Project_9c302f5e-d31c-4356-9f92-4b906fcf137f.pdf)). Cavallo showed the approach tracked official CPI closely in Brazil, Chile, Colombia — and Venezuela — while exposing a ~3x understatement in Argentina's official numbers. Venezuela today is the Argentina case: [BCV](https://www.bcv.org.ve/precios-consumidor/indice-nacional-de-precios-al-consumidor-inpc) publishes late and selectively; the independent [OVF](https://observatoriodefinanzas.com/) is monthly with lag. Nothing daily exists.

**This is a goods index, not headline inflation.** It covers roughly **40–48% of the official INPC basket by weight** (see §1.3) — food, personal care, household consumables, and OTC pharmacy goods sold by formal online retail. It excludes services, rent, transport, utilities, education, and everything sold in informal markets. We publish that caveat everywhere the number appears. Credibility is the product.

---

## 1. Category taxonomy and weights

### 1.1 The official reference: BCV INPC

The BCV/INE Índice Nacional de Precios al Consumidor (INPC) is a Laspeyres fixed-basket index, base **December 2007 = 100**, covering **362 items in 13 COICOP-style groups** (Clasificador del Consumo Individual por Finalidad, CCIF), with geometric means at the elemental level and arithmetic weighted aggregation above it, weighted by the **III Encuesta Nacional de Presupuestos Familiares (ENPF)** expenditure survey. Sources: [BCV INPC methodology presentation (2008)](https://www.bcv.org.ve/sites/default/files/documentos_metodologicos/preinpc0308.pdf), [BCVOZ Económico 4-2010](https://bcv.org.ve/system/files/publicaciones/bcvozecon042010.pdf), [BCV INPC page](https://www.bcv.org.ve/precios-consumidor/indice-nacional-de-precios-al-consumidor-inpc).

National group weights, from the ponderaciones table in the [BCV methodology document](https://www.bcv.org.ve/sites/default/files/documentos_metodologicos/preinpc0308.pdf) (p. 19; Caracas column shown because our panel is Caracas-skewed):

| # | INPC group | National wt (%) | Caracas wt (%) | VPI coverage |
|---|------------|----------------:|---------------:|--------------|
| 1 | Alimentos y bebidas no alcohólicas | 32.2 | 25.6 | **Covered** (full group) |
| 2 | Bebidas alcohólicas y tabaco | 3.0 | 2.3 | Not covered (v1 panel excludes alcohol/tobacco) |
| 3 | Vestido y calzado | 7.2 | 5.2 | Not covered |
| 4 | Alquiler de vivienda | 9.8 | 12.1 | Not covered (service) |
| 5 | Servicios de la vivienda | 2.3 | 2.7 | Not covered (service) |
| 6 | Equipamiento del hogar | 5.6 | 5.4 | **Partially covered** — non-durable household goods (cleaning, paper) only |
| 7 | Salud | 4.3 | 6.5 | **Partially covered** — medicines/OTC only, not medical services |
| 8 | Transporte | 10.8 | 11.2 | Not covered |
| 9 | Comunicaciones | 3.8 | 4.9 | Not covered |
| 10 | Esparcimiento y cultura | 3.6 | 4.0 | Not covered |
| 11 | Servicios de educación | 2.7 | 4.5 | Not covered (service) |
| 12 | Restaurantes y hoteles | 8.8 | 9.4 | Not covered (service) |
| 13 | Bienes y servicios diversos | 5.8 | 6.2 | **Partially covered** — personal-care goods (COICOP 12.1.3) only |

*(National column sums to 99.9 due to rounding in the source table.)*

**Weight vintage caveat — stated plainly:** these weights come from the III ENPF and the December-2007 base. They are the only official weights that exist; BCV has not published a re-based structure. After two decades of economic collapse, dollarization, and migration, actual expenditure shares have shifted — independent basket work (e.g., CENDAS-FVM family food basket coverage in the Venezuelan press) suggests food now absorbs well over half of household spending for most families. We use the stale official weights anyway, because (a) they are the only citable official structure, (b) they make our monthly aggregate directly comparable to BCV/OVF prints, which are built on the same structure. We will publish an **alternative food-heavy weighting as a sensitivity series** (§6.4), not as the headline.

### 1.2 VPI categories → INPC mapping

The VPI panel has four categories, mapped to INPC groups:

| VPI category | INPC group(s) | Full-group weight | Panel SKU share (target) |
|--------------|---------------|------------------:|-------------------------:|
| Food & non-alcoholic beverages | Group 1 | 32.2 | ~50% |
| Household / cleaning | Group 6 (non-durables portion) | 5.6 | ~20% |
| OTC pharma | Group 7 (medicines portion) | 4.3 | ~10% |
| Personal care | Group 13 (personal-care-goods portion) | 5.8 | ~20% |

Note that **SKU-count allocation ≠ index weight**. SKU counts are a sampling decision (more SKUs where price dispersion and churn are highest); index aggregation uses the renormalized ENPF weights below. Food gets 67% of the index weight but only ~50% of the SKUs; personal care gets 12% of the weight with 20% of the SKUs. This is deliberate and standard.

### 1.3 Covered share and renormalization

- **Upper bound (full covered groups):** 32.2 + 5.6 + 4.3 + 5.8 = **47.9%** of the national INPC basket.
- **Conservative (goods-only portions of groups 6, 7, 13):** BCV does not publish sub-group weights in the public methodology document, so we assume the goods portion of each partially-covered group is ~50–60% of the group. That yields roughly 32.2 + ~2.8 + ~2.4 + ~2.9 ≈ **40%**.

We publish the covered share as **"~40–48% of the official basket, goods only."**

Aggregation weights are the **full-group weights renormalized over the covered set** (transparent; avoids inventing sub-group weights we cannot cite):

| VPI category | Renormalized weight w̃_c |
|--------------|-------------------------:|
| Food & non-alc beverages | 32.2 / 47.9 = **67.2%** |
| Household / cleaning | 5.6 / 47.9 = **11.7%** |
| OTC pharma | 4.3 / 47.9 = **9.0%** |
| Personal care | 5.8 / 47.9 = **12.1%** |

These weights are **fixed at launch** and change only at a documented re-basing (never silently).

---

## 2. Index math

### 2.1 Daily chained Jevons within category

For category *c* on day *t*, over the **common-SKU set** S_{c,t} (SKUs with a usable price on both *t−1* and *t* after the §3 rules):

```
r_{c,t} = exp( (1/|S_{c,t}|) · Σ_{i ∈ S_{c,t}} ln( p_{i,t} / p_{i,t−1} ) )
```

i.e., the **Jevons index** — unweighted geometric mean of price relatives — the standard elemental formula in the [IMF/ILO CPI Manual (2020)](https://www.imf.org/en/Data/Statistics/cpi-manual) and the same family of elemental calculation BCV itself uses (geometric means at the elemental level, per the [BCV methodology](https://www.bcv.org.ve/sites/default/files/documentos_metodologicos/preinpc0308.pdf), p. 29).

Category index level, chained daily from base day 0 = **2026-07-03**:

```
I_{c,0} = 100 ;   I_{c,t} = I_{c,t−1} × r_{c,t}
```

**USD chains — two-stage source weighting (v0.4, 2026-09-04).** The pooled formula above gives every common SKU one vote, so a source's catalogue *size* silently becomes its weight. Prices inside one store are set by one pricing desk and move together, so *n* SKUs from one chain are far fewer than *n* independent observations. For the **USD** chains only (§4.1 series 2 and 3), the daily relative is therefore computed in two stages:

```
r_{s,c,t} = exp( (1/|S_{s,c,t}|) · Σ_{i ∈ S_{s,c,t}} ln( p_{i,t} / p_{i,t−1} ) )      per source s
r_{c,t}   = exp( Σ_s w_{s,c,t} · ln r_{s,c,t} ),   w_{s,c,t} = √|S_{s,c,t}| / Σ_s √|S_{s,c,t}|
```

Four times the common SKUs earns twice the say. The **VES chain keeps the single-stage pooled formula** (no VES source is large enough to dominate, and the source that motivated this, Plaza's, posts no VES prices). The alternatives — pooled (status quo) and one vote per source — and their measured noise and drift over the full history are recomputable with `scripts/analysis/usd_source_weighting_sim.py`; the per-source weight share, common-SKU count and trailing 7/14-day behaviour are published daily in `index.json → usd_source_diagnostics`. The √n rule is a declared placeholder: the intra-store correlation of price changes that would justify a specific discount is measurable once a new source has ~30 days of relatives, and the weight will be revisited then (§8 versioning).

### 2.2 Aggregation across categories

Headline VPI is a weighted arithmetic aggregate of category indexes with the fixed renormalized weights from §1.3:

```
VPI_t = Σ_c  w̃_c · I_{c,t}
```

This mirrors Cavallo (2013): category-level chained indexes from online prices, aggregated with official CPI category weights ([paper](https://www.sciencedirect.com/science/article/abs/pii/S0304393212000967)).

### 2.3 Derived rates

- **Daily rate:** VPI_t / VPI_{t−1} − 1.
- **Monthly rate (headline):** following [Cavallo 2013](https://www.hbs.edu/ris/download.aspx?name=Cavallo_Alberto_Online+and+Official+Price+Indexes+Measuring+Argentinas+Inflation.pdf), the percentage change of the **trailing 30-day average index level vs. the previous 30-day average**. This smooths daily noise and is the number compared against OVF/BCV prints (§6).
- Annualized = (1 + monthly)^12 − 1, labeled as such.

### 2.4 Chain drift — acknowledged risk and cross-check

Daily chaining of high-frequency retail prices is known to **drift** relative to a fixed-base index when prices bounce (promotions dip and revert, carry-forwards resolve asymmetrically) — the scanner-data chain-drift problem discussed in the [CPI Manual (2020)](https://www.imf.org/en/Data/Statistics/cpi-manual) and the scanner-data literature it summarizes. Mitigations:

1. **Posted list prices** (`fullPrice`), with promo prices (`offerPrice`) recorded separately, not fed to the headline relatives — removes the biggest bounce source. A promo-inclusive series may be published later as a secondary metric.
2. **Monthly fixed-base cross-check:** each month-end we compute a direct fixed-base Jevons per category — month-end prices vs. base-date (2026-07-03) prices over SKUs present at both dates — aggregate with the same weights, and compare with the chained level. Divergence > 1 pp for the month is logged, investigated, and disclosed in the weekly note. Persistent drift triggers a methodology revision (documented, versioned — never silent).

---

## 3. Missing prices, re-entry, outliers

### 3.1 Missing / stale prices

- **Days 1–7 missing:** carry forward the last observed price (relative = 1). This is the standard BPP treatment; it damps measured inflation slightly during the gap, which we accept and disclose.
- **Day 8+:** the SKU is **dropped from the relatives** (leaves the common set) until re-observed. It stays in the panel roster.
- **Stock-outs:** Source payloads carry stock-status fields. A price posted for an out-of-stock item is recorded but **excluded from relatives** — a posted price you cannot transact at is not a market price, and in Venezuela stock-outs historically correlate with repressed/mismeasured inflation. The daily **stock-out rate of the panel** is retained as a companion scarcity indicator (BCV itself maintained an "indicador de escasez," see [BCVOZ 4-2010](https://bcv.org.ve/system/files/publicaciones/bcvozecon042010.pdf), p. 5).

- **The stock-out rate is a LEVEL WITHIN ONE PANEL, and the panel moves (added 2026-09-21).** The rate is a share over the listings whose stock field was *collected and priced* that day, and which chains those are has changed **twelve times** since the base: multimax out 2026-07-11 and back 07-12, kromi in 08-04, farmatodo and forum in 09-04, forum out 09-06, plazas in 09-17, among others. Each change steps the published level with no shortage behind it, because the chains do not measure the same thing. Measured across the six chains in the pool on 2026-09-21, the per-chain out-of-stock share ranges from **under 1% to over 50%** — a spread far wider than any move the pooled series has ever printed, so which chains are in the pool matters more to the level than the market does. **A change in this rate across a date where the contributing set changed is not a change in scarcity**, and since 2026-09-21 no recomputer will return one: `rc_stockout_change_pp(start, end)` refuses outright when the two endpoints hold different chains, and `rc_source_stockout_rate(source, at)` is the like-for-like quantity that is always comparable. The pooled level itself is unchanged and still published.
- **The chain that joined on 2026-09-17 is a worked example of why, and it corrects that entry.** The 2026-09-17 entry explained the pooled drop as the new chain stocking out less than the pooled average. The measurement is sharper than that: this chain **drops the price** on an out-of-stock listing, and the rate counts priced rows only, so of its 421 known out-of-stock listings on 2026-09-21 **none** reach the numerator while 2.429 zero-stock-out rows reach the denominator. It enters the pool at zero. Over 2026-09-14 → 09-21 the published series reads 22,81% → 22,38%, DOWN; the five chains present on both dates read **22,81% → 23,64%, UP**. The rate is structurally unable to see that chain's stock-outs, and the like-for-like reading is the one to quote.

### 3.2 Re-entry

- Gap ≤ 7 days (i.e., the SKU was in carry-forward): re-enters seamlessly; relative computed against the carried-forward price.
- Gap > 7 days: on the first re-observed day the SKU contributes **level only, no relative** (avoids one giant catch-up relative spanning the gap); it rejoins the common set the following day. The skipped catch-up move is logged so cumulative panel-level effects can be audited.

### 3.3 Outliers — logged, not silently dropped

Day-over-day relatives **> 4.0x or < 0.25x** are flagged to a review queue with the raw payload snapshot. They **remain in the index** unless review confirms a data artifact:

- **Artifacts** (unit/pack-size change, currency-field confusion, obvious scrape corruption): treated as a **series break** — no relative that day; the SKU restarts as if re-entering after a long gap; the break is recorded in the panel changelog.
- **Genuine moves stay in.** In Venezuela, 4x overnight repricings genuinely happen around devaluation episodes; silently trimming them would bias the index down exactly when it matters most. The review log (flag count, kept/removed, reason) is published with the weekly note.
- **Placeholder prints are removed automatically (decided 2026-10-01).** In a listing whose price jumps **10x or more** between two consecutive transactable prints, every print **10x or more from the listing's own median** on that leg is a placeholder: excluded from the index exactly like an out-of-stock price (§3.1), never the current price or the base of a mover or of a change column, and shown **marked** on the basket and the SKU card rather than hidden. One rule, one module (`scripts/suspect_prints.py`), imported by the index and by every movers table. The 4x review flag above still stands for everything under 10x; outside the flagged listings the largest day-to-day move in the panel's history is ~5x. A placeholder run longer than half a listing's history defeats the median and still goes to the hand list.
- **A RANKING of listings is held to a stricter bar than a COUNT over them (decided 2026-09-22).** Every published "mayores movimientos" table — weekly issue, free dashboard, paid terminal and `data/microdata.json` alike — drops a listing flagged out of stock at **either end** of its window, and draws only from chains the §3.4 gate chains. A ranking selects extremes, and a placeholder price (Bs 2,00, Bs 3,00) is always an extreme, so without the rule the table reliably headlines a number nobody could transact at: the live dashboard has done so at +595.266,7% and again at +370.450,0%. The aggregate repricing statistics do **not** carry the same filter, and that is deliberate — repricing, passthrough lag and price dispersion are statements about a **catalogue**, and a chain that reprices an item nobody can buy today has still repriced it (applying the filter there would move the event count from 199.226 to 166.502 and turn it into a different indicator). **Rank on what is buyable, count on what is catalogued.** A stock field that is absent is *unknown*, not out of stock, and never drops a row.

### 3.4 Source eligibility — the liveness gate

*Liveness gate added 2026-09-17, replacing the repricing gate of 2026-08-10. Applies to the full index history; see the restatement notice in the header.*

A source is collected, stored, and reported unconditionally. It enters **index aggregation** unless its catalogue can be shown to be unmaintained.

**The rule.** In each run, for every source, compute `pct_any_change_30d`: the share of that source's SKUs with ≥2 observations in the trailing 30 days where **at least one** of three things changed — the posted price, the in-stock flag, or the discount being run against list price. Below a fixed floor the catalogue is treated as a frozen mirror and dropped from the chain. Sources with too few such SKUs are not judged at all and remain eligible — a new or barely-collecting source is never dropped for lack of history. The floor is not published: with the panel's chains named, publishing it would amount to telling a retailer the threshold at which it stops being measured.

**Why this and not repricing breadth.** From 2026-08-10 to 2026-09-17 the gate was the share of SKUs that had **changed price**, floored at 20%. That test selects sources on the outcome the index exists to measure, and the bias runs one way: a store whose prices held is a store posting zero inflation, and dropping it leaves a panel of the stores that moved. A price index that discards flat prices over-states inflation by construction — the worst failure mode available to a product whose only asset is credibility.

The old gate also conflated two things that look identical in a price series and are not the same fact:

| | What it is | What it should do |
|---|---|---|
| **Dead page** | The catalogue is not maintained. Its numbers are not offers, so chaining them measures our scraper, not the economy. | Exclude. |
| **Price rigidity** | The page is live; the merchant is simply not raising prices. In a dollarized retail market this is the single most interesting thing the index can find. | **Include, and publish it.** |

Liveness separates them because it is a **positive test for evidence of maintenance**, on channels that are independent of the price level. A store cannot fail it by holding prices, and cannot pass it by holding them either. That asymmetry also removes the need for a span floor on judgeability: a SKU that changed across two days proves the page is live however short its history, whereas a SKU that did not move in two days never proved staleness. `REPRICING_MIN_JUDGE_SPAN_DAYS` therefore still governs the **published** repricing-breadth statistic, which is unchanged — it is a finding now, not a filter.

**Why the floor is where it is.** Measured 2026-09-17 over the trailing 30 days, **every collecting source is live**, and the lowest of them still sits far above the floor. A genuinely unmaintained mirror reads near zero, and nothing in the panel lies between zero and the lowest live source, so the floor sits inside a wide empty band rather than next to any chain's actual value. One source in the roster is collected but not published (§5): it has returned no rows since 2026-09-05, so there is no history to judge it on and no verdict to state. Per-chain figures are withheld for the reason given above.

**What the old gate was excluding, on the same evidence.**

- **The largest catalogue in the panel had repriced most of its SKUs.** It was pinned out by hand on 2026-09-05 for a *judgeability* technicality: its Venezuelan catalogue restarted 2026-09-04, so no SKU yet spanned 15 days and the gate could not judge it. It was never measured as frozen on Venezuelan data. The freeze on record (below) belonged to the **Colombian** catalogue it was wrongly scraping until 2026-09-04, and that is handled where it belongs — in quarantine and `ARTIFACT_EXCLUSIONS` — not in an eligibility gate.
- **One source's `list_price` is a frozen MSRP while the USD price it actually posts moves.** It was failing a shelf-basis gate on a field it does not keep up to date. This is a **data-mapping defect, now open work**, not price rigidity; the index chains multimax's shelf field as stored, and its only category (appliances) sits outside both headlines by construction, so nothing in the published series turns on it today.
- **One source is genuinely sluggish** — it moves, slowly, on both shelf price and the other channels. That is a finding about that chain. It is now published as one instead of being used to remove it.

**What exclusion does and does not mean.** Unchanged. An ineligible source is dropped from the chain **only**. It is still scraped every tick, still stored, still reported in `source_health`, and still published in the weekly note's per-chain repricing table. Price rigidity at a major chain is a finding about Venezuelan retail, not noise to be discarded — suppressing the collection would destroy the evidence for it. This is an interpretation-layer gate, exactly like §3.3's artifact handling.

**Stability and change control.** Unchanged. The gate is evaluated once per run and applied to the whole history, so the published series can never contain a silent mid-series composition change from a source drifting across the threshold day to day. **A source crossing the threshold in either direction is a series break** under §8: it must be logged in the Panel Changelog and disclosed in the next public release. `SOURCE_ELIGIBILITY_OVERRIDES` exists to pin such a decision by hand when the break needs deliberate timing; every entry requires a dated comment and a changelog line. It is **empty as of 2026-09-17** — both entries in it (farmatodo 2026-09-05, plazas 2026-09-16) existed to hold sources out of a gate that could not judge them, and there is nothing left for them to pin.

**Current state (as recorded in `data/index.json` on 2026-09-17).** **No source is excluded.** All seven published sources are eligible on measured liveness: locatel, gama, kromi, farmaciasaas, farmatodo, multimax and plazas, and all seven returned rows on the latest date. An eighth source, forum, is collected but hidden (§5) and therefore appears in none of these blocks. Published live in `data/index.json` under `source_eligibility` (which now carries `liveness_any_change_30d` alongside the reported `repricing_breadth_30d`) and `excluded_sources`.

---

#### 3.4.1 Historical record — the repricing gate (2026-08-10 → 2026-09-17)

*Retained because the series was restated under it and the evidence is still cited elsewhere. It no longer describes how the index works.*

**The rule as it stood.** The share of a source's judgeable SKUs whose price changed at least once in the trailing 30 days, floored at 20%; a SKU was judgeable with ≥2 observations spanning ≥15 days of the window.

**The evidence that forced it.** Farmatodo was ~40% of the VES panel. **675 of its 699 SKUs carried exactly one distinct price across the entire 39-day history.** Nineteen of its listings were priced above Bs 1,000,000 — pre-redenomination residue; no other source had a single listing above Bs 100,000. Farmatodo's 97% price-freeze was independently verified on 2026-08-02 as **real, not a scrape defect** (51/51 SKUs matched the retailer's own origin live, with cache headers and update timestamps confirming fresh delivery). The catalog was served correctly; it simply was not repriced. **The cause was identified on 2026-09-04: it was the Colombian catalogue** — pesos in a stable currency — and it is now quarantined as an artifact. The exclusion was right; the reason given for it was not the one that turned out to be true.

> **Note on an earlier version.** Both paragraphs above used to cite a barcode-matched median comparison between the excluded chain and one included chain, on a claimed n=10 with complete coverage. That comparison does not reproduce and has been removed rather than footnoted: the two chains share five barcodes, not ten, and only one Locatel listing spans the full window. The reasoning is preserved below for the record. The exclusion itself never rested on it.

**Correction, 2026-08-23 (post-audit).** The barcode figures it used to carry are
NOT reproducible and must not be cited. Recomputed against
`data/backups/vpi_2026-08-11.sqlite.gz` — the state on the day they were
published — Farmatodo and Locatel share **5 barcodes in total**, not ten:

| Chain | Listings on shared barcodes | Complete 39-day coverage | Median |
|---|---|---|---|
| Farmatodo | 5 | 5 | **+0,00%** (holds) |
| Locatel | 5 | **1** | +16,24% (n=1) |

Four of the five Locatel listings only exist from 2026-08-04, when the Locatel
panel went 123 → 815 SKUs, so "the same window" is not true of them; measured
first-to-last per listing, three of those four **fell** and the median across
the five is **−2,84%**. No construction yields +14,81%.

What survives, and what should be cited in its place, is the catalogue-wide
repricing breadth over the same 30 days — the same argument on 1.354 SKUs
instead of 5 barcodes:

Across the eligible chains, repricing breadth over that window ran from the low nineties to complete coverage. The excluded source sat more than fifty percentage points beneath the lowest of them. Per-chain breadth figures are withheld — see §1 on why the floor and the named panel are only a leak in combination.

The Farmatodo freeze evidence itself is untouched and remains solid: 675 of 699
SKUs at exactly one distinct price, 19 listings above Bs 1.000.000, and the
2026-08-02 live origin check. **The restatement decision was correct**; only
this one piece of supporting evidence was overstated. See
`growth/post-audit-2026-08-23.md` §1.


---

## 4. Dual currency and the FX companion series

Venezuelan retail is de-facto dual-currency: prices are set mentally in USD and posted in VES at some internal rate. This makes a single-currency index ambiguous, so we publish both, plus the FX series that connects them.

### 4.1 The three series

*Rewritten 2026-08-10 (Day-38 audit). The prior text described a launch-time design that the code never implemented — it specified deflation by the **parallel** rate, while `index.py` has always denominated in the **official** rate. The discrepancy is resolved here in favour of what the code does and what §4.2's rate-concept rule requires. Applies to the full history; see the restatement notice in the header.*

1. **VPI-VES** (headline): computed from posted VES prices as in §2, over eligible sources only (§3.4). Currently chained from locatel, gama and kromi.

2. **VPI-USD** (headline): chained from **natively posted USD prices only** — `price_usd` as the retailer published it, with **no exchange rate anywhere in the construction**. Currently chained from farmaciasaas, kromi, and gama's `valueREF` leg — **multimax was removed from this list on 2026-09-15**, having been excluded by the §3.4 gate since 2026-09-03 while this line still named it. Interpretation: the dollar price of goods, as actually quoted by dollarised formal retail.

3. **VPI-USD (FX-derived)** — *secondary, never the headline, and never presented as an independent finding.* VPI-VES divided by the official rate (§4.2), for the SKUs that have no native USD leg. Published as `overall_usd_fx_derived`.

   **This series must always be labelled as an arithmetic restatement of VPI-VES, not a measurement.** Dividing bolívar prices by the bolívar's own exchange rate cannot answer "what happened to dollar prices"; it restates "VES rose less than the currency fell" in different units. It was the published headline until 2026-08-10, when 76.9% of its observations were FX-derived, and it was actively misleading: the −8.4% "goods got cheaper in dollars" figure carried through the first 39 days was, in the main, **frozen Farmatodo bolívar prices divided by a rising FX rate** (§3.4). Over that same window the chains that genuinely post in dollars all moved within a few tenths of a percent either side of flat — nothing resembling −8.4%.

   Since v0.4 (2026-09-04) the native-USD chain combines sources two-stage with √n weights (§2.1); currently chained from farmaciasaas, kromi and gama's `valueREF` leg. Plazas contributed to this chain from 2026-09-04 and was un-chained on 2026-09-16 (§3.4, and the restatement notice at the top of this file).

   The native-only headline is a smaller and noisier panel than the derived one. It is also the only one of the two that is a measurement, and that trade is not close.

4. **Implied item-level exchange rate** — for any source posting both VES and USD prices on the same SKU, implied rate = p_VES / p_USD, aggregated as a daily median. Live via gama's dual-currency legs. Cavallo & Rigobon note this retailer-implied rate is itself an informative shadow-FX measure in controlled economies ([JEP 2016](https://www.hbs.edu/ris/Publication%20Files/Cavallo_Alberto_J7_The%20Billion%20Prices%20Project_9c302f5e-d31c-4356-9f92-4b906fcf137f.pdf)).

**Pass-through must be measured, never inferred from the ratio of the two headlines.** Dividing VPI-VES by the FX series is arithmetic about a denominator. The measured version is `scripts/passthrough.py`'s **conditional pass-through**: among price changes the chain actually made, the ratio of the price move to the official-rate move over the same interval. That statistic is an observation about retailer behaviour and survives everything above.

### 4.2 Daily FX companion series

Collected daily by deterministic scrapers from each retailer's own public storefront. Collector implementation detail is deliberately not published:

| Series | Source | Method |
|--------|--------|--------|
| **Official rate** | [bcv.org.ve](https://www.bcv.org.ve/) homepage widget | Direct scrape (BCV's TLS chain is chronically broken; per-host `verify=False`). 667.05 VES/USD at recon (value-date 2026-07-06). |
| **Parallel rate** | [Binance P2P](https://p2p.binance.com/) USDT/VES | Median of top-10 SELL-side ads (advertisers selling USDT for VES). 762.10 at recon. Median is robust to single-ad outliers. |
| **Cross-check** | [ve.dolarapi.com](https://ve.dolarapi.com/v1/dolares/paralelo) | Both oficial and paralelo legs; sanity check only, BCV scrape wins on conflict. |

**Which date a rate belongs to (2026-09-21).** The official rate is keyed to the date it is **in force**, never to the date it was scraped. BCV publishes for the next banking day, so its homepage widget shows the following Monday's rate on a Friday and a Saturday: the recon reading above is itself an example (667.05, read 2026-07-03, value-date 07-06). The row's own date field decides — `value_date` for the BCV scrape, `oficial.fechaActualizacion` for the dolarapi cross-check — and a date whose only print belongs to a later day carries **no fresh reading**: the index takes the last in-force rate by carry-forward and discloses it (`fx_carried_forward`), and a published claim about the official rate on such a date is **refused**, because BCV published no rate for it. 28 of 133 official rows are affected, all of them weekends or a Monday reading Tuesday's print; the restatement is in the header notice and the 2026-09-21 changelog entry.

**Headline FX metric: the official–parallel spread** = (parallel / official) − 1. At recon: 762.10/667.05 − 1 ≈ **+14.2%**. The spread is Venezuela's recurring macro fault line — its widening has historically preceded devaluation steps and inflation accelerations — and no one publishes it daily alongside a goods index.

---

## 5. Panel specification

- **Size at launch:** ~600–800 SKUs from a target universe of ~9,600 indexed Farmatodo SKUs (recon-verified), allocated food ~50% / personal care ~20% / household ~20% / OTC ~10% (matching `scripts/panel.py` targets).
- **Size today, and which count is which (2026-10-04).** The panel is described by two counts that are different sets, and a size is never published without saying which one it is. **Priced:** listings with a price on the newest day (`coverage.skus_priced` in the public bundle, the site footer; 45.997 on 2026-10-04). **In the index:** listings the headline is computed over that day, the §2.1 common set: in stock, not a §3 placeholder, in one of the four headline categories, with a price in the series' currency on both *t−1* and *t*, a §3.1 carry-forward included (`skus_in_index_ves` / `skus_in_index_usd`; 22.903 VES and 17.700 USD on 2026-10-04). Every listing in the common set is in the mean whether or not its price moved: an unchanged price is a relative of exactly 1, zero inflation, never dropped (1.694 of the 22.903 moved on 2026-10-04). Until this date the site quoted three numbers about none of these sets: ~2.400 (chart tooltip, written 2026-09-02), ~60.000 (the /metodologia lede, the row count of `panel.json` with retired listings in it) and the footer's priced count. Both pages now read the counts from the bundle.
- **Sources (current, 2026-09-17):** seven published: locatel, farmatodo, kromi, farmaciasaas, gama, multimax, plazas. **All seven are eligible** under §3.4's liveness gate and all seven returned rows on the latest date. Some Venezuelan retailers are not in the panel; the reasons are collection constraints, not editorial choices.
- **Hidden sources (2026-09-17):** a source can be COLLECTED but NOT PUBLISHED. Forum Supermayorista is the only one: its storefront serves a fraction of its catalogue, it has returned no rows since 2026-09-05, and two judgeable SKUs is not enough history for §3.4 to reach any verdict. It is collected and stored every morning while the collector is being fixed, and it enters neither the index nor any published chain count, chain list or diagnostic until its collection is complete and judgeable. A hidden source is not an excluded one: exclusion is a measured verdict about a catalogue and is published with its reason; hiding says only that there is not yet anything to judge. The list is `scripts/hidden_sources.py`, adding to or removing from it is a series break under §8, and every published enumerator routes through it so the counts cannot drift apart.
- **Collection window (measured 2026-09-21).** The day's collection is a **single morning pass**, scheduled daily and re-run in the afternoon only when a source failed. Over the 84 successful runs from 2026-07-03 to 2026-09-21 the finish time has a median of **08:57** local (VET), a 5th percentile of 08:38, and **75 of 84 (89,3%) complete before noon**; the remaining 9 are afternoon or evening repair runs after a partial morning collection. A price is therefore the shelf price as posted that morning in the great majority of cases, but the index makes **no same-hour guarantee** and published copy must not claim one: the certifiable statement is that prices are collected **daily**, and a claim that they are collected *each morning* is true of 89,3% of days, not of every day. Each run's own timestamp is in the `runs` table and every published figure is reproducible from it. *(Added 2026-09-21 after the editorial board refused "medidos cada mañana" as a claim with no published method behind it; open-work #146.)*
- **Stable identity:** a panel item is keyed on **source + source_id + barcode + normalized pack size**. Farmatodo's `id` and `barcode` fields are the anchors. A price observation only joins a series if all identity fields match; a changed pack size or barcode is a **new series**, never a continuation.
- **Selection criteria:** in-stock at selection time, non-promotional price available, category-mapped (one-time haiku pass, human-spot-checked), preference for high-frequency staples (harina de maíz, arroz, pasta, aceite, café, leche, azúcar, detergente, jabón, acetaminofén...) over long-tail items.
- **Refresh policy:**
  - **Monthly additions only.** New SKUs enter at a month boundary, contribute levels immediately and relatives after their first common day. Additions are listed in a public panel changelog.
  - **Never silent substitutions.** A discontinued SKU is retired (recorded with retirement date and reason) and any replacement enters as a new series. No splicing of "similar" products under one ID.
  - **Attrition gate:** the 30-day validation run requires < 10% daily dropout (README gate 1). If a category's live SKU count falls below ~70% of target, a documented mid-month addition round is allowed, flagged in the changelog.

---

## 6. Validation and backtest

**This section was rewritten on 2026-08-03, before BCV's July-2026 INPC print (expected ~Aug 5–15 per `docs/MONETIZATION.md` §5).** The original v0.1 draft (2026-07-03) anchored the gate on OVF. A direct retrieval attempt on 2026-08-03 (below) found OVF has not published a public monthly print since May 2025. The gate is re-anchored to BCV as the primary series *before* the number it will be tested against exists — the ordering is what makes this a pre-registration rather than a post-hoc fit. Per §8, this supersedes the v0.1 §6 outright; it is not a retroactive edit of a past test result (none has been run yet). A dated Panel Changelog entry recording this amendment is appended separately.

### 6.1 Reference series

**BCV INPC — primary.** Official monthly index, [bcv.org.ve/estadisticas/consumidor](https://www.bcv.org.ve/estadisticas/consumidor), resumed publishing March 2026 after a multi-year blackout, with a 4–13 day lag. Directly re-verified on 2026-08-03 against the live workbook [`4_5_3.xls`](https://www.bcv.org.ve/sites/default/files/precios_consumidor/4_5_3.xls) ("Cuadro N° I.1 — Índice Nacional de Precios al Consumidor, Clasificado por Grupos," base Dic. 2007 = 100) — downloaded and parsed row-by-row (not taken from a press summary), general index and Alimentos y bebidas no alcohólicas (food) group:

| 2026 | BCV general m/m | BCV *alimentos* m/m |
|---|---:|---:|
| Jan | 32.6% | 36.6% |
| Feb | 14.6% | 12.0% |
| Mar | 13.1% | 12.6% |
| Apr | 10.6% | 11.5% |
| May | 6.3% | 6.3% |
| Jun | 13.8% | 13.1% |

Jan–Jun accumulated (general): 129.8%. Jun y/y (general): 544.1%. **July is not yet in the workbook as of this retrieval** — confirms the July print has not published. The 2025 full-year figures already cited in the v0.1 draft (252.0% average annual variation, [static 2025 XLS](https://www.bcv.org.ve/sites/default/files/precios_consumidor/4_5_3_indice_y_variaciones_promedio_por_agrupaciones_2025_pub.xls)) remain a valid historical snapshot but are superseded by the live workbook for all 2026 comparisons going forward.

**OVF — demoted to secondary, if-available.** A direct retrieval pass (browser-grade, not search-engine-mediated, per the failure mode identified 2026-08-02) found:
- The site's own WordPress REST API ([`/wp-json/wp/v2/posts`](https://observatoriodefinanzas.com/wp-json/wp/v2/posts?per_page=50&orderby=date&order=desc), queried directly, two pages / 100 most recent posts) shows a clean monthly-inflation post cadence from 2023 through **[Inflación anualizada alcanza 229% en mayo](https://observatoriodefinanzas.com/inflacion-anualizada-alcanza-229-en-mayo/)**, published 2025-06-09, covering May-2025 data (the same 229%-annualized figure already cited in this document's original §6.1). **Zero inflation-content posts follow it** through the retrieval date. The only 2026 post in the feed (2026-06-29) is titled "Cara Main Poker Online Pemula: Strategi Aman di Nirwanapoker" — Indonesian-language online-poker/gambling SEO content, unrelated to OVF's mandate, indicating the WordPress install is compromised or its admin pipeline abandoned, not merely "slow."
- The `@observafinanzas` X account is now **protected** (tweets not publicly visible), confirmed via a [nitter.net mirror](https://nitter.net/observafinanzas) on 2026-08-03 ("This account's tweets are protected. Only confirmed followers have access"). This independently corroborates the 402/451 direct-fetch failures from the 2026-08-02 research pass and explains why no numeric 2026 print could be located by any method — it is not a scraping-tooling failure, the content is access-gated.

**Verdict: OVF has not published a publicly retrievable monthly print since May 2025 (14+ months as of this writing).** This is stated with high confidence given a direct, authoritative retrieval (the site's own content database via its REST API, not a cache or index) rather than inferred from a failed fetch. What cannot be ruled out: content shared privately with protected-X followers that never reaches the public site — if OVF publishes a number through that channel for a 2026 test month, it is logged per §6.2.5 as a secondary corroboration, never as the primary gate.

### 6.2 Protocol

For each of the first **2–3 BCV monthly releases** after launch (test months: July, August, September 2026 — July is month 1):

1. Compute the VPI monthly rate for the test month. **Month 1 (July) cannot use the §2.3 trailing-30-vs-prior-30 method** — that needs 60 days of history and the panel has 31 (base date 2026-07-03). July instead uses the calendar-window endpoint method already applied for the 2026-08-02 headline figure (index level 2026-07-31 vs. base 2026-07-03, a 29-day window since Jul 1–2 predate the panel) — published and logged everywhere as **"VPI-provisional,"** never silently presented as the §2.3 headline figure. From month 2 (August) onward, the true §2.3 trailing-30-vs-prior-30 method is used and the provisional label is dropped.
2. The Jul 1–2 gap (index base date is 2026-07-03) biases the July-provisional figure **down** by an estimated 0.2–0.3 pts relative to a true calendar-month print, per the observed daily rate. This is disclosed alongside the number, not corrected for — no upward adjustment is applied to the figure used in the pass/fail test in §6.3.
3. Compare primarily against **BCV's Alimentos y bebidas no alcohólicas group** (§6.1) — the cleanest goods-to-goods overlap and 67% of the VPI's own weight (§1.2) — with **BCV's índice general** reported alongside for context. Divergence from the general index is expected (§6.3) since it includes services the VPI does not cover.
4. Naive baseline (README gate 2, reinterpreted with BCV in place of OVF): the **prior month's BCV general print carried forward unchanged**. For the July test, that is June's 13.8%.
5. If OVF publishes a numeric print covering the test month before it closes, log directional and magnitude agreement with it as a secondary, disclosed data point (§6.1) — it does not itself pass or fail the gate.

### 6.3 Success criteria (pre-registered here, 2026-08-03 — before BCV's July print) — **WITHDRAWN AS A PUBLIC VERDICT, 2026-10-03**

> **Amendment (2026-10-03, per §8).** The VPI is no longer graded against the official print, in public or as a launch gate. The VPI measures posted shelf prices of formal online chains in bolívares, every day; the BCV INPC is a national monthly survey with its own basket, coverage and method (§0, §7). Two independent series that measure different things are expected to differ, in level and sometimes in direction, and an independent figure that differs from the official one is the reason the VPI exists, not a defect in it. A divergence is therefore reported as what it is, a comparison: both numbers, the gap, and where each series moved from the prior month, with no "hit", "miss", "fallamos", "acertamos" or band language. `verify_post.verdict_violations()` now refuses grading language on any post that sets the VPI beside the official print.
>
> What does not change: the three legs below are still computed every month by `verify_post.benchmark_verdict()` as an **internal diagnostic** (a persistent, unexplained gap is still something to investigate under §7), the figures already recorded for July–September 2026 stand, and the criteria text below is kept as written so the record of what was pre-registered is intact. What changes is that no outcome of these legs is published as a success or a failure, and none of them gates the product (`docs/MONETIZATION.md` §5).
>
> Why now: the September 2026 print (BCV *alimentos* 8,12%, VPI food 7,64%) moved in the opposite direction to the VPI (BCV 8,22 → 8,12, VPI 5,94 → 7,64) and the account published that as "Fallamos". Nothing was wrong with either series; the post framed an expected property of two different measurements as an error.


- **Directional agreement with BCV** in every test month that has a preceding VPI data point to call a direction from (acceleration/deceleration correctly signed). **Not assessable for the July 2026 test month** — the VPI has no observation before its 2026-07-03 base date, so there is no prior VPI month to compare a direction against. First assessable from the August 2026 test month (the July→August move in both series).
- **Credible magnitude:** the VPI monthly rate must fall within a band of roughly **±⅓ of the BCV print for the matched series** — primarily BCV *alimentos* (e.g., BCV food 15% → VPI 10–20%), with BCV general reported alongside — **and** be closer to the BCV print than the naive baseline (§6.2.4).
- **Beats naive baseline:** |VPI − BCV| < |naive baseline − BCV|.
- **OVF corroboration, not gate:** any OVF print located for a test month is logged for directional/magnitude agreement but carries no pass/fail weight, per its non-availability status (§6.1).
- **A miss is a finding, not smoothed away.** BCV divergence used to be an acceptable outcome under the old OVF-primary design (replicating the Argentina result). With BCV as the sole primary anchor, a magnitude or directional miss instead triggers the response already committed in `docs/MONETIZATION.md` §5: **paid launch does not proceed**; the miss is published honestly in the weekly note; the cause is diagnosed (goods/headline coverage gap, panel composition — e.g. the farmatodo price-freeze logged 2026-08-02, Caracas/formal-retail skew per §7.2, or a methodology bug) and disclosed either way; the gate re-runs on the next month's print.
- **What would falsify the index:** a magnitude miss that persists across all 2–3 test months, is not explained by a diagnosed and disclosed cause above, and does not narrow as the provisional-estimator caveats (§6.2.1–2) are resolved from month 2 onward. A single-month miss with an identified, disclosed cause is not, by itself, falsifying.
- **No silent re-specification.** Per §8, this criteria set is frozen as of 2026-08-03. Any change to the band width, the reference series, or the baseline definition after a BCV print is observed requires a dated, disclosed amendment stating what changed and why — never a quiet edit of this section once a test month's outcome is known.

**Implementation note (added 2026-09-07, no change to the criteria above).** These three criteria
are now executed in code rather than read by a person at print time:
`scripts/verify_post.benchmark_verdict(period)` recomputes the band, the naive-baseline comparison
and the direction test from `data/index.json` and `data/benchmarks.json`, and returns the single
word the account is permitted to publish about that month. `scripts/xnarrative.py` and
`scripts/xreply.py` both call `verify_post.verdict_violations()` before publishing, and a post
whose text asserts a verdict the rule does not support is blocked in the same way a post carrying
an unrecomputable number is.

The reason is a defect, recorded here because §6.3's own promise is what it broke: between
2026-09-04 and 2026-09-06 three published posts described the August test month as a miss
("quedamos 2,3 puntos por debajo"), every figure in them arithmetically exact, while the criteria
frozen on 2026-08-03 score that month a pass on all three legs (ours 5,94% against BCV *alimentos*
8,22%, band 5,48–10,96; naive baseline = July BCV *general* 19,90%, so |ours − BCV| = 2,28 <
11,68; both series decelerate July→August). The band width, the reference series and the baseline
definition are unchanged; what changed is that nothing now reports one leg of the rule as the
whole result. The correction is published under the §6.3 commitment that a miss publishes as
loudly as a hit, read in the direction it had never been read before.

The naive leg reads the prior month's BCV *general* print through
`benchmarks.json["official_inpc"].general_mom` when the summary `bcv` list carries no *general*
row for that month (it carries none for 2026-07). Same workbook, same field, per §6.2.4.

### 6.4 Robustness series

Published alongside the headline at backtest time: (a) equal-weighted four-category aggregate; (b) a food-heavy alternative weighting (food ≈ 80%, reflecting post-collapse expenditure reality); (c) promo-inclusive variant; (d) **food-only VPI subseries** (the `food_beverage` category alone), reported as the single most direct comparator to BCV's *alimentos* group per §6.2.3. If conclusions depend materially on the weighting choice, that fragility is disclosed. The known ~0.2–0.3 pt downward bias from the missing Jul 1–2 days (§6.2.2) is reported as a standing sensitivity note on every provisional-estimator figure.

### 6.5 Pre-registered figures for the July 2026 test month (locked 2026-08-10)

*Recorded **before** BCV's July print exists. Verified the same day, directly against the live workbook [`4_5_3.xls`](https://www.bcv.org.ve/sites/default/files/precios_consumidor/4_5_3.xls) via `scripts/benchmarks.py --dry-run`: **the July row is absent; the latest published month is June**. The ordering is what makes this a pre-registration and not a fit. The §6.3 criteria are **unchanged** — they remain frozen as of 2026-08-03. What changed is the index construction (§3.4, §4.1), which is disclosed in the header restatement notice and in the Panel Changelog, and which was decided on evidence about source behaviour that makes no reference to any BCV figure.*

**July-provisional, corrected construction** (index level 2026-07-31 vs. base 2026-07-03 — a 29-day window; the §6.2.1 "VPI-provisional" label and the §6.2.2 downward bias of ~0.2–0.3 pts from the missing Jul 1–2 both still apply):

| Series | July-provisional | Under the superseded construction |
|---|---:|---:|
| **VPI-VES headline** (4-cat, published weights) | **+13.13%** | +3.24% |
| **VPI food_beverage only** — §6.4(d), the §6.2.3 comparator to BCV *alimentos* | **+12.19%** | — |
| VPI-USD headline (native-USD only) | −1.72% | — |
| VPI-USD FX-derived (secondary, §4.1) | −0.23% | −8.0% |

**§6.4 robustness, same window:** equal-weighted 4-category **+13.97%**; food-heavy (food 80%) **+12.67%**; published weights **+13.13%**. The spread across weighting schemes is ~1.8 pts — the conclusion does not depend on the weighting choice.

**Per category:** food_beverage +12.19%, household +13.98%, otc_pharma +14.73%, personal_care +14.99%.

**Benchmarks in play:** BCV June general 13.8%, BCV June *alimentos* 13.1%. Naive baseline (§6.2.4) = **13.8%**. Official FX move over the same window: +11.93%.

**Pre-computed outcome against hypothetical July prints.** Published in full, including the cases where it fails, so that no post-print reading of this table can be selective. Test statistic is the food-only VPI (+12.19%) against BCV *alimentos*, band = ±⅓, naive = 13.8%:

| If BCV *alimentos* July = | §6.3 band | Band | Beats naive |
|---:|---|---|---|
| 6.0% | 4.0 – 8.0 | **FAIL** | pass |
| 8.0% | 5.3 – 10.7 | **FAIL** | pass |
| 10.0% | 6.7 – 13.3 | pass | pass |
| 12.0% | 8.0 – 16.0 | pass | pass |
| 13.1% | 8.7 – 17.5 | pass | **FAIL** |
| 15.0% | 10.0 – 20.0 | pass | **FAIL** |
| 18.0% | 12.0 – 24.0 | pass | **FAIL** |

Two things follow, both recorded now rather than argued after the fact:

1. **The band is passed for any print at or above ~9.2%** and failed below it. Under the superseded construction the band was failed at *every* plausible print — that reversal is the whole point of the restatement, and it is why the restatement had to happen before the print or not at all.
2. **The "beats naive baseline" criterion is the binding one, and it may well fail.** June printed 13.8%, so the naive baseline is strong; if July lands near June, a carry-forward beats us almost by construction. This is a known weakness of a one-month-old panel tested against a persistent series, it is **not** grounds for re-specification, and per §6.3 the criterion stands as written. If the gate fails on this leg, `docs/MONETIZATION.md` §5 applies unchanged: **paid launch does not proceed**, the miss is published, and the gate re-runs on August's print — which, per §6.2.1, is also the first month the true §2.3 trailing-30-vs-prior-30 estimator is available and the first month a directional test is assessable at all.

### 6.6 Movement claims — what the account is permitted to say the series is DOING (pre-registered 2026-09-07, **threshold amended 2026-09-08**, frozen under §8)

§6.3 scores a conclusion about a **closed benchmark month**. It says nothing about the sentence this account actually publishes most often, which is a claim about **now**: *"La inflación en Venezuela viene desacelerando."* That sentence went out on 2026-08-12, 2026-08-19, 2026-08-26 and 2026-09-06. Every figure in all four recomputed exactly and the truth gate certified all four. The defect was in the tense: the only test underneath the headline was `peak − now ≥ 8`, a distance from the July high that stays satisfied for months and says nothing about the current direction. The last of the four published at 12:40 with the monthly pace at 9,6% after six consecutive rising sessions (5,0% on 08-30 → 9,6%).

This section is the rule that sentence never had. It is written **before** the next movement post, it fixes the thresholds against the observed noise of the series rather than against any post the desk would like to keep, and per §8 it cannot be re-specified quietly.

**The series it scores.** The 30-day-annualised pace of §2.3, recomputed from `index.json` levels alone: `pace(t) = ((L(t)/L(t−7))^(30/7) − 1) × 100`. Sample as of this writing: **n = 60 observations, 2026-07-10 to 2026-09-07**, one regime break inside it (the July collapse). This is a small sample and every threshold below is calibrated on a 19-day subset of it. That is stated rather than hidden.

**Why it is not an endpoint test.** `pace` is already a 7-day window, so `pace(t)` and `pace(t−7)` rest on **disjoint** weeks and their difference is a second difference of log prices — the noisiest estimator available. Measured on the flat month of August 2026 (pace 5,60% on 08-01 and 5,28% on 08-31, i.e. no net move across 30 days), that difference had sd **1,66 pp** and reached **4,59 pp**, which is most of the level itself. Any single-day threshold on it below ~4,6 pp would have called that flat month a trend.

**The instrument.** Two adjacent, **non-overlapping** 7-observation means of the pace:

- `shift` = mean(pace over the last 7 observations) − mean(pace over the 7 before those). The trend leg.
- `recent` = pace(t) − pace(t−7). The **tense** leg, and it is the one that stops a true-but-stale sentence: on 2026-07-31 the smoothed shift was still −6,37 pp while the pace had already run from −1,33% to 5,27% in three days.

**The threshold — amended 2026-09-08, and the original is kept here because it is the evidence.** As first written, this section set a fixed **1,50 pp** bar, from the sd of the 19 `shift` values that flat August produced (sd **0,63 pp**, max **1,19 pp**), and called it ~2,4 sd out. That inference was wrong, and the rule carrying it returned **accelerating on 2026-08-04 → 08-10 — seven consecutive days inside the very month the calibration calls flat.** It fired on a round trip (pace 5,60% → 8,29% → back to 5,30%) and was still calling it a trend on 08-10, after the move had fully reversed. The calibration window opens 08-13, three days after that misfire ends, so nothing inside the window could have revealed it.

The error is the **sliding window, one level up from the one this section already warns about.** `shift` spans 21 index days, so `shift(t)` and `shift(t−1)` share 20 of them (lag-1 autocorrelation **0,802**). Scatter measured across adjacent dates is how much one window wobbles as a single day enters it — not how much the statistic varies across independent samples. Those 19 values are one or two independent draws wearing nineteen hats. Resampled properly the sampling sd is **2,16 pp**, which puts 1,50 pp **0,70 sd out, not 2,4** — a directional verdict on **25–56%** of trendless series against a target of at most 10%.

**The bar is therefore studentized: `|shift| ≥ 2 × SE`, where SE is bootstrapped from the trailing 30 days of this series' own returns, as they stood on the scoring date.** Daily volatility across the three months on record ranges **0,177–0,639 pp/day**, a factor of 3,6, so no fixed pp constant can be right in every regime: 1,50 pp is ~12 sd out in September's calm and a fifth of a sd out during the July collapse. The null is built with no trend in it by construction — resample the returns, rebuild the pace path, take the spread of the statistic across independent draws — with a **block bootstrap, mean block 3**, because daily returns are positively autocorrelated (lag-1 **+0,46**) and preserving that structure widens the null rather than flattering it. The seed and trial count are fixed (`TREND_NULL_SEED`, 4.000 draws) because a publication gate that answers differently on a re-run is not a gate. Measured false-positive rate of the amended rule: **0,8–7,0%** across six specifications (two regimes × blocks 1/3/7), worst case 7,0% against a 10% target — where the constant it replaces scored 25–56%.

**The rule.**

| Verdict | Condition |
|---|---|
| **decelerating** | `shift ≤ −2 × SE` **and** `recent < 0` |
| **accelerating** | `shift ≥ +2 × SE` **and** `recent > 0` |
| **flat** | anything else |
| *not scorable* | fewer than 14 pace observations, or fewer than 20 returns to draw the null from |

A movement word publishes **only when the rule returns that same word**. "Flat" is a real answer, not an abstention: on a day the rule scores flat, both *desacelerando* and *se acelera* are false and neither publishes. A series too short to score is **not** a licence to assert a direction — the word is refused, not waved through. Inflation up is bad and down is good, so the sign is never decoration; a post that gets the direction wrong gets the meaning wrong.

**What this rule costs, stated up front — and the amendment made it cost more, not less.** It is deliberately blind to slow trends. Late August *looks* like a decline by eye — the pace ran 7,23% on 08-17 down to 5,28% on 08-31 — and the rule scores it **flat**, because its shift is −0,34 pp, well inside what that same flat month produced on its own.

Under the 1,50 pp constant the rule certified one deceleration, 2026-07-23 → 07-29, the July collapse. **The studentized bar certifies nothing at all: it returns flat on every one of the 67 dates on record, the collapse included.** At 07-27 the shift is −14,95 pp and the trailing pool's own 2 × SE is **24,66 pp** — because that pool *is* the collapse. A move is only certifiable against the churn that preceded it, and this series' single big move **was** the churn; the series begins 2026-07-03, thirteen days before it, so there is no quiet stretch behind it to measure it against.

That is the honest read of a young, violent series, not the gate refusing to work. The bar moves with the regime and the directional verdicts remain reachable: across the 47 scorable dates it ranges from **4,09 pp** (2026-08-26) to **24,91 pp** (2026-07-25), a factor of six, and the +5,16 pp shift of 2026-08-06 — which its own regime judged against a 16,03 pp bar — would clear the bar on 17 of those 47 dates. Today's bar is **4,30 pp** against a shift of +2,21 pp. The account has published a movement word four times and all four were noise on the day they went out; a rule that would have permitted none of them is the correct trade for a desk whose failure mode is publishing a tense it cannot defend. A missed true statement costs one silent day; a published false one cost the account the only asset it has.

**Known limitation, logged rather than fixed here.** The trailing 30-day pool overlaps the 21 days `shift` is computed over, so a large move inflates the null it is tested against — the rule is hardest on exactly the moves most worth reporting. This is conservative in the safe direction and it is why the July collapse scores flat. A non-overlapping pool (days t−51 → t−21) removes the self-defeat but needs 51 days of history before it can score anything, which would make every post before 2026-08-23 unscorable. That is a separate amendment, on more data, not this one.

**Tense.** Only **present and present-progressive** assertions are scored. *"subieron 1,2% en la semana al 31 de agosto"* is a **quantity** about a closed window and §6.3's machinery — a declared, recomputed claim — already certifies it. *"viene desacelerando"* is a claim about the current window and nothing did. A sentence that names a calendar month or an ISO period is read as a closed-window statement and left to §6.3, so *"ambas series desaceleran de julio a agosto"* is a benchmark sentence, not a movement one.

**Oil production (`../vnzla-oil/data/estimate_ledger.json`).** The production read is monthly, revised, and reported three ways that disagree by 7–23%. Over 2018–2026 the three sources disagree on the **direction** of a 3-month change in 15 of 44 scorable months, and in those disagreeing months the median 3-month change never exceeded **39 kb/d** in absolute value. The rule is therefore: **all three sources must move the same way over three months, and the median move must clear 50 kb/d** — above every disagreeing month on record, and ~4,5% of Venezuela's current ~1.100 kb/d. Below that the word is *flat*. The series also **lags**: on 2026-09-07 the newest month all three cover is 2026-07. A present-tense claim about production is a claim about a month that closed weeks ago, so the rule **refuses outright** once the newest common month is more than 120 days past its close rather than pretending the reading is current. Production up is good, which is the opposite of the price series and is why the sign is scored, not just the size.

**Implementation.** `scripts/verify_post.trend_verdict(asof=None)` and `oil_trend_verdict(asof=None)` recompute the verdict from source and return each leg separately, on the same contract as `benchmark_verdict` — including returning `None` when a series cannot be scored. `interpretation_violations(tweets, asof=None)` classifies what a Spanish post asserts and blocks only when the assertion contradicts the recomputed word; it is silent when a post asserts nothing, and it fails closed on its own errors. It runs inside `scripts/xbrowser.gate()`, on every post from every caller, with no exemption and no way for a caller to switch it off. `placement_violations(tweets, parent, asof=None)` adds the reply clause below and runs in the same place; `xbrowser.post_reply()` **refuses** a reply to anyone else's tweet when the parent text was not supplied, because a check a caller can skip by forgetting an argument is not a check. `scripts/xnarrative.py::cand_pace_turn` calls `trend_verdict()` instead of testing itself; the drafter-local check it briefly carried is gone, because a drafter that grades its own tense is the pattern the chokepoint exists to abolish.

**Placement — added 2026-09-08, and it is the only part of §6.6 that reads text the account did not write.** Everything above scores the sentence being published. For an original that is the whole world; for a **reply** it is half of one, because what the reader takes away is the parent plus the reply. On 2026-09-08 a queued reply to @CronicaUno's *"La inflación **se desacelera** a 8,9 % en agosto"* typed no directional verb at all — it typed two levels, *"corre a 10,9% mensualizado, contra 37,9% en el pico de julio"*. Both numbers recomputed, the truth gate, §6.3 and the classifier above all returned clean, and the rule's verdict that day was **flat**. Answering a deceleration headline with now-vs-peak delivers the deceleration conclusion without asserting it: the same failure as the four `pace_turn` posts, with the tense supplied by the room instead of by the verb.

The rule, deliberately narrow because the reply channel is the account's best-performing surface: a reply is refused when **(i)** its parent asserts a direction this section does not license, **(ii)** the reply asserts a number — numbers are what corroborate — and **(iii)** the reply never states the desk's own reading, either by asserting the recomputed word or by **denying** the parent's (*"no como un descenso en curso"*). Stating the flat verdict out loud converts a silent corroboration into an explicit disagreement, which is what the reply actually posted that day does. A reply carrying no numbers, or standing under a parent that draws no conclusion, is untouched.

**Classifier repairs, same date, and they are part of the rule rather than plumbing.** The Spanish patterns that decide *what a post asserts* had three defects, found by the data seat in one pass and fixed in one change because repairing any one alone shifts which honest posts get refused: the vocabulary had never seen `descenso` / `caída` / a bare present-tense `baja`, so the identical assertion published clean in a synonym (**fails open**, and the up-side mirror `suben` was closed in the same change); the patterns were **negation-blind**, so *"lo marca plano, no como un descenso en curso"* was refused — the gate forbade the clearest way to disagree with a deceleration headline (**fails closed**); and the sentence splitter broke inside Spanish thousands groups, so *"En julio la producción llegó a 1.200 kb/d y viene bajando"* lost its month to a split and the same claim blocked or passed on whether its number carried a dot (**fails closed**). Repairing the splitter exposed a fourth: the closed-window exemption is now scoped to the **clause** that names the month, not to the whole sentence, so a present-progressive clause riding on the end of a historical one is still scored.

**Regression, re-run 2026-09-08 after the amendment.** The same 107-post scan blocks **the same 4 posts and no others** — the amendment does not widen the net over the published record, it raises the bar for what may be published next. Their shifts on those days were −0,15 / +0,81 / −0,61 / +1,30 pp against studentized bars of **7,13 / 6,43 / 4,09 / 4,50 pp**: flat on all four by a far wider margin than the 1,50 pp constant gave them. The positive control in `scripts/tests/test_publication_gate.py` fails if the bar ever stops responding to the data.

**Regression, run 2026-09-07 (the original, against the 1,50 pp constant).** Scored over every published post whose text is reachable (107: 104 tweet headlines from `data/xmetrics.json`, truncated to 70 characters, plus 13 full reply texts), each **as of its own publication date**, the rule blocks **4** — the four printings of the same `pace_turn` template listed at the top of this section, whose shifts on those days were −0,15 / +0,81 / −0,61 / +1,30 pp against a 1,50 pp threshold, i.e. flat on all four. **No post in the reachable record is blocked that was right.** The scan is headline-level, not full-text, and that bounds the claim rather than settling it.

### 6.7 Attribution and causation — pre-registered as unpublishable (2026-09-07, frozen under §8)

The question this section answers is the honest half of §6.6: *"una cadena empuja el índice", "los precios suben por la devaluación"*. Which of these can be reduced to a computable, recomputable quantity, and which cannot?

**None of them can, and so none of them publishes.** Written out rather than left implicit, because a sentence with no rule under it is a sentence this account does not say:

1. **Chain-level attribution.** The index publishes no per-source decomposition of the headline move. A chain's share of repricing *events* exists in the panel, but it is a different quantity from a contribution to the index and would read as the latter. **Does not publish.**
2. **Category-level attribution.** A contribution share would be `w_c × Δlog(index_c)`, and `data/weights.json` is a self-declared **`v2-placeholder`** — renormalised placeholders pending real expenditure shares (§1.1, §7.6). A contribution share computed on placeholder weights is not a certifiable attribution. **Does not publish as an attribution.** What may publish is the pair of quantities under it, each declared and recomputed separately: *"los alimentos se movieron X% y el índice Y%"* — two facts, no claim about which drove which.
3. **FX causation.** Establishing that a bolívar move *caused* a price move requires an identification strategy this project does not have, and the FX-lag statistic is withheld for weak sample. **Does not publish.**
4. **Policy causation** (sanctions, controls, reform). Out of scope on methodological neutrality grounds regardless of the statistics.

**Enforcement.** `interpretation_violations` blocks a causal construction (*por la devaluación*, *se debe a*, *a causa de*, *empuja/arrastra/impulsa*, *se explica por*, …) whenever it appears in a sentence that also names the index, prices or production. It is scoped to that co-occurrence so that an operational note — *"no publicó hoy debido a una recolección incompleta"* — is not swept up.

**This kills a live capability, and that is the finding, not a side effect.** The reply engine has no attribution branch today, so nothing currently drafted is lost; what is lost is the option to write one without first building the machinery — real expenditure weights for a category share, a per-source index decomposition for a chain share — and pre-registering the rule here before the first post uses it.

---

## 7. Known biases (publication language)

Stated in every public release, in roughly this form:

1. **Goods only — not headline inflation.** The VPI covers ~40–48% of the official INPC basket by weight: food, personal care, household consumables, OTC pharmacy. No services, no rent, no transport, no utilities, no education, no restaurants. Services inflation in Venezuela can diverge sharply from goods inflation; the VPI does not capture it.
2. **Caracas / formal-retail / online skew.** Prices come from the online storefronts of formal retail chains, whose store footprint and delivery coverage concentrate in Caracas and major cities. Informal markets, mercados municipales, and street vendors — where a large share of low-income consumption happens — are absent. The official INPC's own Caracas weights differ visibly from national ones (§1.1), and the interior consistently shows different (often higher) OVF regional inflation.
3. **Dollarized-retail skew.** Formal chains that maintain online storefronts are precisely the most dollarized segment of Venezuelan commerce. Their repricing behavior (frequent, indexed to the parallel rate) may lead or overstate economy-wide repricing of bolívar prices in the informal sector — or understate shortage-driven effective inflation.
4. **Narrow effective panel.** Eight chains are in the roster and none is currently excluded (§3.4), but measured movement is still dominated by locatel and gama, which together supply most of the VES repricing events — no eligibility rule fixes that, only more sources. Retailer-specific pricing policy at those two is not fully separable from market-wide movement. Disclosed on every release.
7. **Price rigidity is real and is excluded from the index, not from the record.** One major chain (Farmatodo) does not reprice its public catalog at all. Excluding it is a judgement that a frozen catalog is not a price signal (§3.4); a reader who believes stale posted prices are what consumers actually face would compute a materially lower number. The excluded series is published alongside the index so that reader can.
5. **Posted prices, not transaction prices.** No cash discounts, no haggling, no informal FX at the register. Prices include VAT as posted.
6. **Stale official weights.** Aggregation uses ENPF/2007-base weights (§1.1) for comparability with BCV/OVF; true current expenditure shares are food-heavier. Sensitivity series in §6.4 bounds the effect.

---

## 8. Versioning and change control

Any change to formulas, weights, thresholds, or panel policy gets: a version bump in this file's header, an entry in a `CHANGELOG` section appended below, and a note in the next public release. Silent revisions are prohibited — index history is only restated with an explicit, dated restatement notice.

**Collected but not published.** A chain in the roster can be collected every morning and left out of everything published, including every chain count. As of 2026-09-17 one chain is in that state: it returns only a fraction of its catalogue and has not left enough history to judge, so it enters neither the index nor any published count until its collection is complete and judgeable. Moving a source into or out of that state is a panel change and is logged in the Panel Changelog like any other.

**Since 2026-09-16, every text this project publishes passes a three-seat editorial review before it goes out** — Chief Editor, SEO Specialist and Data Scientist, all three must return PASS. It covers X posts and replies, Substack posts, the weekly report, blog posts and site copy, and it sits at the publishing chokepoint (`scripts/editorial/review.py`, wired into `xbrowser.gate()`, `weekly_report.main()` and `scripts/substack/publish.py`). The mechanical half of it refuses em dashes, emoji, exclamation marks, mixed decimal conventions and a missing attribution line; the Data Scientist seat independently recomputes every figure through `scripts/verify_post.py` and blocks on any number it cannot recompute. The gate fails closed, has no override flag, and never edits: it returns the fixes and the author resubmits. A weekly issue it refuses is written to `reports/weekly/_held/` instead of being published.

