feat(hyperliquid): recover settled outcomes' question_id from settledOutcome - #306
Merged
Merged
Conversation
…Outcome
`outcomeMeta` drops a multi-outcome question once it fully resolves, so
the live `outcomeToQuestion` map can no longer link the settled child
outcomes back to their parent question. The settled outcomes ended up
in `state_outcome_meta` with `question_id=null`, and downstream
consumers lost the ability to group them under their parent market.
HL still exposes the link on the `settledOutcome` payload:
response.question = {
question: { settled: <qid> } | { live: <qid> },
name: "...",
description: "...",
}
Adds `extractSettledQuestionId(settled)` to read the discriminated
field and uses it as a fallback when the live map has no entry. The
two paths cover both lifecycle states:
- parent question still active (partial settlement): live map hits
- parent question fully settled and dropped: response.question hits
Self-healing for previously-orphaned rows: the already-settled guard
now requires `question_id IS NOT NULL`, so settled outcomes captured
before this change get re-probed on the next cycle and have their
parent question id backfilled via the RMT collapse. Binary
single-outcome markets re-probe each cycle (no parent to find, write
is idempotent) — a small steady-state cost worth the simpler logic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves the Hyperliquid outcomes scraper so that settled child outcomes from fully-resolved multi-outcome questions can still be linked back to their parent question_id, even after Hyperliquid drops the parent question from outcomeMeta.
Changes:
- Add
extractSettledQuestionId()and extend thesettledOutcometype to capture the optionalquestionwrapper for parent question linkage. - Fall back to extracting
question_idfromsettledOutcomewhen the liveoutcomeToQuestionmap has no entry. - Narrow the “already settled” skip set to
status='settled' AND question_id IS NOT NULL, so previously-orphaned rows can be re-probed and backfilled.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| services/hyperliquid/outcomes.ts | Uses extractSettledQuestionId() fallback and adjusts “already settled” filtering to allow orphan backfill. |
| services/hyperliquid/outcomes.test.ts | Adds an integration-style cycle test to verify question_id recovery from settledOutcome. |
| services/hyperliquid/outcomes-info.ts | Extends settled outcome typing and adds extractSettledQuestionId() helper. |
| services/hyperliquid/outcomes-info.test.ts | Adds unit tests covering extractSettledQuestionId() behavior across wrapper variants. |
Comment on lines
+47
to
+53
| * Discover outcome_ids we've already captured as `status='settled'` *with* a | ||
| * populated `question_id`. Once both are written the row is terminal — settled | ||
| * payloads are immutable on the HL side, so skipping them turns the steady-state | ||
| * cycle into a no-op on the Info API even as the cumulative settled universe | ||
| * grows. Rows with `status='settled' AND question_id IS NULL` are orphans | ||
| * (early scraper missed the multi-outcome question link) and stay eligible for | ||
| * re-probe so they self-heal on the next cycle. |
Comment on lines
55
to
58
| async function fetchAlreadySettledIds(): Promise<Set<number>> { | ||
| const { data } = await query<{ outcome_id: string }>( | ||
| "SELECT toString(outcome_id) AS outcome_id FROM state_outcome_meta FINAL WHERE status = 'settled'", | ||
| "SELECT toString(outcome_id) AS outcome_id FROM state_outcome_meta FINAL WHERE status = 'settled' AND question_id IS NOT NULL", | ||
| ); |
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
When a multi-outcome question fully resolves (e.g. "World Cup: Germany vs Curacao"), HL drops it from `outcomeMeta`. The live `outcomeToQuestion` map then can't link the settled child outcomes back to their parent, so they land in `state_outcome_meta` with `question_id=null` and downstream consumers lose the grouping.
HL still exposes the link on the `settledOutcome` response under `question.question.{settled|live}`. This change:
Binary single-outcome markets continue to re-probe each cycle (no parent to find, write is idempotent) — small steady-state cost in exchange for not adding a separate "probed completely" sentinel to the schema.
🤖 Generated with Claude Code