defmodule Polymarket.Schemas.Event do @moduledoc """ An event returned by the Gamma REST API (e.g. `GET /events/keyset`). An event groups one or more `Polymarket.Schemas.Market`s under a single question/title (e.g. an NBA game with its various betting markets). The Gamma API delivers its keys in `camelCase`; `from_attrs/1` normalises them to the `snake_case` fields below. The `markets`, `series`, `tags`, and `event_metadata` relations are always returned by the keyset endpoint and are parsed into their respective schemas. Every scalar field in the Gamma `Event` OpenAPI schema is mapped (covering both the keyset and single-event endpoints). The nested relational objects in that spec (`bestLines`, `collections`, `eventCreators`, `externalPartners`, `internalUsers`, `sport`, `teams`, `templates`, the `*Optimized` image objects, ...) are intentionally omitted; only `markets`, `series`, `tags`, and `eventMetadata` are modelled as embedded relations. """ use TypedEctoSchema alias Polymarket.JsonUtil alias Polymarket.Schemas.Coerce alias Polymarket.Schemas.EventMetadata alias Polymarket.Schemas.Market alias Polymarket.Schemas.Series alias Polymarket.Schemas.Tag @primary_key false @derive Jason.Encoder typed_embedded_schema do # Identity / description field(:id, :string) field(:ticker, :string) field(:slug, :string) field(:title, :string) field(:description, :string) field(:resolution_source, :string) field(:category, :string) field(:subcategory, :string) field(:series_slug, :string) field(:sort_by, :string) field(:gmp_chart_mode, :string) field(:image, :string) field(:icon, :string) field(:updated_by, :string) field(:created_by, :string) field(:color, :string) field(:country_name, :string) field(:election_type, :string) field(:neg_risk_market_id, :string) field(:subtitle, :string) field(:featured_image, :string) field(:carousel_map, :string) field(:disqus_thread, :string) field(:estimated_value, :string) field(:template_variables, :string) field(:last_highlight, :string) field(:last_highlight_type, :string) # Status flags field(:active, :boolean) field(:closed, :boolean) field(:archived, :boolean) field(:new, :boolean) field(:featured, :boolean) field(:restricted, :boolean) field(:cyom, :boolean) field(:deploying, :boolean) field(:pending_deployment, :boolean) field(:comments_enabled, :boolean) field(:enable_neg_risk, :boolean) field(:neg_risk_augmented, :boolean) field(:neg_risk, :boolean) field(:enable_order_book, :boolean) field(:automatically_active, :boolean) field(:cumulative_markets, :boolean) field(:requires_translation, :boolean) field(:show_all_outcomes, :boolean) field(:show_market_images, :boolean) # The Gamma API emits `estimateValue` as a boolean `false` (not a number); # `estimated_value` is a separate string field (in the Identity block above). field(:estimate_value, :boolean) field(:automatically_resolved, :boolean) field(:cant_estimate, :boolean) field(:is_template, :boolean) # Counts / volume / liquidity field(:comment_count, :integer) field(:featured_order, :integer) field(:neg_risk_fee_bips, :integer) field(:parent_event_id, :integer) field(:tweet_count, :integer) field(:competitive, :float) field(:liquidity, :float) field(:liquidity_amm, :float) field(:liquidity_clob, :float) field(:open_interest, :float) field(:volume, :float) field(:volume24hr, :float) field(:volume1wk, :float) field(:volume1mo, :float) field(:volume1yr, :float) # Dates field(:start_date, :utc_datetime_usec) field(:end_date, :utc_datetime_usec) field(:creation_date, :utc_datetime_usec) field(:created_at, :utc_datetime_usec) field(:updated_at, :utc_datetime_usec) field(:closed_time, :utc_datetime_usec) field(:published_at, :utc_datetime_usec) field(:deploying_timestamp, :utc_datetime_usec) field(:start_time, :utc_datetime_usec) field(:event_date, :date) field(:last_highlight_at, :utc_datetime_usec) field(:scheduled_deployment_timestamp, :utc_datetime_usec) # Sports / live-event metadata (only populated for sports-market events) field(:live, :boolean) field(:ended, :boolean) field(:elapsed, :string) field(:period, :string) field(:score, :string) field(:turn_provider_id, :string) field(:event_week, :integer) field(:game_id, :integer) field(:rescheduled_from_game_id, :integer) field(:spreads_main_line, :float) field(:totals_main_line, :float) field(:finished_timestamp, :utc_datetime_usec) # Array fields field(:sub_events, {:array, :string}) field(:tag_labels, {:array, :string}) field(:tag_slugs, {:array, :string}) embeds_one(:event_metadata, EventMetadata) embeds_many(:markets, Market) embeds_many(:series, Series) embeds_many(:tags, Tag) end @required [:id, :slug] @doc """ Builds an `Event` from the raw (JSON-decoded) Gamma attributes. Keys may be in `camelCase` (atom or string); they are normalised to the `snake_case` fields and each value is coerced to its field type. Returns `{:error, {:missing_fields, fields}}` when a required field is absent. """ @spec from_attrs(map()) :: {:ok, t()} | {:error, {:missing_fields, [atom()]}} def from_attrs(attrs) do attrs = JsonUtil.snake_case_keys(attrs) %__MODULE__{ id: attrs["id"], ticker: attrs["ticker"], slug: attrs["slug"], title: attrs["title"], description: attrs["description"], resolution_source: attrs["resolution_source"], category: attrs["category"], subcategory: attrs["subcategory"], series_slug: attrs["series_slug"], sort_by: attrs["sort_by"], gmp_chart_mode: attrs["gmp_chart_mode"], image: attrs["image"], icon: attrs["icon"], updated_by: attrs["updated_by"], created_by: attrs["created_by"], color: attrs["color"], country_name: attrs["country_name"], election_type: attrs["election_type"], neg_risk_market_id: attrs["neg_risk_market_id"], subtitle: attrs["subtitle"], featured_image: attrs["featured_image"], carousel_map: attrs["carousel_map"], disqus_thread: attrs["disqus_thread"], estimated_value: attrs["estimated_value"], template_variables: attrs["template_variables"], last_highlight: attrs["last_highlight"], last_highlight_type: attrs["last_highlight_type"], active: attrs["active"], closed: attrs["closed"], archived: attrs["archived"], new: attrs["new"], featured: attrs["featured"], restricted: attrs["restricted"], cyom: attrs["cyom"], deploying: attrs["deploying"], pending_deployment: attrs["pending_deployment"], comments_enabled: attrs["comments_enabled"], enable_neg_risk: attrs["enable_neg_risk"], neg_risk_augmented: attrs["neg_risk_augmented"], neg_risk: attrs["neg_risk"], enable_order_book: attrs["enable_order_book"], automatically_active: attrs["automatically_active"], cumulative_markets: attrs["cumulative_markets"], requires_translation: attrs["requires_translation"], show_all_outcomes: attrs["show_all_outcomes"], show_market_images: attrs["show_market_images"], estimate_value: attrs["estimate_value"], automatically_resolved: attrs["automatically_resolved"], cant_estimate: attrs["cant_estimate"], is_template: attrs["is_template"], comment_count: Coerce.to_int(attrs["comment_count"]), featured_order: Coerce.to_int(attrs["featured_order"]), neg_risk_fee_bips: Coerce.to_int(attrs["neg_risk_fee_bips"]), parent_event_id: Coerce.to_int(attrs["parent_event_id"]), tweet_count: Coerce.to_int(attrs["tweet_count"]), competitive: Coerce.to_float(attrs["competitive"]), liquidity: Coerce.to_float(attrs["liquidity"]), liquidity_amm: Coerce.to_float(attrs["liquidity_amm"]), liquidity_clob: Coerce.to_float(attrs["liquidity_clob"]), open_interest: Coerce.to_float(attrs["open_interest"]), volume: Coerce.to_float(attrs["volume"]), volume24hr: Coerce.to_float(attrs["volume24hr"]), volume1wk: Coerce.to_float(attrs["volume1wk"]), volume1mo: Coerce.to_float(attrs["volume1mo"]), volume1yr: Coerce.to_float(attrs["volume1yr"]), start_date: Coerce.to_datetime(attrs["start_date"]), end_date: Coerce.to_datetime(attrs["end_date"]), creation_date: Coerce.to_datetime(attrs["creation_date"]), created_at: Coerce.to_datetime(attrs["created_at"]), updated_at: Coerce.to_datetime(attrs["updated_at"]), closed_time: Coerce.to_datetime(attrs["closed_time"]), published_at: Coerce.to_datetime(attrs["published_at"]), deploying_timestamp: Coerce.to_datetime(attrs["deploying_timestamp"]), start_time: Coerce.to_datetime(attrs["start_time"]), event_date: Coerce.to_date(attrs["event_date"]), last_highlight_at: Coerce.to_datetime(attrs["last_highlight_at"]), scheduled_deployment_timestamp: Coerce.to_datetime(attrs["scheduled_deployment_timestamp"]), live: attrs["live"], ended: attrs["ended"], elapsed: attrs["elapsed"], period: attrs["period"], score: attrs["score"], turn_provider_id: attrs["turn_provider_id"], event_week: Coerce.to_int(attrs["event_week"]), game_id: Coerce.to_int(attrs["game_id"]), rescheduled_from_game_id: Coerce.to_int(attrs["rescheduled_from_game_id"]), spreads_main_line: Coerce.to_float(attrs["spreads_main_line"]), totals_main_line: Coerce.to_float(attrs["totals_main_line"]), finished_timestamp: Coerce.to_datetime(attrs["finished_timestamp"]), sub_events: attrs["sub_events"], tag_labels: attrs["tag_labels"], tag_slugs: attrs["tag_slugs"], event_metadata: Coerce.embed(EventMetadata, attrs["event_metadata"]), markets: Coerce.embed_list(Market, attrs["markets"]), series: Coerce.embed_list(Series, attrs["series"]), tags: Coerce.embed_list(Tag, attrs["tags"]) } |> Coerce.require_fields(@required) end end