feat(schemas): filter_diagnostics on get_products response#3508
Merged
Conversation
Adds optional filter_diagnostics block to get-products-response.json — non-fatal observability for the filter-not-fail empty-result UX gap that both pre-build expert reviewers independently flagged on PR #3472. Shape: { total_candidates, excluded_by: { <filter_name>: { count, values?, notes? } } }. Optional, additive, counts only (never product names) to avoid competitive-intel leakage on adjacent campaigns or seller inventory. Disambiguates "no inventory matches the brief" from "your required_metrics filter excluded everything" without breaking the filter-not-fail convention. Sellers still silently exclude unmatched products; this block is observability, not error reporting. Counting semantics intentionally loose — sellers vary on ANY-filter vs ONLY-this-filter counting. Spec documents counts as approximate triage signal, not exact accounting. Tightening would force every seller to implement the same filter-evaluation ordering, which AdCP shouldn't mandate. Closes #3482. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ed envelope, typed values Three review fixes per expert feedback on #3508: 1. Add semantics: "only" | "any" | "approximate" enum on the parent filter_diagnostics block. Both reviewers flagged that "approximate" alone isn't RFC-2119 enough — buyers writing automated triage will file tickets when arithmetic doesn't add up. Sellers self-declare how their counts are computed; buyers inspect before doing math. Recommended value is "only" (counts products that would have been included if not for this filter alone — the value that's actually useful for "which filter killed my result set"). 2. Close the per-filter detail envelope (additionalProperties: false). Sellers wanting to extend should use the response-level ext field; the inner shape stays sealed. 3. Constrain values to oneOf: [{type:string},{type:object}] instead of the open items: {}. Each filter's value type is known; buyers can at least know they're dealing with strings or objects without a per-filter switch statement. Plus: document that total_candidates and excluded_by are independently optional — sellers whose baseline catalog size is sensitive MAY emit excluded_by without total_candidates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds optional
filter_diagnosticsblock toget-products-response.json— non-fatal observability for the filter-not-fail empty-result UX gap that both pre-build expert reviewers independently flagged on PR #3472.The gap
Every
required_*filter in AdCP is silent-exclude semantics (matches OpenRTB / SSP capability discovery patterns). When the result list is empty or unexpectedly small, the buyer can't distinguish:required_metricsexcluded everything"required_geo_targetingexcluded everything"budget_rangehad no overlap"Today the buyer must blindly relax filters one at a time to find the unsatisfiable one.
Shape
{ "products": [], "filter_diagnostics": { "total_candidates": 47, "excluded_by": { "required_metrics": { "count": 31, "values": ["completed_views"] }, "required_geo_targeting": { "count": 9 }, "budget_range": { "count": 7 } } } }total_candidates: baseline before filters applied. May be sampled at large catalogs.excluded_by: keyed by filter property name; each value carriescount(required), optionalvalues(the filter values that contributed to exclusions), and optionalnotes.Design decisions
values(inputs that did the excluding, not the products that got excluded) is enough for triage without that leakage.Backwards compatibility
Optional and additive. Sellers that don't populate the field, and buyers that don't consume it, see no change. Sellers without per-filter instrumentation adopt incrementally; absence is conformant.
Test plan
npm run build:schemas— cleannpm run test:schemas— 7/7npm run test:examples— 34/34npm run test:json-schema— 255/255npm run test:composed— 32/32npm run typecheck— cleanCloses #3482.
🤖 Generated with Claude Code