Skip to content

[Impl] DOOH line items have no standardized fields for venue, screens, spot and loop length, share of voice or impressions per play #134

Open

Description

@jaanijuk

What did you build?

A mapping to an existing OMS / ad server / DSP data model

What you tried to express, and what happened
Filed during the extended public comment period, to November 10, 2026.

About our build.
We tested an unreleased seller-agent branch that maps Google Ad Manager (GAM) objects and seller-declared inventory to OpenProposal line items: IABTechLab/seller-agent, branch feat/openproposal-mapping at e77022e, with its line-item model in src/ad_seller/models/openproposal.py (Pydantic v2, default settings). We used synthetic data only, in IAB Australia's AAMP lab, on October 27, 2026. The archived request and response files for all six checks (D1 to D6), and the results summary, are byte-identical across three runs in two environments: two in a container and one under WSL2 on a separate machine, all with Python 3.12 and Pydantic 2.12.5.

Separately, we checked the D6 input against an independent, unofficial JSON Schema generated from the draft (details below). None of these results is an official conformance finding.

Section references are to spec/openproposal-3.0.md at 44dfaaf.

We tried to express a DOOH line item for one synthetic Melbourne screen: a 10-second spot in a 60-second loop, one-sixth share of voice, 14.2 audience impressions per play, a committed audience figure and an AUD 30 CPM.

Through this branch's mapping (D1 to D4).

  • D1: a synthetic GAM line item for a portrait DOOH screen network, with environmentType: BROWSER, came out as channel: display.
  • D2: the same line item as a 10-second video, with environmentType: VIDEO_PLAYER, came out as channel: video, with the branch's note "channel: VIDEO_PLAYER cannot be told apart from CTV; mapped to video". This tested a 10-second duration, not loop scheduling.
  • D3: a GAM ad unit for one screen came out as channel: display, with environments set to web_desktop and web_mobile.
  • D4: the branch's many-to-one builder combined two seller-declared screen objects marked dooh into one channel: dooh line item. Each screen became a properties.available entry with id, name and supported_formats. The line item had no environments, because the branch's channel-to-environment table has no entry for dooh.

These are observations of this branch's mapper output, not limits of the draft's channel or environments vocabulary.

Line-item test (D6). We validated one DOOH line item against the branch's LineItem model. The values exercise field handling; they aren't a validated commercial offer. The venue value and format identifier are synthetic test strings, not registered taxonomy or format values. MOVE appears as an illustrative measurement label; the fixture doesn't claim the figures were measured or endorsed by MOVE.

D6 input, exactly as archived
{
  "line_item_id": "wattlegum-cbd-portrait-network",
  "status": "published",
  "channel": "dooh",
  "properties": {
    "available": [
      {
        "id": "wg-mel-001",
        "name": "Collins St Tram Shelter 1",
        "domains": [],
        "supported_formats": [
          "dooh/portrait-1080x1920-10s"
        ]
      }
    ],
    "included": [
      "wg-mel-001"
    ]
  },
  "environments": {
    "available": [
      "dooh"
    ],
    "included": [
      "dooh"
    ]
  },
  "formats": {
    "available": [
      "dooh/portrait-1080x1920-10s"
    ],
    "included": [
      "dooh/portrait-1080x1920-10s"
    ]
  },
  "geo": {
    "allowed": [
      "AU-VIC"
    ]
  },
  "daypart": {
    "allowed": [
      "06:00-10:00",
      "15:00-19:00"
    ]
  },
  "frequency_cap": {
    "max": {
      "min": 1,
      "max": 1
    },
    "period": "day",
    "basis": "screen"
  },
  "pricing": [
    {
      "cost_method": "CPM",
      "gross_rate": 30.0,
      "currency": "AUD"
    }
  ],
  "committed_metrics": [
    {
      "metric": "audience_impacts",
      "value": 1200000,
      "basis": "MOVE"
    }
  ],
  "venue_type": "transit.tram_shelter",
  "loop_length_s": 60,
  "spot_length_s": 10,
  "share_of_voice": 0.1667,
  "impression_multiplier": {
    "multiplier": 14.2,
    "sourcetype": 1,
    "vendor": "MOVE"
  },
  "screen_location": {
    "lat": -37.8136,
    "lon": 144.9631
  }
}

The test ran:

li = LineItem.model_validate(DESK_LINE)            # DESK_LINE: the input above
dumped = li.model_dump(exclude_none=True)
dropped = [k for k in NO_HOME if k not in dumped]  # NO_HOME: the six DOOH fields
Check Observed in all three runs
Validation Accepted, without a validation error
venue_type, loop_length_s, spot_length_s, share_of_voice, impression_multiplier, screen_location All six missing from the output
Any of the six kept under ext No; the output had no ext
frequency_cap.basis: "screen" Kept as "screen"

Independent schema check (separate from the October 27 runs). We also checked the D6 input on October 28, with Python jsonschema 4.26.0, against an independent, unofficial JSON Schema generated from the draft: KonstantinMirin/openproposal-schema at 159a254, as-written build, published at openproposal.noetis.dev. Its author reports on generating it in #121. It isn't an IAB Tech Lab artifact, and where the draft leaves a shape or constraint unspecified, its rules are its author's choices. The schema rejected the input. Because it closes every object the draft defines, it treats each of the six DOOH fields as a property the line item doesn't define. It also rejects basis: "screen" and the committed metric (below). Its remaining failures come from its own format for daypart windows. So the same line item is trimmed without an error by one tool and rejected under the other's rules.

Update — October 29, 2026. The independent schema maintainer corrected its treatment of committed metric values. Our recheck at e3870c4 confirms that 1,200,000 no longer fails the quantity constraint; using example.com:audience_impacts also removes the metric-name failure. Other D6 validation failures remain. The October 28 result above records the earlier schema version. See [the follow-up verification](#134 (comment)).

The alternative DOOH design below also needs a qualification: derive share of voice from slot and loop length only for sov_basis: loop_time. For sov_basis: impressions, carry the agreed share explicitly across the eligible inventory pool, without requiring a fixed slot or loop.

None of the fields in § 5.2 to § 5.7 gives the terms that define a DOOH buy a standardized home:

Term What it tells a buyer Nearest field in the draft Gap
Venue type What kind of place the screen is in (rail platform, airport, shopping center) environments (dooh only); properties (id, name, domains, supported_formats) No standardized venue field or taxonomy
Screen location Where each screen is geo (§ 5.5), a targeting envelope of geo codes Says what a buyer may select, not where each screen is
Spot length Length of one play formats and specs (§ 5.4), through the format registry The registry has no dooh/* entries (open question 4)
Loop length How often the spot comes around None Not a creative spec, so the registry can't hold it
Share of voice Share of the loop the buyer gets None It's what a guaranteed DOOH package sells
Impressions per play Audience impressions one play represents None Without it, a DOOH CPM is ambiguous

The draft's descriptive fields could hold these terms as free text or ad hoc structure: specifications on the proposal (§ 4.2, "structured detail at media-kit depth") or content_detail on the line item (§ 5.2). Neither gives them agreed names or meaning, so the draft provides no shared interpretation for receiving agents.

Open question 4 (#5) notes that the format registry has no dooh/* entries. A registry entry can hold creative specs such as spot length, but the other five terms are inventory, commercial and measurement terms that a registry entry can't hold. Open question 7 (#8) asks sellers to map real inventory onto environments and report what doesn't fit. This is that report for DOOH: dooh fits as a value, but venue type, which tells one DOOH environment from another, has no standardized field in § 5.2.

§ 5.7 says a DOOH guarantee is "an audience-modelled metric for DOOH", but doesn't name the metric or say whether its value is a volume or a rate. D6 committed 1,200,000 audience_impacts. The branch's model kept it as given. The independent schema rejected both the name, because the only bare metric names it accepts are viewable_rate and completion_rate, and the value, because it treats every committed metric value as a fraction from 0 to 1. Nor does the line item have a standardized field for the per-play audience figure the metric is modeled from.

The price basis is ambiguous. Taking D6's own figures as arithmetic, not as a test result: at AUD 30 CPM with 14.2 audience impressions per play, one play costs AUD 0.426 if the CPM is per audience impression, as OpenRTB 2.6 bills with the imp.qty multiplier, or AUD 0.03 if it's read as a CPM per play. That's a 14.2-fold difference a buying agent can't resolve from the draft.

For PMP and open-market buys, the line item lands in OpenRTB 2.6 (§ 1.1), which already carries DOOH: the dooh object (§ 3.2.32) with venuetype and venuetypetax, imp.qty (§ 3.2.31) with multiplier, sourcetype and vendor, and imp.dt for when the play is expected. The proposal stage is the gap in that chain.

This builds on #5 and #8. Open change requests already draft much of the fix: #96 (venue type, screens, operating hours and loop, on the property object), #93 (delivery_type, including share_of_voice), #92 (what a play is, and the multiplier that turns plays into impressions) and #79 (share of voice and loop constraints). The evidence here supports them. How a receiver should treat fields it doesn't recognize is #33; a companion comment there presents this evidence (comment).

Suggested change

Much of this is drafted in open change requests: #96 puts venue type, screens with their location, operating hours and loop on the property object; #93 adds delivery_type, including share_of_voice; and #92 defines a play, says whose multiplier turns plays into impressions, and names impressions as the DOOH committed metric. Whichever shape the working group adopts, our evidence says it needs to:

  • give each of the six terms D6 lost a defined home: venue type with its taxonomy and version, screen location, loop length, spot length, share of voice, and impressions per play with its measurement source;
  • derive share of voice from the loop and slot terms, rather than carry it as a separate value that can disagree with them;
  • make the price basis explicit, since D6's two readings differ 14.2-fold.

#96, #93 and #92 cover most of this. Three points they leave open:

  1. Committed metric values. With impressions as the DOOH metric ([Change] § 5.6, § 5.7 — DOOH: define a play, and whose multiplier turns plays into impressions #92), § 5.7 should also say whether each metric's value is a rate or a count: the "metric and unit" the replies on OQ-3 — Cross-channel guarantee comparability is unsolved #4 ask for. The draft doesn't say, and the independent schema treats every committed value as a fraction from 0 to 1, so it rejected D6's 1,200,000.
  2. Units. [Change] § 5.2, § 5.5 — DOOH properties: venue type, screens, operating hours, and loop #96 now says which quantity is committed for each kind of DOOH product, and premesh's list on OQ-7 — environments[] needs validation against real inventory #8 names the units sellers use: impressions, plays, slots per loop and unit-periods. List them as core terms for availability.unit and commitment_bounds, so a line item that counts only plays, as [Change] § 5.6, § 5.7 — DOOH: define a play, and whose multiplier turns plays into impressions #92 allows, can state its quantity.
  3. Taxonomy versions. A venue type's taxonomy identifier comes from AdCOM's DOOH Venue Taxonomies list, as in [Change] § 5.2, § 5.5 — DOOH properties: venue type, screens, operating hours, and loop #96's { taxonomy, id }. That list identifies OpenOOH 1.0 (1) and 1.1 (5) but has no entry for OpenOOH 1.2.1 (February 2026). Version 1.2.1 added tram categories, such as 10803 Tram Station, which fit the tram-shelter screen we tested. In 1.1 the nearest category is 106 Train Stations, which is what OpenOOH's own 1.2-to-1.1 mapping uses. Say how a line item identifies a taxonomy version the AdCOM list doesn't cover yet, and that one version's identifier doesn't cover later versions.

For comparison with #96, we also offer an alternative shape: a channel-keyed dooh object on the line item, keyed to property ids, which leaves the property option the same for every channel. #96 covers operating hours, time zones, seller-allocated screens and sync groups, which this shape doesn't, and #92's impression_multiplier covers more of the multiplier than this shape's multiplier_source.

Alternative shape: a line-item dooh object

A dooh object on the line item, REQUIRED when channel is dooh and absent otherwise. Buyers select screens through properties (§ 5.2), as for any other channel; the dooh object adds what a property option can't hold. Each field carries one mutability marker (§ 3). Where OpenRTB 2.6 or AdCOM already has the concept, the field aligns with it.

Field Type and constraints Mutability Meaning Aligns with (OpenRTB 2.6 / AdCOM)
dooh.venue_taxonomy Integer, REQUIRED seller-set An explicit value from AdCOM's DOOH Venue Taxonomies list, identifying the taxonomy and version used for every screen's venue_type (for example, 5 = OpenOOH 1.1). One version's identifier doesn't cover later versions. REQUIRED rather than defaulted, because OpenRTB 2.6's default of 1 means OpenOOH 1.0 dooh.venuetypetax
dooh.screens Object, REQUIRED seller-set One entry per properties.available[] option, keyed by its id: { venue_type, geo: { lat, lon }, impressions_per_play }. venue_type (the venue's code in that taxonomy; for OpenOOH, its integer enumeration ID) and geo are REQUIRED. impressions_per_play is a number greater than 0, REQUIRED when any price or commitment is in audience impressions dooh.id, dooh.venuetype, device.geo, imp.qty.multiplier
dooh.multiplier_source Object seller-set { sourcetype, vendor }: who measured impressions_per_play, for every screen in the line item. REQUIRED when any screen has impressions_per_play. sourcetype is a value from AdCOM's DOOH Multiplier Measurement Source Types list imp.qty.sourcetype, imp.qty.vendor
dooh.loop_length_s Integer, greater than 0 seller-set Fixed-loop inventory only. Loop length in seconds, the same for every screen in the line item. Screens on different loops go in separate line items None
dooh.slot Object selectable Fixed-loop inventory only. available[] of { id, spot_length_s, spots_per_loop }, both integers greater than 0, with pricing per option and max_select: 1. Declaring pairs, not two separate lists, means a buyer can only pick combinations the seller sells None
dooh.share_of_voice Number, greater than 0 and at most 1 derived Fixed-loop inventory only. spot_length_s × spots_per_loop ÷ loop_length_s for the selected slot None

Rules for the object:

  • Fixed-loop inventory (sold as a share of a screen's loop): loop_length_s and slot are REQUIRED. dooh.slot.included[] holds exactly one slot id, and every slot option satisfies spot_length_s × spots_per_loop ≤ loop_length_s.
  • Inventory sold by the play, without a fixed loop (for example, programmatic DOOH): omit loop_length_s, slot and share_of_voice. Spot length comes from formats and specs (§ 5.4), and quantity from availability in plays (clarification 2).
  • Every properties.available[].id has an entry in dooh.screens, and dooh.screens has no other keys.
  • Receivers compute share_of_voice from the selected slot. If a document states a value, it MUST equal the computed value rounded to four decimal places; otherwise the receiver rejects the line item.

Design example, not a tested payload: a separate synthetic rail-platform line item that shows the proposed DOOH fields, not a complete line item. It uses OpenOOH 1.1 (AdCOM taxonomy identifier 5) and venue enumeration 10602 (Train Stations: Platform). The screen, slot ID, location, figures and vendor are illustrative, and the format ID is synthetic, since the format registry has no dooh/* entries yet (open question 4).

{
  "line_item_id": "wattlegum-rail-design-example",
  "status": "published",
  "channel": "dooh",
  "properties": {
    "available": [
      { "id": "wg-rail-0412", "name": "Synthetic rail platform screen", "domains": [],
        "supported_formats": ["dooh/portrait-1080x1920-10s"] }
    ],
    "included": ["wg-rail-0412"]
  },
  "dooh": {
    "venue_taxonomy": 5,
    "screens": {
      "wg-rail-0412": { "venue_type": 10602, "geo": { "lat": -37.81, "lon": 144.96 }, "impressions_per_play": 14.2 }
    },
    "multiplier_source": { "sourcetype": 1, "vendor": "example-measurement.com" },
    "loop_length_s": 60,
    "slot": {
      "available": [{ "id": "10s-x1", "spot_length_s": 10, "spots_per_loop": 1 }],
      "included": ["10s-x1"],
      "max_select": 1
    },
    "share_of_voice": 0.1667
  },
  "pricing": [{ "cost_method": "CPM", "gross_rate": 30.0, "currency": "AUD" }]
}

Why this shape:

  • One object keyed to the channel respects the no-inheritance rule (§ 2) and keeps DOOH fields off every other line item.
  • Buyers select screens through the existing properties field, which is where this branch already maps them (D4). The dooh object only adds what a property option can't hold.
  • The loop fields apply only where a seller sells a share of a fixed loop, so the object doesn't make loop scheduling mandatory for every DOOH buy.
  • Aligning the concepts with OpenRTB and AdCOM reduces ambiguity when mapping proposals to execution.
  • The new fields add no requestable fields, so the number that needs a round trip (Appendix B) stays at three.

What you did instead

Nothing interoperable. The implementation dropped the terms without an error. We could have written them as free text in a descriptive field, but nothing would tell another agent what they mean, and the draft defines no ext.

Fields involved

specifications (§ 4.2); channel (§ 5.1); properties, content_detail and environments (§ 5.2); formats and specs (§ 5.4); geo and frequency_cap (§ 5.5); pricing[].cost_method, availability.unit and commitment_bounds (§ 5.6); committed_metrics[] (§ 5.7).

Before you submit

  • I have read CONTRIBUTING.md and agree my contribution may be published under this repository's licence.
  • This contains no confidential or commercially sensitive information — no real rate cards, deal terms, client names or inventory data.

Activity

  1. KonstantinMirin commented on Oct 28, 2026

    @KonstantinMirin

    Thank you for testing our schema, @jaanijuk. Treating every committed value as a fraction was our mistake.

    Postindustria (IAB Tech Lab member) maintains the schema and has fixed it: see the corrected decision. A rate, such as viewable_rate, is a fraction from 0 to 1. A quantity, such as DOOH audience impacts, is a non-negative number. The metric name check stays: a name the draft doesn't list is a namespaced extension term, such as example.com:audience_impacts.

    Our mistake supports your point on #4: the draft should say whether each metric's value is a rate or a count, as #92's revision proposes.

    Analyzed and generated with the help of Claude, fully reviewed by me.

  2. premesh commented on Oct 28, 2026

    @premesh

    Thanks for testing this against a real mapping. Agree on giving each term a defined home and making the price basis explicit.

    One caveat on deriving share of voice from loop and slot. That holds for a fixed loop spot (sov_basis: loop_time in #93): a 10-second slot in a 60-second loop is one-sixth. It doesn't hold for rotary products, where share of voice is a share of impressions across a pool of screens (sov_basis: impressions) and the creative doesn't sit in a fixed slot. So I'd derive it when sov_basis is loop_time, and carry it as a stated value when sov_basis is impressions.

  3. jaanijuk commented on Oct 28, 2026

    @jaanijuk
    Author

    Thanks @KonstantinMirin and @premesh — both points help clarify the proposal.

    @KonstantinMirin, we rechecked the archived D6 input against the corrected independent schema at e3870c4, using Python jsonschema 4.26.0. The 1,200,000 value no longer fails the quantity constraint. With the metric renamed to example.com:audience_impacts, there are no committed-metric errors; bare audience_impacts still fails the name check. The tested zero, negative and rate-boundary cases also behave as expected. The full payload retains its other validation failures.

    We’ll retain the October 28 result as evidence of the earlier schema version and add this correction alongside it. The remaining specification question is how each metric declares its meaning and whether its value is a rate or quantity, as discussed on #4 and #92.

    @premesh, agreed: our derivation was limited to fixed-loop inventory. For sov_basis: loop_time, share can be derived from spot length, spots per loop and loop length. For sov_basis: impressions, it should be an explicitly agreed share across the eligible inventory pool, without requiring a fixed slot or loop.

    For that pooled share, could the definition also make explicit whether the denominator counts audience impressions or plays, which screens are eligible, and the flight/daypart scope? That would let agents distinguish the promised share from the delivered share.

  4. premesh commented on Oct 30, 2026

    @premesh

    @jaanijuk yes to all three, and most of it already has a home:

    Promised share is the offered term. Delivered share belongs in the settlement record, measured against the same denominator and pool, so the two are comparable.

  5. KonstantinMirin commented on Nov 4, 2026

    @KonstantinMirin

    Thanks, @jaanijuk and @premesh. We revised three filings from this thread and #8:

    Analyzed and generated with the help of Claude, fully reviewed by me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions