Tennis Raptor: live tennis event-state scout (observe-only) - #7
Conversation
…-only) A second venue-neutral macro Raptor beside the Sports Raptor, built on the same pattern (issue mbordash#6): polls the Live Tennis API free-tier REST endpoint GET /matches?status=live (Bearer key from LIVETENNIS_API_KEY), tracks one live match (sticky by id, else freshest score), and broadcasts a Copy TennisSnapshot — sets, current-set games, serving side, tiebreak flag, and a derived break-point flag (receiver at AD, or receiver at 40 vs server below 40; never in tiebreaks) — over a watch channel, with telemetry under the fixed "tennis" health-map key. Feed health mirrors the other scouts' stale-feed-reads-as-disconnected rule: each score carries the API's own last-change timestamp, and when the tracked score is older than TENNIS_SCORE_STALENESS_SECS the raptor reports tennis_connected = false plus feed_age_secs, so a consumer widens or pulls rather than holding. Zero live matches is a healthy neutral state (tennis has quiet hours), and a missing key parks the raptor idle exactly like the Sports Raptor. Honest budget defaults in all three config templates: the free tier is 30 req/min / 100 req/day, so TENNIS_POLL_SECS defaults to 900 (all-day safe); ~60s polling is documented as develop-and-test / few-match cadence. The provider's push WebSocket and model win-probability are top-tier features and are NOT used — v1 is free-tier REST only, observe-only, not consumed by any Viper sizing. Wiring: spawned beside the Sports Raptor on the intl and us_retail paths; SquadronRaptors gains an optional tennis receiver attached post-construction so no constructor signature changes; telemetry sampler de-duplicates the slow feed like the Sports feed. 11 unit tests cover parsing, break-point edge cases, tiebreaks, null/empty score fields, tracked-match stickiness/rotation, staleness, and error bodies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
✅ All contributors have signed the CLA. |
|
I have read the CLA Document and I hereby sign the CLA |
|
recheck |
|
Heads-up on the red |
|
recheck |
1 similar comment
|
recheck |
|
ok all good now, thanks for helping me debug. I'll review the PR today. |
|
one thing -- your PR is forcing me in a good way to create raptor setup panel cards in the UI as an initial start -- right now raptors use the .env to setup keys which is kind of ugly. I'm going to take care of that today. |
|
Glad the CLA hunt paid off, and setup-panel cards for raptor keys sounds like exactly the right call — .env works but a panel makes the whole raptor ecosystem feel first-class. When it lands I'm happy to adapt the tennis raptor's config surface to it (should be a small change; everything key-related is already isolated in the config layer). No rush on the review — the PR will keep. |
There was a problem hiding this comment.
This is really solid work. The break-point tests and the malformed-body ones are exactly what I'd want to see, and thanks for adding the constants to all three profile templates — that one trips people up a lot, since config.rs is gitignored and CI builds off the balanced example.
Pulled it down locally, builds clean and all 11 of your tests pass.
Three small things inline. One other gap: LIVETENNIS_API_KEY does still need an entry in .env.example in case users want to run in headless mode without the UI. Look at ODDS_API_KEY at line 210 for a good template.
Really glad to have this in, it's the first contributed raptor and a good template for the next one.
…mple Address PR mbordash#7 review: - Telemetry heartbeat 300s → 1800s to match the Sports feed. The raptor polls every 900s, so nothing can change between polls — the shorter heartbeat only re-stored identical points and cut retained history to ~5 days instead of ~30. Comment rewritten to state the real rationale. - US build: thread the tennis receiver through run_us_trader → run_wing → trade_one_market → register_us_squadron, attached to both wings' SquadronRaptors exactly like us_sports_rx, so the channel stays live for the first consumer instead of relying on a scope-held binding. - tennis.rs header doc: state up front that the tracked match is sticky on the previous id, else freshest score, and is NOT yet tied to a specific venue market (left to the first consumer). - .env.example: LIVETENNIS_API_KEY entry per the ODDS_API_KEY template, for headless runs without the UI. cargo test green (142 passed), us_retail + kalshi feature checks clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All four addressed in c68e1b3 — the three inline items (heartbeat → 1800 with an honest comment, tennis receiver threaded through the US trader like Re-ran the gate after the changes: Thanks for pulling it down and running the tests yourself — and for the fast, precise review. Glad this can serve as the template for the next raptor. |
|
Thank you-- I ended up creating the new UI panels for Raptors this morning and will wire your's up when I merge everything together. |
|
Merged same-day — thank you! And glad the raptor ended up pulling the setup-panel work forward; that's the best kind of side effect. When the panel wiring lands, ping me (or just open an issue) and I'll adapt the tennis raptor's config surface to it — everything key-related is isolated in one place, so it should be a small, clean change. Happy to be raptor #1 in the nest. |
* feat(raptors): Tennis Raptor — live tennis event-state scout (observe-only) A second venue-neutral macro Raptor beside the Sports Raptor, built on the same pattern (issue #6): polls the Live Tennis API free-tier REST endpoint GET /matches?status=live (Bearer key from LIVETENNIS_API_KEY), tracks one live match (sticky by id, else freshest score), and broadcasts a Copy TennisSnapshot — sets, current-set games, serving side, tiebreak flag, and a derived break-point flag (receiver at AD, or receiver at 40 vs server below 40; never in tiebreaks) — over a watch channel, with telemetry under the fixed "tennis" health-map key. Feed health mirrors the other scouts' stale-feed-reads-as-disconnected rule: each score carries the API's own last-change timestamp, and when the tracked score is older than TENNIS_SCORE_STALENESS_SECS the raptor reports tennis_connected = false plus feed_age_secs, so a consumer widens or pulls rather than holding. Zero live matches is a healthy neutral state (tennis has quiet hours), and a missing key parks the raptor idle exactly like the Sports Raptor. Honest budget defaults in all three config templates: the free tier is 30 req/min / 100 req/day, so TENNIS_POLL_SECS defaults to 900 (all-day safe); ~60s polling is documented as develop-and-test / few-match cadence. The provider's push WebSocket and model win-probability are top-tier features and are NOT used — v1 is free-tier REST only, observe-only, not consumed by any Viper sizing. Wiring: spawned beside the Sports Raptor on the intl and us_retail paths; SquadronRaptors gains an optional tennis receiver attached post-construction so no constructor signature changes; telemetry sampler de-duplicates the slow feed like the Sports feed. 11 unit tests cover parsing, break-point edge cases, tiebreaks, null/empty score fields, tracked-match stickiness/rotation, staleness, and error bodies. * review: telemetry heartbeat, US-build tennis receiver, doc + .env.example Address PR #7 review: - Telemetry heartbeat 300s → 1800s to match the Sports feed. The raptor polls every 900s, so nothing can change between polls — the shorter heartbeat only re-stored identical points and cut retained history to ~5 days instead of ~30. Comment rewritten to state the real rationale. - US build: thread the tennis receiver through run_us_trader → run_wing → trade_one_market → register_us_squadron, attached to both wings' SquadronRaptors exactly like us_sports_rx, so the channel stays live for the first consumer instead of relying on a scope-held binding. - tennis.rs header doc: state up front that the tracked match is sticky on the previous id, else freshest score, and is NOT yet tied to a specific venue market (left to the first consumer). - .env.example: LIVETENNIS_API_KEY entry per the ODDS_API_KEY template, for headless runs without the UI. cargo test green (142 passed), us_retail + kalshi feature checks clean.
Addresses #6 — the tennis event-state Raptor we discussed, built on the Sports Raptor pattern as you suggested.
What it does
src/raptors/tennis.rs— venue-neutral, observe-only, shaped likesports.rs: keyed on envLIVETENNIS_API_KEY, parks idle with a neutral snapshot when the key is absent, pollsGET /matches?status=live(free tier), and broadcasts aCopyTennisSnapshotover awatchchannel + telemetry under a fixed"tennis"health-map key.TENNIS_SCORE_STALENESS_SECS(default 600s — tennis pauses legitimately at changeovers/set breaks), the raptor reportstennis_connected = falseplusfeed_age_secs, so a consumer treats a stale feed exactly like a missing one — widen/pull, never hold. Zero live matches is reported as healthy-neutral (num_live = 0), not a failure; tennis has quiet hours every day.us_retailpaths;SquadronRaptorsgains an optionaltennisreceiver, attached post-construction (the same way the US general wing attaches its sports feed) so no constructor signatures changed and all three venue builds stay compatible. Telemetry sampler de-duplicates it like the Sports feed, with a shorter heartbeat so staleness stays visible on the chart. Config block added to all three profile templates, observe-only and inert without a key. No Viper consumes it — that bar stays yours.Honest cadence / budget
Disclosure again: I run the Live Tennis API. The free tier is 30 req/min and 100 req/day.
TENNIS_POLL_SECSdefaults to 900 so all-day polling fits inside the free cap (≤96 req/day); dropping it to ~60s gives near point-level tracking but burns the free day cap in about 100 minutes — fine for develop-and-test or following a few matches, sustained fast polling needs a paid tier. The doc comments say all of this. The top tier's push WebSocket and model win-probability fields are not used — v1 is free-tier REST only; a WS lane can come later as a separate config-gated option if the raptor earns it.Tests
Fields are read against the API's published OpenAPI spec (docs.livetennisapi.com/openapi.yaml), including its documented quirks (null point entries, empty games arrays on completed matches). Parsing is factored into pure functions with 11 inline unit tests: full parse, a break-point truth table, tiebreaks, null/empty score fields, tracked-match stickiness and rotation, staleness flagging, missing timestamps, empty live list, API error bodies, malformed JSON, and URL/tour-filter building.
Locally, per CONTRIBUTING:
Happy to sign the CLA as soon as the bot posts. Thanks for the quick green light and the pointer at the odds-raptor pattern — it made this a very direct build. If the raptor-via-UI roadmap lands and you'd rather this migrate to that surface, I'm glad to follow up.
🤖 Generated with Claude Code