Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,13 @@ jobs:
python-version: "3.12"
- name: Check every field in the body appears in Appendix A
run: python3 scripts/check_appendix.py

pricing-fixtures:
name: Pricing discussion fixtures
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python3 scripts/check_pricing_fixtures.py
117 changes: 117 additions & 0 deletions docs/proposals/pricing-commitment-arithmetic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Account for seller fees after selecting one price

Discussion wording for [#94](https://github.com/IABTechLab/OpenProposal/issues/94)
and [#115](https://github.com/IABTechLab/OpenProposal/issues/115), against draft
`44dfaaf618736219426c311ec997b6d37e94b950`. The arithmetic also exercises the
final-rate reading discussed in [#30](https://github.com/IABTechLab/OpenProposal/issues/30).
This is a proposed profile, not an adopted requirement or an OpenProposal wire
validator. The normative specification is unchanged.

## The fee interpretation changes the available media spend

#94 treats `pricing[]` entries as alternatives. #115 adds seller fees as extra
entries and includes them in `commitment.amount`. An extra alternative cannot
simultaneously be an additive fee. Summing all entries instead loses the choice
of cost method, currency and transaction mechanism.

At USD 25 CPM, a USD 50,000 commitment containing a USD 2,500 production fee
leaves USD 47,500 for media:

```
media_spend = 50,000 - 2,500 = 47,500 USD
derived_units = 47,500 / 25 * 1,000 = 1,900,000 impressions
```

Two million impressions would cost USD 50,000 before the fee, or USD 52,500
in total. Therefore #115's statement that `derived_units` is unaffected needs
revision if `amount` includes charges outside the unit rate.

## Proposed wording for sections 3.1, 5.6 and 5.6.1

This wording assumes adoption of the alternative-selection rule in #94.

`pricing[]` entries are alternative prices. A commitment MUST resolve to
exactly one entry under that rule. An implementation MUST NOT sum alternative
entries to compute the buyer's charge.

A selected option's `delta_flat` is a one-time seller charge for that line
item commitment. It MUST NOT enter the unit rate or become another pricing
alternative solely to make it additive. A cost already included in the unit
rate MUST NOT also be added as a flat charge. Each selected option that is not
already included contributes its charge once.

For `basis = spend`, `amount` is the total pre-tax amount payable to the seller
for this line item, including applicable seller flat charges. It excludes
amounts payable by the buyer to other parties. For a selected unit-rate method:

```
flat_charges = sum of applicable one-time seller charges
media_spend = commitment.amount - flat_charges
derived_units = media_spend / agreed_rate * rate_per
```

`rate_per` is 1,000 for CPM and CPMV, and 1 for CPC. The agreed rate MUST be
positive. Flat charges MUST NOT exceed the spend commitment. Zero remaining
media spend yields zero informative units and still has to satisfy any
applicable minimum-unit requirement.

For `basis = units`, the committed units remain binding:

```
media_spend = commitment.amount / rate_per * agreed_rate
seller_total = media_spend + flat_charges
```

`min_spend` and budget bounds apply to the seller total on the same pre-tax
basis. The selected entry's currency applies to rates, flat charges and budget
bounds. No currency conversion is implied. Informative unit estimates MUST NOT
be rounded in a way that changes the committed amount or authorizes extra spend.

## Arithmetic inputs do not establish acceptance

The adapter consumes resolved, field-qualified adjustments that are not already
included. Its provisional #30 calculation starts with the chosen `gross_rate`,
adds applicable `delta_cpm`, then multiplies by `(1 + delta_pct)` factors.
`0.20` means a 20 percent increase. Flat charges remain separate.

If the caller supplies an already accepted final-rate counter, that rate replaces
the composed rate without applying option rate deltas again. The caller must
have established acceptance for the bound proposal revision and selection.
`selection_key` is an opaque fixture binding, not an acceptance protocol.

The adapter does not evaluate floor eligibility. Every successful result carries
`floor_check: not_checked`. A posted floor is checked only for numeric shape and
nonnegativity. In particular, an explicitly accepted USD 19 rate against a posted
USD 20 floor remains USD 19 in the calculation. The floor does not veto acceptance
already established by the caller.

How a counter resolves automatically, how selected adjustments compose the floor,
and how seller assent is authenticated remain in #30. That discussion proposes
applying rate adjustments to the base floor. This profile neither adopts that
proposal nor silently compares a final rate with an uncomposed floor. A successful
calculation never authorizes a commitment, reservation or agreed order.

## Scope and reproduction

```
python3 scripts/check_pricing_fixtures.py
python3 scripts/check_pricing_fixtures.py --json
```

[`fixtures/pricing/cases.json`](../../fixtures/pricing/cases.json) contains fixed
expected outcomes independent of the calculator. Decimal strings and rational
outputs avoid binary floating point rounding. `"1000/3"` is an exact informative
quantity, not a proposed JSON wire type. Currency checks cover agreement and
shape, not ISO-4217 membership. The caller supplies a mechanism-eligible choice.

The profile implements CPM, CPMV and CPC only. It rejects unsupported tax-inclusive
inputs, #103 print adjustments and flat credits. It cannot infer a mandatory fee
that is not represented by the resolved options. General fee objects, tax amounts,
invoice rounding and cost-method extensions need separate decisions. Impression
bounds are checked only for CPM; clicks and viewable impressions are not silently
treated as the same quantity. Unsupported cases are not declarations that an
OpenProposal document is invalid.

If the fee wording is adopted, update the affected body sections, field
descriptions in Appendices A and B, and committed examples together. The executable
profile is supporting discussion evidence, not a replacement for those changes.
Loading