WattifUnderwriting reference

Methodology · IPMVP Option B

How Wattif performs IPMVP Option B Measurement and Verification

A reference for CMVP reviewers and customer finance teams. It sets out the Option B engine as built: the tables that hold the audit trail, the rule that gates each row, the two crons that produce the baseline and the verification windows, and the named failure modes that hold a row out of settlement.

One line: every dollar of verified savings is anchored to one sub-circuit, one boundary meter, one twenty-eight day baseline, and one append-only row signed by a named verifier.

Prepared by Wattif Technologies Pte. Ltd. · May 2026Singapore · Method version v1.3.0
01What the engine assumes

Working principles

One Option per measure family

Each measure type is classified into a single IPMVP family. The platform refuses to scope an intervention with an Option that does not match its family. Option B is reserved for measures that act on a contained sub-circuit.

The boundary meter is part of the record

Every Option B verification ledger row carries a snapshot of the meter that produced it: serial, device type, metering class, accuracy class, calibration certificate URL. The audit chain is portable without reading any other table.

Coverage gates every window

Both the baseline window and every measurement window run through the same coverage rule. Less than eighty percent telemetry coverage halts the write. A case row is raised; nothing settles until the next pass.

Append-only ledger

intervention_verifications is the financial primitive. Rows are immutable. Corrections supersede prior rows via supersedes_id. The mutable interventions cache is reconstructible from the latest ledger row at any time.

Method version stamped on every row

lib/mv/ipmvp.ts exports a METHOD_VERSION string. The validator that accepts a row writes that string into intervention_verifications.mv_method_version. A reviewer two years on can tell which rule set validated which row.

02Measure-family mapping

When Option B applies

Option B is the retrofit-isolation path. The platform allows it only when the measure under verification acts on a contained electrical scope that a boundary meter can isolate. The mapping is enforced by a pure validator in lib/mv/ipmvp.ts and is rerun both at scope time and at the moment a verification row is written.

Measure familyAllowed optionsRationale
sub_circuit_retrofitBBoundary meter on a contained electrical scope. Default Option B path.
whole_facility_retrofitC, DWhole-facility bill comparison or calibrated simulation. Option B refused at scope time.
tariff_or_contractCBill-driven; deterministic re-costing. Option B refused at scope time.
key_parameter_isolationASingle parameter measured, others estimated.
calibrated_simulationDReserved for measures that require a calibrated model.

An attempt to scope or verify Option B against any non-sub-circuit family returns option_not_allowed_for_measure_family with the allowed Options for that family in the message.

03What Option B requires

The boundary meter

Every Option B intervention is attached to exactly one boundary provision (device_provisions row), which is paired to exactly one device channel that feeds exactly one asset. The meter's metering class is recorded on the provision and copied onto every downstream record so the audit chain does not depend on the mutable provision row.

Revenue (Class 0.2 / 0.5S)

Default accuracy used in uncertainty calculations: ±0.5%. Tight enough to stand behind any savings margin.

Billing (Class 1)

Default accuracy used in uncertainty calculations: ±1.0%. Acceptable for Option B at any savings margin.

Screening (estimated)

Default accuracy used in uncertainty calculations: ±5.0%. Acceptable only when the savings-to-uncertainty ratio exceeds three. Annex F's distinguishable-from-noise threshold.

The screening-class path exists so that high-margin retrofits sub-metered by an existing Harvy2-with-VT install can settle without forcing a revenue meter onto every circuit. Marginal measures (savings below three times uncertainty) still require a billing-or-revenue meter; the validator blocks settlement otherwise.

04lib/mv/baseline-builder.ts

The baseline

The baseline is the reference against which every verification window is measured. One row in mv_baselines, written by the nightly cron the moment the boundary meter is paired to its asset and producing data. Re-deriving the baseline chains the new row to the old via supersedes_id; nothing is overwritten.

Baseline · 28 daysPre-deploymentDeploymentVerify · M1Verify · M2Verify · M3Each window writes one ledger row · monthly or quarterly cadenceSettlebaseline is the reference for every verification window
1

Window

Twenty-eight consecutive days ending at the intervention's deployment timestamp. The cron defaults to measuring_started_at, then now(), when the deployment timestamp is not yet set.

2

Integration

TDEngine returns a per-hour mean kW and sample count over the boundary asset's child table. Energy is summed as the sum of (mean kW × one hour) across the window.

3

Coverage

At or above ninety-five percent of expected samples is a clean pass. Between eighty and ninety-five percent is a pass with a widened uncertainty flag. Below eighty percent fails outright; the baseline is not written.

4

Gaps

Hours whose sample count drops below fifty percent of the expected per-hour cadence are flagged. Consecutive low-coverage hours collapse into a single gap span, persisted on mv_baselines.gaps.

5

Day-type profile

Weekday and weekend hour-of-day mean kW vectors (length twenty-four each) are stored on mv_baselines.day_type_profile. This is the structure verification windows normalise against.

6

Provenance

Builder version, baseline window, boundary asset, boundary provision, meter snapshot, coverage statistics, and a supersedes_id (when re-derived) are all persisted on the same row.

The builder refuses to write twice for the same intervention without an explicit supersedes_id. A re-baselined intervention chains the prior mv_baselines row via supersedes_id; both rows remain on disk.

05lib/mv/measurement.ts

The measurement window

Each verification reads the boundary meter for one cadence window (monthly or quarterly) ending at the cron tick. The same helper that built the baseline integrates the kWh and checks coverage. The window passes, passes with a flag, or fails outright.

fail · no row writtenpass with flagpass
0%80%95%100%

Coverage is samples observed divided by samples expected over the window. The same rule gates baseline and every verification window. The colour band reflects what lands in the database, not a tolerance the operator can override.

06lib/mv/uncertainty.ts · Annex F lite

Uncertainty

Each verification row carries an uncertainty band on the savings figure. The computation is a lite implementation of IPMVP Annex F: the two contributors that always exist (meter precision and sampling variability) are combined as the root-sum-square of independent terms and scaled to a confidence half-width.

Contributors

Meter precision (from the meter's accuracy class) and sampling variability (interval-level standard deviation across baseline and measurement windows). Combined via root-sum-square as independent contributors.

Confidence level

Default ninety percent, two-sided. z = 1.645. The platform also supports eighty and ninety-five percent on the same engine.

Reported as

Half-width of the savings confidence band, expressed as a fraction of baseline kWh. Directly comparable to verified_savings_pct in the screening-meter test.

Out of scope (this version)

Regression baseline residual error (Option D / weather normalisation) and t-distribution for small samples are not yet modelled. Sample counts below twenty are flagged on uncertainty_distribution so a reviewer sees the basis.

07Savings greater than uncertainty

The screening-meter doctrine

A screening-class boundary meter is acceptable, but only when the savings comfortably exceed the noise floor. The validator runs one extra test at ledger-write time: verified savings, as a fraction of baseline, must be at least three times the uncertainty band, also as a fraction of baseline. Anything below that ratio is refused. The error message names the two ways out: upgrade the meter to billing-grade, or extend the window to tighten uncertainty.

Test

|verified_savings_pct|  /  max(|uncertainty_pct|, 0.001)   ≥   3

Both terms are dimensionless fractions of baseline kWh. The constant three is the Annex F rule of thumb for distinguishable-from-noise.

08intervention_verifications

The append-only ledger

recordVerification (lib/mv/ledger.ts) is the only sanctioned writer of intervention_verifications. The table has no INSERT policy; only the service-role client can write. Every call follows the same six steps and produces exactly one immutable row.

1

Read intervention

Pulls the intervention's measure_type, currency, baseline reference, and current state.

2

Re-validate the method

The same validateMvMethod that gated the Scoped → Approved transition runs again here. Defense in depth: even if a scoped intervention slips through, an inconsistent (option, meter, margin) tuple cannot land in the ledger.

3

Resolve baseline and currency

Baseline window, kWh, cost and tCO2e default from the intervention row when the caller omits them. The ledger row carries the resolved values verbatim.

4

Insert the immutable row

One row in intervention_verifications. Carries the full boundary meter snapshot, baseline reference, measurement window, measured kWh and dollars, uncertainty band, verifier identity, outcome, and a stamp of METHOD_VERSION.

5

Stamp the writeback

interventions.verified_savings, verified_tco2e, verifier_id, verified_at, verification_method_version, verification_uncertainty_pct, measurement_period_start, and measurement_period_end are updated in place. The mutable cache is downstream of the ledger.

6

Transition the state

Verified outcomes transition Measuring → Verified. Disputed outcomes transition Measuring → Disputed. Under-performing outcomes (|Δ| between 10% and 25%) transition to Verified with the Wattif share recomputed by lib/mv/fail-verification: new share = original × max(0, 1 + Δ), floored at five percent. Revenue tracks delivered value without ever zeroing out.

The three writes are not wrapped in one database transaction. The order is chosen so a mid-flight failure is recoverable. Ledger first: it is the record everything else is rebuilt from, regardless of cache state. Writeback second: rebuildable from the ledger. State transition last: nobody sees Verified without a durable row behind it.

09M&V plan, ledger row, audit chain

How a reviewer sees it

Three screens. The M&V plan that the intervention is operating under, the ledger row produced at settlement, and the audit chain a reviewer walks when reconstructing a settled claim. Data is drawn from the Acme test facility; every value traces to a real row or registry default.

screen 1M&V plan and live measurement statusOption B, billing-class boundary meter, baseline built, day 18 of measurement, predicted savings tracking.
wattif.ai/facility/acme/interventions/iv-136c8884/mv-plan
InterventionsFCU occupancy setback · L3-EastM&V plan
measuringday 18 of measurement period

FCU occupancy setback · L3-East

next verification · 2026-07-01
ScopeCustomer viewM&V planVerifications

IPMVP option

B

sub-circuit retrofit isolation

Baseline

28 days

built · mvb-7c91e2 · 99.4% coverage

Cadence

monthly

first verification 2026-07-01

Boundary meter

provisiondp-3a82c1channelshelly_pm:chan2metering classbilling (Class 1)accuracy±1.0%calibration certview (PDF)last verified2026-05-15

Current period

measurement window2026-06-01 → presentkWh accumulated54.2coverage so far99.1%predicted savingsAUD 14.10method versionmv@1.4.0

Verification cron runs hourly. The first ledger row will land on 2026-07-01 at 02:00 SGT. Coverage below 80% halts the write and surfaces a Case.

screen 2The verified row, field by fieldAppend-only ledger entry showing every column a reviewer needs: window, kWh, tariff, savings, uncertainty band and its basis, meter class snapshot, verifier identity, row hash, method version.
wattif.ai/facility/acme/verifications/v-2026-06
Verificationsiv-136c8884June 2026
verifiedappend-only · hash-chained

Verification row · v-2026-06

Field-by-field row inspector
row_idvrf_0a4f7c92
intervention_idiv-136c8884
measurement_period2026-06-01 → 2026-06-30
ipmvp_optionB
mv_baseline_idmvb-7c91e2
adjusted_baseline_kwh142.0
reporting_period_kwh91.0
avoided_kwh51.0
tariff_aud_per_kwh0.2765
verified_savings_aud14.10
uncertainty_band_pct± 9.0
uncertainty_basisAnnex F lite (meter precision 1.0%, sampling 8.9%)
meter_class_snapshotbilling (Class 1)
coverage_pct99.4
verifier_kindagent
verifier_nameAccountant
mv_method_versionmv@1.4.0
row_hash0x4a92e8...
supersedes_idNULL

Screening-meter doctrine: N/A. Boundary meter is billing-class. The savings-to-uncertainty ratio test (savings_pct ÷ uncertainty_pct ≥ 3) does not apply to billing or revenue-class boundaries.

screen 3Audit chain readerFive reads, left to right. Each row inspectable. Reviewer reconstructs a settlement from the anchor to the state transition without leaving the page.
wattif.ai/facility/acme/interventions/iv-136c8884/audit
Interventionsiv-136c8884Audit chain

Audit chain · 5 reads

read order matches /methods/mv-option-b-method · §11
01interventions· the anchoriv-136c8884
facility_id, category_id, target_entities, expected_savings, state, ...
02mv_baselines· the referencemvb-7c91e2
window: 2026-05-04 → 2026-06-01 · kWh: 154.8 · coverage: 99.4% · meter_snapshot · day_type_profile · supersedes_id: NULL
03intervention_verifications· the append-only ledgervrf_0a4f7c92
v-2026-06 · adjusted_baseline_kwh: 142.0 · reporting_kwh: 91.0 · savings: AUD 14.10 · band: ±9% · verifier: Accountant
04interventions writeback· the mutable cacheiv-136c8884
verified_savings: 14.10 · verified_at: 2026-07-01 02:14:33 · method_version: mv@1.4.0 · matches ledger row
05state machine· the transition recordmeasuring → verified
emitted_at: 2026-07-01 02:14:33 · event_bus: 'measuring_to_verified' · listeners: 1 (lib/mv/ledger.ts)

The chain reads left-to-right at the database level. Each column is reconstructible from the column to its left, except the ledger (column 03), which is the source of truth.

10Intervention state machine

Where M&V sits in the lifecycle

M&V is not a side-channel. It is the gated revenue transition inside the intervention's own state machine. Diagnosis produces a Scoped intervention. The boundary install moves it through Approved, Deploying, and Live. The M&V engine moves it through Measuring to Verified.

Scoped
Approved
Deploying
Live
Measuring
Verified
M&V engine active Scoping and deploymentDisputed branches off Measuring; resolves back to Verified.
ScopedvalidateMvMethod runs at scope time. An Option B scope without a billing-or-revenue boundary meter is held until the meter class is set or a screening-meter savings margin is justified.
ApprovedBaseline period and IPMVP option are fixed. The customer counter-signs the baseline by reference to the mv_baselines row.
DeployingBoundary install completes. boundary_provision_id is wired to the device channel that feeds the boundary asset.
LiveEquipment is energised. The nightly mv-baseline-builder cron derives the twenty-eight day baseline and stamps mv_baseline_id.
MeasuringVerification cadence (monthly or quarterly) drives the hourly mv-verifier cron. Each elapsed cadence window assembles evidence and calls into the ledger writer.
VerifiedThe saving on the latest ledger row is the number the customer is shown and the number the proof report renders. Continuous interventions stay in Verified with subsequent ledger rows appended each cadence.
DisputedHeld for review. A successor ledger row with outcome 'verified' resolves the dispute and returns the intervention to Verified.
11Who signs off a row

Verifier identity

Every ledger row carries a verifier_kind, verifier_id, and verifier_display_name. The schema accommodates three kinds from day one. The resolver lives in lib/mv/verifier.ts and is the single point through which a verifier identity reaches the ledger writer.

Agent (today, default)

The Accountant runs the monthly P&L cron and signs off the Measuring → Verified write. lib/mv/verifier.ts is a registry; only Accountant.canVerify is true. The other six agents are registered so a future Engineer-led equipment retrofit verification lights up by flipping a flag, not by editing scattered allowlists.

Human (CMVP-credentialed)

resolveHumanVerifier reads user_profiles. The credential column on user_profiles is the planned site of the CMVP check; until it lands, the resolver returns identity without a credential gate.

Third party

Reserved for external CMVP providers. The mv_third_party_verifiers registry is intentionally not yet built; the resolver returns a hard error so a row cannot land with a fabricated identity.

12Two scheduled writers

The crons that drive the engine

The platform's M&V engine is two scheduled writers. Both are registered in vercel.json. Both are batched (cap fifty per run) so a single tick does not exhaust the function ceiling, and the backlog drains across subsequent ticks.

mv-baseline-builder

Nightly at 02:30 SGT. Selects interventions in deploying or live without mv_baseline_id, batch of fifty. Resolves facility, boundary asset, and twenty-eight day window. Builds the baseline; coverage failures write a case row and leave the row to retry on the next tick. Idempotent: the selector predicate excludes rows that already have a baseline.

mv-verifier

Hourly. Selects interventions in measuring whose cadence boundary has elapsed since the last verification or measuring_started_at. Reads tariff from facilities.tariff.energy_rate (mean of peak and off-peak when TOU). Computes uncertainty per Annex F lite. Writes the ledger row via recordVerification. Coverage failures and missing tariff each write a case row.

13When a row is not written

Failure modes

Every failure mode is named, returns a structured error code, and either writes a case row or returns to the cron for the next pass. The system never silently writes a degraded row.

coverage_fail

Less than eighty percent telemetry coverage in the window. No baseline written; no ledger row written. A case row is raised and the intervention waits for the next cron tick.

screening_meter_savings_margin_insufficient

Boundary meter is screening-class and the savings-to-uncertainty ratio is below three. The validator rejects at both scope time and ledger-write time. Resolution paths are documented in the validator's error message: upgrade the meter or extend the window.

option_not_allowed_for_measure_family

Scope time guard. Option B requested on a measure that does not classify as sub_circuit_retrofit. Resolution: change the measure type, or change the Option, before approval.

writeback_failed (ledger row already inserted)

The append-only row is canonical; the mutable writeback can be rebuilt by reading the latest ledger row for the intervention. The cron returns the partial failure so an operator can retry the writeback specifically.

under_performing

Verified savings fell between ten and twenty-five percent below expected. lib/mv/fail-verification.ts classifies the delta and recomputes the Wattif share before the ledger insert: new share = original × max(0, 1 + Δ), floored at five percent. The ledger row carries the signed delta_pct and the rationale string. Overperformance and undershoots within ten percent classify as 'verified' with no recompute.

14Audit chain

How a reviewer reconstructs a settlement

Any verified settlement can be reconstructed from five reads against the database, left to right.

interventions

the anchor

mv_baselines

the reference

intervention_verifications

append-only ledger

interventions writeback

cache, rebuildable

state machine

Verified / Disputed

Five reads, left to right. Every column is reconstructible from the column to its left, except the ledger, which is the source of truth.

  1. Find the intervention. interventions.id is the anchor.
  2. Read the baseline. mv_baselines (joined on intervention_id) carries the window, kWh, coverage statistics, gap list, day-type profile, boundary asset, boundary provision, meter snapshot, and builder version. supersedes_id chains prior baselines when one was redrawn.
  3. Read the verifications. intervention_verifications (joined on intervention_id, ordered by created_at) is append-only. Each row carries its own baseline reference (frozen at the time of the row), measurement window, measured kWh and dollars, uncertainty band, meter snapshot, verifier identity, outcome, and mv_method_version.
  4. Reconcile the cache. interventions.verified_savings, verified_at, verification_method_version, and verification_uncertainty_pct should match the latest non-superseded intervention_verifications row. Any drift is recoverable by replaying from the ledger.
  5. Confirm the state. The state machine transition that fired on the ledger write is logged on the interventions row and emitted on the event bus for downstream consumers (analytics, partner webhooks, notifications).
15Honest open work

What is not yet in scope

The methodology document is honest about its boundaries. The following items are deliberately deferred and are documented here so a reviewer is not surprised.

  • Continuous M&V dashboard. Per-intervention savings surface at /facility/[id]/savings is specified and not yet built. Today's interface is the report deliverable and the database.
  • TOU-weighted savings. The verifier reads facilities.tariff.energy_rate or the mean of peak and off-peak. A full time-of-use weighted savings computation lands when the cost engine integration is wired.
  • Regression baselines. Option D's calibrated simulation path, and the regression-residual contribution to uncertainty, are not yet implemented. The current Option B path uses a day-type profile, not a regression model.
  • Third-party verifier registry. The schema accommodates kind='third_party'; the mv_third_party_verifiers table and its CMVP credential references are not yet built.
  • External CMVP review of the methodology. Scheduled for Q3 2026. The credential letter from that review is the precondition for any Program Facility that references IPMVP compliance in its term sheet.
16Sources

Standards alignment

The Wattif Option B engine is implemented against the following standards. The Q3 2026 CMVP review will formally validate the mapping; until then the alignment claims here are the engineering team's, not an independent reviewer's.

IPMVP Core Concepts (Volume I, 2022). Options A through D defined; Annex F treats uncertainty.

Efficiency Valuation Organization

ASHRAE Guideline 14-2014: Measurement of Energy, Demand, and Water Savings.

ASHRAE

ISO 50047:2016: Energy savings determination in organisations.

ISO

FEMP M&V Guidelines 4.0: Measurement and Verification for Federal Performance Contracts.

U.S. Department of Energy