When automating the job of setting AdMob floors (eCPM thresholds) from a report, the first idea most people reach for is "hand the whole report to Gemini and have it answer the new floor for each group." I started down that path too, and stopped almost immediately. Letting an LLM do the threshold arithmetic and judgment makes that judgment unauditable. Setting a floor is an irreversible operation tied directly to revenue, and any mechanism where you cannot later reproduce "why this value" must not be put into operation.
I have run a solo app business since 2014 — 50 million cumulative downloads — and AdMob is a revenue source peaking around one million yen a month. Running that scale alone, alongside an art practice with 17 international awards, I review the floors of 42 mediation groups every month. What that experience tells me is that Gemini's structured output is strong not at "judging" but at "extracting." This article shows the design that confines structured output to the extraction step and separates the judgment into code, together with the actual decision rules.
Why you must not let the LLM do the judging
The floor decision rules themselves are, in fact, fully deterministic. The rules I use are these. On iOS, the floor is based on actual eCPM × 55%; if the ratio of current floor / eCPM exceeds 65%, lower it; if the ratio is under 40% and the match rate exceeds 95%, raise it; the middle (40–65%) holds. The minimum unit is $0.50. Android is a flat $0.50, not a ratio of actuals.
These rules complete with arithmetic and threshold comparisons alone. That is, there is no room for an LLM to "decide." And yet, passing the whole ruleset to an LLM via prompt produces three problems. First, output wavers at the threshold edges (e.g., when the ratio is exactly 65.0%). Second, even on the same input it may return subtly different values per run, breaking reproducibility. Third, and biggest: you cannot verify, from the output, the basis for a judgment like "hold because ratio is 64%." With code, anyone can trace the single line ratio = floor / ecpm; an LLM's internal reasoning cannot be traced.
A mis-set floor causes no-fill if too high (fill rate drops) and collapses eCPM if too low. In fact, I once had Android INT floors set excessively high relative to actuals, which capped the match rate in the low 70% range. To verify and fix this kind of "too strong / too weak" afterward, the judgment must be deterministic.
So what do you let Gemini do — only "extraction"
On the other hand, when you receive an AdMob report as CSV or pasted text, the data is astonishingly messy. Inconsistent group-name notation, currency symbols present or absent, a match rate written as "47.53%" one time and "0.4753" another, column order shifting with export settings. Tidying this messy input into uniformly typed rows is exactly where Gemini's structured output is overwhelmingly strong.
The trick here is to include no "decision result" whatsoever in the output schema. Define only the raw observations in the schema (group name, group ID, actual eCPM, match rate, current floor), and never put the new floor or a "raise/lower" judgment into it. You confine the LLM's responsibility to "messy input → typed observations."
import os
from google import genai
from pydantic import BaseModel
class GroupRow(BaseModel):
group_name: str
group_id: str
ecpm_usd: float # actual eCPM (observation only)
match_rate: float # normalized to 0.0-1.0
current_floor_usd: float
class ExtractedReport(BaseModel):
rows: list[GroupRow]
client = genai.Client(api_key=os.environ["YOUR_GEMINI_API_KEY"])
raw_report = open("admob_mediation_report.txt", encoding="utf-8").read()
resp = client.models.generate_content(
model="gemini-2.5-pro",
contents=(
"Extract the following AdMob mediation report, normalized to one row per group. "
"Convert match_rate to 0.0-1.0. Do NOT output any judgment or recommended value. "
"Observations only.\n\n" + raw_report
),
config={
"response_mime_type": "application/json",
"response_schema": ExtractedReport,
},
)
report: ExtractedReport = resp.parsedThe key is stating in the prompt, too, "Do NOT output any judgment or recommended value." Bound by the schema and limited in natural language as well, Gemini works stably as an "extractor."