Skip to content
Open
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
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level1/portfolio_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ def create_portfolio_manager(
2. Delegate to channel specialists (Branding, Mobile, CTV, Performance)
3. Consolidate recommendations and ensure coherent strategy
4. Monitor overall campaign performance
5. Make real-time optimization decisions""",
5. Make real-time optimization decisions

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.manager_llm_model,
temperature=0.3,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level2/branding_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ def create_branding_agent(

You work closely with the Research Agent to discover inventory and the
Execution Agent to book placements. You report to the Portfolio Manager
and coordinate with other channel specialists for cohesive campaigns.""",
and coordinate with other channel specialists for cohesive campaigns.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.5,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level2/ctv_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ def create_ctv_agent(

You work with the Research Agent to discover premium CTV inventory and
the Execution Agent to book placements. You coordinate with Branding
for cohesive video strategies across screens.""",
for cohesive video strategies across screens.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.5,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level2/deal_library_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ def create_deal_library_agent(

When a deal needs to be booked for a campaign, you hand off to the appropriate
campaign flow. When you detect underperforming deals or better supply paths, you
propose changes that the campaign flow can execute.""",
propose changes that the campaign flow can execute.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.3,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level2/dsp_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ def create_dsp_agent(

You work closely with channel specialists (CTV, Branding, Performance, Mobile)
to understand inventory requirements and with the Execution Agent for
any direct booking needs outside of DSP activation.""",
any direct booking needs outside of DSP activation.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.5,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level2/linear_tv_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ def create_linear_tv_agent(
You work with the Research Agent to discover available linear TV inventory
and the Execution Agent to book placements. You coordinate with the CTV
Specialist for cross-screen TV strategies and with Branding for cohesive
video campaigns across linear and streaming.""",
video campaigns across linear and streaming.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.5,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level2/mobile_app_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ def create_mobile_app_agent(

You work closely with the Research Agent to find quality mobile inventory
and the Execution Agent to book campaigns. You coordinate with the
Performance Agent for retargeting strategies.""",
Performance Agent for retargeting strategies.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.5,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level2/performance_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ def create_performance_agent(
and the Execution Agent to book campaigns. You collaborate with the
Reporting Agent to analyze performance and identify optimization
opportunities. You coordinate with other specialists for full-funnel
strategies.""",
strategies.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.5,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level3/audience_planner_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ def create_audience_planner_agent(
- Portfolio Manager on campaign audience strategy
- Channel Specialists on channel-specific audience availability
- Research Agent on inventory discovery
- Sellers' Audience Validator agents via UCP exchange""",
- Sellers' Audience Validator agents via UCP exchange

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
verbose=verbose,
allow_delegation=False, # Makes final audience decisions
memory=True,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level3/execution_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ def create_execution_agent(

You work for the channel specialists and execute bookings only after
receiving approved recommendations. Always verify parameters before
executing any booking action.""",
executing any booking action.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.1,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level3/reporting_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ def create_reporting_agent(
- Are there any lines that need attention?

You work for the channel specialists and Portfolio Manager to provide
insights that inform optimization decisions.""",
insights that inform optimization decisions.

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.2,
Expand Down
7 changes: 6 additions & 1 deletion src/ad_buyer/agents/level3/research_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ def create_research_agent(
- Targeting requirements
- Flight dates
- Quality metrics
- Publisher reputation""",
- Publisher reputation

CRITICAL: NEVER estimate, assume, or fabricate CPM pricing. Only use prices
explicitly provided by sellers through quotes or media kits. If no pricing is
available from the seller, state clearly that pricing requires negotiation. Do
not fill in CPMs from market knowledge or training data.""",
llm=LLM(
model=settings.default_llm_model,
temperature=0.2,
Expand Down
57 changes: 51 additions & 6 deletions src/ad_buyer/booking/pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,57 @@
"""

from dataclasses import dataclass
from enum import Enum

from ..models.buyer_identity import AccessTier


class PricingSource(Enum):
"""Provenance of a pricing value.

Every PricingResult carries a pricing_source indicating where the
price came from. This prevents the system from silently using
fabricated CPMs.

Values:
SELLER_QUOTED: Price was provided by the seller (base price exists).
NEGOTIATED: Price was agreed via negotiation (target_cpm accepted).
UNAVAILABLE: No pricing is available (seller has not provided a price).
"""

SELLER_QUOTED = "seller_quoted"
NEGOTIATED = "negotiated"
UNAVAILABLE = "unavailable"


@dataclass
class PricingResult:
"""Result of a pricing calculation.

Attributes:
base_price: Original base price before any discounts.
None when pricing_source is UNAVAILABLE.
tier: The buyer's access tier.
tier_discount: Tier-based discount percentage applied.
volume_discount: Volume-based discount percentage applied.
tiered_price: Price after tier discount (before volume discount).
None when pricing_source is UNAVAILABLE.
final_price: Price after all discounts (tier + volume + negotiation).
None when pricing_source is UNAVAILABLE.
requested_volume: Impression volume used for volume discount calculation.
deal_type: Deal type requested (if any).
pricing_source: Provenance of the pricing value.
"""

base_price: float
base_price: float | None
tier: AccessTier
tier_discount: float
volume_discount: float
tiered_price: float
final_price: float
tiered_price: float | None
final_price: float | None
requested_volume: int | None = None
deal_type: str | None = None
pricing_source: PricingSource = PricingSource.SELLER_QUOTED


class PricingCalculator:
Expand Down Expand Up @@ -81,7 +105,7 @@ class PricingCalculator:

def calculate(
self,
base_price: float,
base_price: float | None,
tier: AccessTier,
tier_discount: float,
volume: int | None = None,
Expand All @@ -93,7 +117,9 @@ def calculate(
"""Calculate the final price after tier and volume discounts.

Args:
base_price: Base CPM price from the product.
base_price: Base CPM price from the product. When None,
the calculator refuses to compute and returns a result
with pricing_source=UNAVAILABLE.
tier: Buyer's access tier (public/seat/agency/advertiser).
tier_discount: Discount percentage for the tier (0-15).
volume: Requested impression volume (may unlock volume discounts).
Expand All @@ -103,8 +129,24 @@ def calculate(
deal_type: Deal type requested (for informational purposes).

Returns:
PricingResult with all pricing details.
PricingResult with all pricing details. When base_price is
None, all price fields are None and pricing_source is
UNAVAILABLE.
"""
# Guard: refuse to compute when base_price is None
if base_price is None:
return PricingResult(
base_price=None,
tier=tier,
tier_discount=tier_discount,
volume_discount=0.0,
tiered_price=None,
final_price=None,
requested_volume=volume,
deal_type=deal_type,
pricing_source=PricingSource.UNAVAILABLE,
)

# Step 1: Apply tier discount
tiered_price = base_price * (1 - tier_discount / 100)

Expand All @@ -118,13 +160,15 @@ def calculate(
final_price = tiered_price

# Step 4: Handle negotiation
pricing_source = PricingSource.SELLER_QUOTED
if target_cpm is not None and can_negotiate and negotiation_enabled:
floor_price = tiered_price * 0.90
if target_cpm >= floor_price:
final_price = target_cpm
else:
# Counter at floor
final_price = floor_price
pricing_source = PricingSource.NEGOTIATED

return PricingResult(
base_price=base_price,
Expand All @@ -135,6 +179,7 @@ def calculate(
final_price=final_price,
requested_volume=volume,
deal_type=deal_type,
pricing_source=pricing_source,
)

def _get_volume_discount(
Expand Down
17 changes: 11 additions & 6 deletions src/ad_buyer/booking/quote_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def get_pricing(
volume: int | None = None,
target_cpm: float | None = None,
deal_type: str | None = None,
) -> PricingResult:
) -> PricingResult | None:
"""Calculate pricing for a product based on buyer context.

Args:
Expand All @@ -66,11 +66,12 @@ def get_pricing(
deal_type: Deal type requested.

Returns:
PricingResult with all pricing details.
PricingResult with all pricing details, or None if no
valid pricing is available from the seller.
"""
base_price = product.get("basePrice", product.get("price", 0))
base_price = product.get("basePrice", product.get("price"))
if not isinstance(base_price, (int, float)):
base_price = 0
return None

tier = self._buyer_context.identity.get_access_tier()
tier_discount = self._buyer_context.identity.get_discount_percentage()
Expand All @@ -96,7 +97,7 @@ def build_deal_data(
flight_start: str | None = None,
flight_end: str | None = None,
target_cpm: float | None = None,
) -> dict[str, Any]:
) -> dict[str, Any] | None:
"""Build deal data dict for deal creation.

Calculates pricing and generates a Deal ID, returning
Expand All @@ -113,7 +114,8 @@ def build_deal_data(

Returns:
Dict with deal details including deal_id, pricing,
and activation instructions.
and activation instructions. Returns None if the
product has no valid pricing available.
"""
# Calculate pricing
pricing = self.get_pricing(
Expand All @@ -123,6 +125,9 @@ def build_deal_data(
deal_type=deal_type,
)

if pricing is None:
return None

# Generate deal ID
identity = self._buyer_context.identity
identity_seed = identity.agency_id or identity.seat_id or "public"
Expand Down
Loading
Loading