boncourant

invoice.reconstruct-from-coefficients — explanation


Goal


Reproduce a supplier's invoice math from two index reads (start, end) using

the regulatory profile coefficients published by Enedis. Cross-check our

ranker so the optimization we surface matches what the customer will actually

pay.


Inputs


```

{

offer: TariffOffer,

// Half-hourly kWh series for the billing period. If unavailable, the

// caller reconstructs it from index reads × normalized profile

// coefficients (which sum to 1 over the period).

halfHourSeries: number[],

// Same-length boolean mask — required for HP/HC offers, ignored otherwise.

hpMask?: boolean[],

// Days in the billing period — used to prorate the annual abonnement.

billingDays: number,

// Optional supplemental charges

turpeEur?: number,

taxesEur?: number,

}

```


Algorithm


  • Energy costpriceHalfHourSeries(offer, halfHourSeries, hpMask)
  • from lib/tariff/price-halfhour.ts. Same function the ranker calls.

  • Abonnementoffer.abonnement_annual_eur × billingDays / 365.
  • TURPE — passed in (zero by default; production callers compute it
  • from lib/methodology/methods/turpe/).

  • Taxes — passed in (zero by default).
  • Total — sum of the above.
  • delta_vs_ranker_eur — the ranker's annualized year-2 cost for the
  • same offer + same total kWh, minus our (energy + abo) reconstruction

    annualized. Used by tests to guard the two paths.


    Why a separate method?


    The ranker works at year-aggregate granularity (annual kWh + estimated HP

    fraction). This method works at half-hour granularity. Both share

    priceHalfHourSeries / priceAggregateEnergy from

    lib/tariff/price-halfhour.ts — different inputs, same arithmetic.


    Golden bills


    Three anonymized bills under ./__fixtures__/golden-{1,2,3}/ ship with the

    method. Each fixture provides the half-hour series, the offer, the billing

    period, and the observed invoice total. The test asserts the reconstructed

    total is within €1 of the observed total and that

    |delta_vs_ranker_eur| ≤ 1 on the annualized view.