Screen Events
screen_eventsFinds and ranks upcoming events by price, demand and inventory, using a predicate over the columns list_screen_columns names; the predicate input carries the grammar and the column rules. Use it for "which events" questions; use search_events to find one event by name and get_event_analytics for every number on one event.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | ||
| sort | No | Server-side ranking: sorts the FULL match set by `col` (NULLS LAST, event_id tiebreak) so the page is the true top-N, not an event_id slice. Omit for default event_id ASC. Fast on the ranking primitives (`ea.price_self_z_7d_pct_rank_subcat_tte`, `ea.price_self_z_7d_xs_z_subcat_tte`, `ea.inventory_velocity_1d_xs_z_subcat_tte`, `ea.pace_z_3d_vs_14d_xs_z_subcat_tte`); sorting other columns over a broad predicate may time out; narrow the predicate. | |
| count | No | Compute the total number of matches (up to 10,000, surfaced as `total`; `countCapped` true means at least that many). Default false: counting roughly DOUBLES query cost and is usually not needed to answer with a page of events. Set true ONLY when the user's ask is about how many events match ("how many…", "count the…") or you must paginate exhaustively. When false, `total`, `totalPages` and `countCapped` are null. | |
| limit | No | Rows per page. A page too large for one tool result comes back as the first rows plus `meta.truncated` and `meta.total_rows`; narrow the predicate, project fewer columns, or page, rather than repeating the call. | |
| columns | No | Snapshot columns to return, as namespaced refs (e.g. ["ea.median_price_current","e.event_name"]). A bare namespace ("ea") returns that whole bag; ["*"] returns the full ea+e+pa snapshot. Omit for a compact default (identity + headline signals) that keeps the payload small. Request more only when you need them. Names must come from list_screen_columns, spelled exactly; an unknown one is REJECTED by name, not dropped, so fix it and call again. EVERY ROW IS ALREADY LINKED: `event_id` and `event_url` are on every row already and do not need projecting. | |
| predicate | No | A pure boolean FILTER, not an ordering. Omit or pass {} to screen the whole live universe. To surface the most interesting rows first, use the `sort` parameter; the predicate cannot express ordering. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: "ea." = event_analytics, "e." = events, "pa." = performer_analytics, "p." = performers (p.name, the act the row is about), "v." = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {"all":[{"col":"ea.price_self_z_7d_xs_z_subcat_tte","op":">=","val":2},{"col":"ea.days_to_event","op":"between","val":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {"col":"ea.last_price_snapshot_date","op":">=","val":{"days_ago":1}} means "priced within the last day" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {"days_ago": N} value form is valid on any date/timestamp column. A weekend is {"col":"e.local_date","op":"between","val":{"weekend":"this","tz":"America/New_York"}} ("next" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error. A window of event dates from today ('the next week', 'the next 30 days') is ea.days_to_event between [0, N]. NON-ADMISSION SKUs ARE ALWAYS EXCLUDED: parking and shuttle / no-admission passes (sub_category_id 75, or an event_name matching '%parking%') never appear in a screen, and a screen that asks for sub_category_id 75 alone is refused. Also screenable: marketplace demand levels/changes (ea.interest*, ea.sales*; young series, gate freshness on ea.demand_asof_date), PERFORMER-grain demand (pa.interest*, pa.sales*, pa.demand_asof_date; a separate performer-resource reading, NOT a rollup of that performer's events; filtering on it selects every event of a matching performer, so use it to find hot performers and ea.demand_* to pick among their events; gate on pa.demand_asof_date), performer popularity-rank momentum (pa.popularity_rank*; positive change = climbing), and sale-timing/ops columns: e.presale1_date and e.onsale_date (timestamptz; NULL = unknown/TBD and NULL never matches a range predicate; negative days_ago means the FUTURE: 'on sale in the next 7 days' is {"all":[{"col":"e.onsale_date","op":">=","val":{"days_ago":0}},{"col":"e.onsale_date","op":"<=","val":{"days_ago":-7}}]}) and e.monitoring_priority (monitoring priority from P1, highest, to P10, lowest, stored as 1 to 10). Count changes come as counts (ea.listings_delta_*, ea.tickets_delta_*) AND as percents (ea.listings_pct_1d/_7d, ea.tickets_pct_1d/_7d): 'listings down 10%' is ea.listings_pct_7d <= -10, never a count of 10. ea.median_price_7d_ago is the median of 7 days earlier, a past level. ea.tm_tickets_pct_3d is the primary market's 3-day percent change (-100 = none left after some 3 days ago), and e.tm_sells is true when TM sells the event as its own primary sale ('not on TM' is false); both are primary-market columns. ONLY EVENTS DATED TODAY OR LATER (UTC) ARE SCREENED, whatever active_only says: a screen whose e.local_date range ended two or more days ago is refused. ABSORPTION IS DELISTING, NOT SALES: ea.absorption_count_1d/7d, ea.absorption_rate_7d and ea.listings_added_count_* count listings that LEFT (or joined) the marketplace: a broker withdrawal, a move to another exchange, or an expiry counts the same as a sale. NEVER report them as tickets bought or as sales volume; say 'absorbed' / 'delisted'. A predicate or sort on an absorption column adds ea.absorption_data_quality, ea.absorption_grain and ea.absorption_asof_date to the projection; quote the quality and the as-of day with any absorption number. The as-of day is yesterday (UTC) for every covered event; an event without seat block ledger coverage is NULL in every absorption column, never 0. Quality ranges from 0 to 1 and reflects observation coverage and listing-tracking quality. Lower values warrant more caution when comparing activity. Published counts can decrease after revisions and can be negative when returns exceed departures. | |
| active_only | No | Restrict to the active event universe (status='active'). Only events dated today or later are screened either way; false adds inactive events in that window, whose rows can be frozen at the last computation or hollow (partial_row true: treat their nulls as unknown). Default true: inactive/closed events are stale and should not rank as live signals. Set false to include them. | |
| apply_floor | No | Apply the economic floor (listings_current >= 25 AND median_price_current >= 40). Default true. Set false only when explicitly screening the raw universe. | |
| diagnostics | No | Explain the result set. EMPTY RESULTS ARE EXPLAINED, NOT GUESSED AT: the block carries `rows_evaluated` (events the predicate was measured against, after the active, parking, economic floor and freshness gates), `system_gates` (how many survived each gate), `root` and a per-leg `passed`/`failed`/`null` breakdown. Read the `warnings` before answering: ALL_NULL means that column is NULL for EVERY row evaluated, so the screen is broken, NOT a real no-match; say the signal is not computed for these events and offer another column, never 'no events match'. A real no-match has legs with non-zero `passed`: loosen the thresholds. HIGH_NULL_RATE (>50% NULL): the results are real but cover part of the universe, so say so. NO_ROWS_EVALUATED: the scope flags, not the predicate, are the problem. A leg's `marginal` is how many more rows dropping that leg adds. If `diagnostics.status` is "unavailable" the events are still correct. "on_empty" (the default) runs the analysis only when this page came back with zero rows, so an empty screen is never reported as a plain 'no matches' when it is really an ALL_NULL column. true runs it always (useful to check how much of the universe a signal actually covers); false never runs it. The analysis is a second aggregate query over the whole candidate universe and adds ~5s, so don't set true routinely. | on_empty |
| exclude_stale | No | Drop stale rows: events last priced more than 3 days behind the market frontier (last_price_snapshot_date). Default true; a 4-day-old snapshot is not a live signal. Set false only when the user explicitly wants stale rows included. |