Screen Events by Market Data
screen_eventsFinds upcoming ticketed events whose market data meets conditions (prices and their changes, listings and tickets, demand and sales, performer rank and odds, sale dates, venue, location and date) and ranks them by any of those columns. Use it when the person asks for events that match such criteria, or for a ranked list across many events, such as the games with the largest price drop this week. Write the predicate over the columns list_screen_columns names, with the grammar the predicate input gives; preset or view_id runs a preset screen or a saved View instead. To find one event by name, performer or venue, use search_events; for every number on one event, use get_event_analytics.
Tool call template
This tool declares no required parameters. Add optional parameters from the input schema as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {},
"name": "screen_events"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | The page to read, from 1. Default 1. | |
| 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. | |
| preset | No | A preset screen's key, from list_screen_columns with view presets (e.g. top_selling_24h): runs that preset's predicate in its order, the same rows the app's preset shows. Send it instead of predicate and sort. | |
| 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. | |
| parking | No | Parking events: a parking pass that is its own event (sub_category_id 75 or 'Parking' in the name). `hide` (the default) leaves them out; `only` returns parking alone; `include` returns parking beside every other event. Set `only` when the user asks for parking, `include` when they want both; leave it at `hide` otherwise. With view_id the View's own parking choice runs and this is ignored. | hide |
| view_id | No | A saved View's id, from list_views: runs that View's predicate with the View's own economic floor setting, the events the app shows for it now. Send it instead of predicate and preset; sort, columns and paging still apply. | |
| 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. Views and alerts do not gate freshness: when a View is about movement, add a relative-date leg on ea.last_price_snapshot_date (days_ago 1). 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. Views and alerts do not gate freshness: a View about movement adds the leg {"col":"ea.last_price_snapshot_date","op":">=","val":{"days_ago":1}}, which 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 SCREEN KEEPS THE ROWS PRICED WITHIN 3 DAYS OF THE MARKET BY DEFAULT (ea.last_price_snapshot_date, exclude_stale), so a screen adds the leg only for a tighter window. A window of event dates from today ('the next week', 'the next 30 days') is ea.days_to_event between [0, N]. PARKING IS HIDDEN BY DEFAULT: parking passes that are their own event (sub_category_id 75, or 'Parking' in the name) appear only with parking 'only' or 'include'; under the default 'hide' a screen that asks for sub_category_id 75 alone is refused. Also screenable: marketplace Interest (the most people viewing in an hour, whole numbers, with its changes, ea.interest*) and Sales (tickets bought in the last 24 hours, 7 and 30 days, ea.sales_24h / ea.sales_7d / ea.sales_30d, with orders beside them, ea.orders_*); 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.interest* / ea.sales_* 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. TOP SELLING IS SALES, NOT ABSORPTION: 'top selling today' sorts ea.sales_24h desc and 'top selling this week' ea.sales_7d desc (tickets bought, whole numbers; ea.orders_24h / ea.orders_7d count the orders). 0 = checked and no sale; null = no sales record for the event. 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 COUNTS TICKETS THAT LEFT THE MARKETPLACE: ea.absorption_count_1d/7d, ea.absorption_settled_7d, ea.absorption_rate_7d and ea.listings_added_count_* count listings that LEFT (or joined) the marketplace, whether a buyer took them or the seller withdrew, moved or let them expire. Say 'absorbed' / 'delisted'; completed sales are ea.sales_* and ea.orders_*. 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 the last full day (UTC) the block reading covers; an event without listing-level coverage is NULL in every absorption column, never 0, and absorption_count_1d is also NULL when the event was not captured the day before. Quality ranges from 0 to 1 and reflects observation coverage and listing-tracking quality; lower values warrant more caution when comparing activity. Every absorption value is 0 or more. A ticket that comes back on sale comes off the counts, and an exit that takes longer to confirm is added to the 7d count later, on the day it left, so the 7d count and the rate can rise as well as fall after they are published. ea.absorption_settled_7d counts only confirmed exits and leaves out the newest days of the window: quote it as the settled figure and absorption_count_7d as the latest. | |
| 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; the freshness gate is exclude_stale, on by default), `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 |
| include_past | No | Screen events that have already taken place too. Default false. Reading past events takes the plan that includes them. | |
| exclude_stale | No | Keep the rows priced within 3 days of the market frontier (last_price_snapshot_date). Default true. Set false only when the user explicitly wants older rows included. |