A03 · Contract inventory forecasting and online allocation
Prerequisites: foundations 31–34; A02.
Evidence: explicitly constructed mechanism experiments, not real ad logs. This chapter does not claim industrial-system reproduction or online gains.
Learning order: predict → change one variable → run → explain.
English companion. All instructional Markdown is translated. Experiment code, Chinese language examples, execution counts, and saved outputs remain as in the source edition; historical output text may therefore be Chinese. The initial setup cell anchors the working directory and Python import path for this companion's deeper folder; it does not change the experiment. Saved outputs are historical evidence, not a new run. Run from the top after preparing the required data and models.
# English companions live one directory deeper; keep the original experiment code unchanged.
from pathlib import Path as _TranslationPath
import os as _translation_os
import sys as _translation_sys
_translation_root = next((p for p in [_TranslationPath.cwd(), *_TranslationPath.cwd().parents]
if (p / "reclab").is_dir()), None)
if _translation_root is None:
raise RuntimeError("Open this notebook from inside the Search Recommendation Ads Lab checkout.")
_translation_os.chdir(_translation_root)
if str(_translation_root) not in _translation_sys.path:
_translation_sys.path.insert(0, str(_translation_root))
Problem and input/output contract
Contract j has demand d_j; inventory t has eligibility edges to contracts. Offline allocation uses each inventory unit at most once and never exceeds contract demand. This example maximizes 10×fulfilled units. Forecast inventory is not guaranteed, and online decisions cannot see the future. Constructed flex inventory serves A or B, while onlyA serves only A. Giving flex to A first may consume B’s only chance. Enumeration provides a tiny offline reference, not a production solver.
Before running, predict which result the changed variable will affect and which invariants must hold.
from pathlib import Path
import sys
root=Path.cwd()
if not (root/'reclab').exists(): root=root.parent
if str(root) not in sys.path: sys.path.insert(0,str(root))
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from reclab import ads_curriculum as acRun the one-variable comparison
Inputs are small constructed examples visible in source. Output units and constraints are defined above. Run assertions to verify mathematics and accounting.
past_flex=np.array([2,2,2]);forecast=int(np.round(past_flex.mean()))
print('历史均值预测flex',forecast,'本期实际flex',1,'误差',1-forecast)
baseline=ac.contract_allocation(demand=(1,1))
shift=ac.contract_allocation(supply=('onlyA','flex','onlyA'),demand=(1,1))
print('flex先到',baseline);print('次序变化',shift)
assert baseline['offline'][0]==20 and baseline['greedy'][0]==10
short=ac.contract_allocation();print('预测不足与欠投',short)
assert sum(short['offline_shortfall'])==1Saved notebook output · not executed on this page
历史均值预测flex 2 本期实际flex 1 误差 -1
flex先到 {'offline': (20, (1, 0, -1)), 'greedy': (10, (0, -1, -1)), 'offline_shortfall': [0, 0], 'inventory_counts': {'flex': 1, 'onlyA': 2}}
次序变化 {'offline': (20, (0, 1, -1)), 'greedy': (20, (0, 1, -1)), 'offline_shortfall': [0, 0], 'inventory_counts': {'flex': 1, 'onlyA': 2}}
预测不足与欠投 {'offline': (20, (1, 0, -1)), 'greedy': (10, (0, -1, -1)), 'offline_shortfall': [0, 1], 'inventory_counts': {'flex': 1, 'onlyA': 2}}
Interpretation and limits
With inventory fixed, changing only arrival order changes greedy fulfillment value from 10 to 20; the offline optimum remains 20. Reserving flexible inventory for scarce opportunities matters. Default B demand is two but only one flex unit exists, so no algorithm can fulfill everything. Allocation cannot eliminate forecast errors.
Inspect source, then try an exercise
Add penalties for discarded inventory, opportunity revenue from auctions, and contract underdelivery. Enumerate different flex quantities, plot optimal fulfillment and shortfall, and explain why the offline optimum is only an upper bound for online decisions.
import inspect
print(inspect.getsource(ac.contract_allocation))Saved notebook output · not executed on this page
def contract_allocation(supply=('flex','onlyA','onlyA'), demand=(1,2)):
"""A accepts both types, B only flex. Reward 10 fulfilled, 0 discarded."""
eligible=lambda t,j: j==0 or (j==1 and t=='flex')
def score(assign):
count=[assign.count(j) for j in (0,1)]
return 10*sum(min(n,d) for n,d in zip(count,demand))
feasible=[]
for assign in product((-1,0,1),repeat=len(supply)):
if all(j==-1 or eligible(t,j) for t,j in zip(supply,assign)):
counts=[assign.count(j) for j in (0,1)]
if all(n<=d for n,d in zip(counts,demand)):
feasible.append((score(assign),assign))
best=max(feasible)
counts=[0,0]; greedy=[]
for t in supply:
options=[j for j in (0,1) if eligible(t,j) and counts[j]<demand[j]]
j=options[0] if options else -1
greedy.append(j)
if j>=0: counts[j]+=1
return {'offline':best,'greedy':(score(tuple(greedy)),tuple(greedy)),
'offline_shortfall':[max(0,d-best[1].count(j)) for j,d in enumerate(demand)],
'inventory_counts':{'flex':supply.count('flex'),'onlyA':supply.count('onlyA')}}
Sources and next step
A-CCR p. 37 and matching topics; inventory forecasting and online allocation are Lab extensions. This is an original Lab exercise, not a copy of the complete lecture text.
Chinese GitHub chapter · Authors’ RTB slides. The Chinese deck is not a complete modern advertising-engineering course. Historical descriptions of platforms are not presented as current market facts.
After the exercise, return to the ads learning path and continue to the next semantic unit.