Module adcp.types.domains.media_buy.commercial_terms

Classes

class CancellationTerms (**data: Any)
Expand source code
class CancellationTerms(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    effective_at: AwareDatetime
    fee: Fee | None = None
    reason: Annotated[str | None, Field(max_length=500, min_length=1)] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config.

Set ADCP_STRICT_VALIDATION=1 in the environment ("1", "true", "yes", "on" are accepted) to flip the default to extra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.

Important

The env var is resolved once at module import time. Set it in your shell or CI environment before import adcp runs — mutating os.environ["ADCP_STRICT_VALIDATION"] after the first adcp import has no effect on already-imported model classes (they captured the policy at class-body evaluation).

Consumers who want per-model strict validation can override model_config on their subclass.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var effective_at : pydantic.types.AwareDatetime
var fee : Fee | None
var model_config
var reason : str | None

Inherited members

class CommercialTerms (**data: Any)
Expand source code
class CommercialTerms(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    source_feed_version: Annotated[
        str | None,
        Field(
            description='Wholesale product feed version against which direct published offers were accepted. Omitted when the seller authored terms outside a wholesale snapshot.',
            min_length=1,
        ),
    ] = None
    source_pricing_version: Annotated[
        str | None,
        Field(
            description='Pricing-layer version against which published rates were accepted.',
            min_length=1,
        ),
    ] = None
    brand: brand_key.BrandKey
    advertiser_industry: advertiser_industry_1.AdvertiserIndustry | None = None
    purchases: Annotated[
        list[product_purchase.ProductPurchase],
        Field(
            description='Exact canonical product, pricing, format, catalog, budget, targeting, bidding, optimization, resolved flight, measurement, and performance terms in the commercial envelope.',
            min_length=1,
        ),
    ]
    start_time: start_timing.StartTiming
    end_time: AwareDatetime
    total_budget: TotalBudget | None = None
    daily_budget_cap: Annotated[
        StrictFloat | None,
        Field(
            description='Hard aggregate daily spend ceiling accepted as part of these terms. It bounds total spend without creating purchase allocations.',
            ge=0.0,
        ),
    ] = None
    frequency_cap: Annotated[
        media_buy_frequency_cap.MediaBuyFrequencyCap | None,
        Field(
            description='Hard MediaBuy-level cap accepted as part of these terms. One counter aggregates exposures across every purchase; purchase targeting caps remain independently binding.'
        ),
    ] = None
    budget_cap_timezone: Annotated[
        str | None,
        Field(
            description='Shared IANA calendar-day boundary for aggregate and purchase daily caps in these terms.',
            min_length=1,
        ),
    ] = None
    budget_allocation: canonical_budget_allocation.CanonicalBudgetAllocation | None = None
    pacing: pacing_1.Pacing | None = None
    bidding: Annotated[
        bidding_policy.BiddingPolicy | None,
        Field(
            description="Media-buy bidding policy. A proposal answering criteria.outcome_target.cost_per states here the cost the seller can plan to, which the buyer adopts on acceptance: the requested strength, and an amount greater than or equal to the ask (the ask when the seller can forecast goal volume under it within the buyer's budget, otherwise the lowest such amount), denominated in the purchases' pricing currency, which equals cost_per.currency. It is an execution control, not an expected price; when the planned spend at that amount is below total_budget, forecast points carry metrics.spend. See outcome-target.json for goal binding."
        ),
    ] = None
    invoice_recipient: business_entity.BusinessEntity | None = None
    purchase_order_ref: Annotated[str | None, Field(max_length=255, min_length=1)] = None
    agency_estimate_number: Annotated[str | None, Field(max_length=100)] = None
    reporting_commitments: Annotated[
        list[ReportingCommitment] | None,
        Field(
            description='Binding reporting contract keyed by position in purchases. Amendments preserve prior entries and add metrics with effective_at; seller-assigned package IDs live in the execution binding, outside this digest.',
            min_length=1,
        ),
    ] = None
    cancellation_terms: CancellationTerms | None = None
    change_terms: Annotated[
        list[change_term.MediaBuyChangeTerm] | None,
        Field(
            description='Binding buyer change rights included in the commercial envelope and therefore covered by terms_digest. Entries are uniquely keyed by action. When this field is present, an omitted action is not a negotiated change right. Omission of the entire field means legacy-unspecified rights, not a prohibition.',
            min_length=1,
        ),
    ] = None

    @model_validator(mode='after')
    def _validate_change_term_set(self) -> CommercialTerms:
        if self.change_terms is None:
            return self
        actions = [term.action.value for term in self.change_terms]
        term_ids = [term.term_id for term in self.change_terms]
        if len(set(actions)) != len(actions):
            raise ValueError('change_terms must be uniquely keyed by action')
        if len(set(term_ids)) != len(term_ids):
            raise ValueError('change_terms term_id values must be unique')
        currencies = set()
        for purchase in self.purchases:
            if purchase.pricing is None:
                raise ValueError('accepted commercial-term purchases require resolved pricing')
            currencies.add(purchase.pricing.currency)
        for term in self.change_terms:
            if term.constraints is None:
                continue
            constraint = term.constraints
            if constraint.kind == 'budget':
                money_fields = (
                    constraint.max_delta_amount,
                    constraint.min_result_amount,
                    constraint.max_result_amount,
                )
                if any(money is not None and money.currency not in currencies for money in money_fields):
                    raise ValueError('change-term monetary constraint currency must match purchases')
                if (
                    constraint.min_result_amount is not None
                    and constraint.max_result_amount is not None
                    and constraint.min_result_amount.amount > constraint.max_result_amount.amount
                ):
                    raise ValueError('change-term minimum result exceeds maximum result')
            elif constraint.kind == 'flight':
                if (
                    constraint.earliest_result is not None
                    and constraint.latest_result is not None
                    and constraint.earliest_result > constraint.latest_result
                ):
                    raise ValueError('change-term earliest result exceeds latest result')
            elif constraint.kind == 'effective_timing' and (
                constraint.earliest_effective_at is not None
                and constraint.latest_effective_at is not None
                and constraint.earliest_effective_at > constraint.latest_effective_at
            ):
                raise ValueError('change-term earliest effective time exceeds latest time')
        return self

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config.

Set ADCP_STRICT_VALIDATION=1 in the environment ("1", "true", "yes", "on" are accepted) to flip the default to extra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.

Important

The env var is resolved once at module import time. Set it in your shell or CI environment before import adcp runs — mutating os.environ["ADCP_STRICT_VALIDATION"] after the first adcp import has no effect on already-imported model classes (they captured the policy at class-body evaluation).

Consumers who want per-model strict validation can override model_config on their subclass.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var advertiser_industry : AdvertiserIndustry | None
var agency_estimate_number : str | None
var bidding : BiddingPolicy | None
var brand : BrandKey
var budget_allocation : CanonicalBudgetAllocation1 | CanonicalBudgetAllocation2 | None
var budget_cap_timezone : str | None
var cancellation_terms : CancellationTerms | None
var change_terms : list[MediaBuyChangeTerm] | None
var daily_budget_cap : float | None
var end_time : pydantic.types.AwareDatetime
var frequency_cap : MediaBuyFrequencyCap | None
var invoice_recipient : BusinessEntity | None
var model_config
var pacing : Pacing | None
var purchase_order_ref : str | None
var purchases : list[ProductPurchase]
var reporting_commitments : list[ReportingCommitment] | None
var source_feed_version : str | None
var source_pricing_version : str | None
var start_time : Literal['asap'] | pydantic.types.AwareDatetime
var total_budget : TotalBudget | None

Inherited members

class Fee (**data: Any)
Expand source code
class Fee(TotalBudget):
    pass

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config.

Set ADCP_STRICT_VALIDATION=1 in the environment ("1", "true", "yes", "on" are accepted) to flip the default to extra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.

Important

The env var is resolved once at module import time. Set it in your shell or CI environment before import adcp runs — mutating os.environ["ADCP_STRICT_VALIDATION"] after the first adcp import has no effect on already-imported model classes (they captured the policy at class-body evaluation).

Consumers who want per-model strict validation can override model_config on their subclass.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config

Inherited members

class ReportingCommitment (**data: Any)
Expand source code
class ReportingCommitment(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    purchase_index: Annotated[SchemaInt, Field(ge=0)]
    metrics: Annotated[
        list[canonical_reporting_commitment.CanonicalReportingCommitment], Field(min_length=1)
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config.

Set ADCP_STRICT_VALIDATION=1 in the environment ("1", "true", "yes", "on" are accepted) to flip the default to extra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.

Important

The env var is resolved once at module import time. Set it in your shell or CI environment before import adcp runs — mutating os.environ["ADCP_STRICT_VALIDATION"] after the first adcp import has no effect on already-imported model classes (they captured the policy at class-body evaluation).

Consumers who want per-model strict validation can override model_config on their subclass.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var metrics : list[CanonicalReportingCommitment1 | CanonicalReportingCommitment2]
var model_config
var purchase_index : int

Inherited members

class TotalBudget (**data: Any)
Expand source code
class TotalBudget(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    amount: Annotated[StrictFloat, Field(ge=0.0)]
    currency: Annotated[str, Field(pattern='^[A-Z]{3}$')]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config.

Set ADCP_STRICT_VALIDATION=1 in the environment ("1", "true", "yes", "on" are accepted) to flip the default to extra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.

Important

The env var is resolved once at module import time. Set it in your shell or CI environment before import adcp runs — mutating os.environ["ADCP_STRICT_VALIDATION"] after the first adcp import has no effect on already-imported model classes (they captured the policy at class-body evaluation).

Consumers who want per-model strict validation can override model_config on their subclass.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Subclasses

Class variables

var amount : float
var currency : str
var model_config

Inherited members