betbetter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2206300f9adab01c910ecebf03b96d045751fff30aa1f59ed4ce965e1094d3db
4
+ data.tar.gz: '0086f17ac2ea570145fc6019f28d6d4e0f0785f032f6970e283d9ca3e7048974'
5
+ SHA512:
6
+ metadata.gz: ec316a259a6e30d19200651c0cf64364f153df0aa15f165a6bee77645570f1d52bb4418e36d617259bad6cec5a7575d247bf8d158fc0134208e088aff2ade1f7
7
+ data.tar.gz: c5c9b4792e6c7f3c787525b6deeb3d73b9a8a967b1a7fe03c1e5559e79f6149ec6d2d693cc587f6aec5a23a94706073bdd0f561d4d899f86ae2c9ca5b07b90c9
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Edward Glush
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # betbetter (Ruby)
2
+
3
+ Zero-dependency Ruby client for the **free [Bet Better](https://betbetter.world/api) sports model
4
+ API** — win probabilities, fair odds and plain-English verdicts for every selection the models
5
+ publish. **No API key required.** Standard library only (`net/http`, `json`).
6
+
7
+ Bet Better was founded in 2024.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ gem install betbetter
13
+ ```
14
+
15
+ Or in a Gemfile:
16
+
17
+ ```ruby
18
+ gem "betbetter"
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require "betbetter"
25
+
26
+ Betbetter.list_leagues # => ["afl", "mlb", "nba", ... "soccer/world-cup"]
27
+
28
+ feed = Betbetter.get_picks("nba") # game lines + player props
29
+ feed["picks"].first(3).each do |p|
30
+ puts "#{p['game']} - #{p['selection']} (#{p['confidence']}, fair odds #{p['fairOdds']})"
31
+ end
32
+
33
+ Betbetter.get_best_bets("nfl") # game lines only
34
+ Betbetter.get_prop_bets("nfl") # player props only
35
+ Betbetter.get_results # the public settled record - wins AND losses
36
+ Betbetter.get_scorecard # hit rate, ROI and closing-line value by market
37
+ Betbetter.get_predicted_scores("nba") # array of row hashes, one per priced game
38
+ ```
39
+
40
+ ## What you get
41
+
42
+ Every selection carries the model's own numbers — no bookmaker prices are republished:
43
+
44
+ | Field | Meaning |
45
+ |---|---|
46
+ | `game`, `gameTimeUtc` | Fixture and UTC start time |
47
+ | `market`, `selection`, `line` | What the bet is, in plain words |
48
+ | `winProbabilityPct` | The chance we give this selection (%) |
49
+ | `fairOdds` | Decimal odds implied by that chance |
50
+ | `confidence` | `HIGH` / `LEAN` / `LONG-SHOT` |
51
+ | `verdict` | A dated plain-English read |
52
+
53
+ Leagues: `Betbetter::LEAGUES` — AFL, MLB, NBA, NFL, NHL, NCAAF, NRL, UFC, WNBA, cricket, ATP/WTA
54
+ tennis, and six soccer competitions.
55
+
56
+ ## Tests
57
+
58
+ ```bash
59
+ ruby -Ilib test/test_betbetter.rb # makes one live API call
60
+ BETBETTER_SKIP_LIVE=1 ruby -Ilib test/test_betbetter.rb # offline
61
+ ```
62
+
63
+ ## Links
64
+
65
+ - API docs: https://betbetter.world/api
66
+ - Source: https://gitlab.com/betbetterworld/betbetter-ruby
67
+
68
+ ## Licence
69
+
70
+ Client code: MIT (Edward Glush, 2026).
71
+
72
+ Data: CC BY 4.0, free to reuse with a credit — *Bet Better (https://betbetter.world)*.
73
+
74
+ Model estimates, not betting advice. 18+. Gamble responsibly.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Betbetter
4
+ VERSION = "1.0.0"
5
+ end
data/lib/betbetter.rb ADDED
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ # betbetter - zero-dependency Ruby client for the free Bet Better sports model API.
4
+ #
5
+ # Win probabilities, fair odds and plain-English verdicts for every selection the
6
+ # Bet Better models publish. No API key required.
7
+ #
8
+ # Data licence: CC BY 4.0 - free to reuse with a credit to Bet Better
9
+ # (https://betbetter.world). Model estimates, not betting advice. 18+.
10
+ #
11
+ # require "betbetter"
12
+ # feed = Betbetter.get_picks("nba")
13
+ # feed["picks"].first(3).each { |p| puts "#{p['game']} - #{p['selection']} (#{p['confidence']})" }
14
+
15
+ require "net/http"
16
+ require "uri"
17
+ require "json"
18
+
19
+ require_relative "betbetter/version"
20
+
21
+ module Betbetter
22
+ BASE_URL = "https://betbetter.world"
23
+ USER_AGENT = "betbetter-ruby/#{VERSION}"
24
+
25
+ # League path segments accepted by the API (the `league` enum in the OpenAPI
26
+ # spec). Soccer and tennis use two segments.
27
+ LEAGUES = %w[
28
+ afl mlb nba nfl nhl ncaaf nrl ufc wnba cricket
29
+ tennis/atp tennis/wta
30
+ soccer/epl soccer/la-liga soccer/serie-a soccer/bundesliga
31
+ soccer/ligue-1 soccer/world-cup
32
+ ].freeze
33
+
34
+ # Sports served by /predicted-scores.
35
+ SCORE_SPORTS = %w[
36
+ nfl ncaaf nba ncaab mlb nhl afl nrl wnba soccer cricket
37
+ ].freeze
38
+
39
+ # Raised when the API returns a non-200 response.
40
+ class Error < StandardError; end
41
+
42
+ class << self
43
+ # The leagues this API serves.
44
+ # @return [Array<String>]
45
+ def list_leagues
46
+ LEAGUES.dup
47
+ end
48
+
49
+ # Combined picks feed (game lines + player props) for a league.
50
+ # Each pick carries game, gameTimeUtc, market, selection, line,
51
+ # winProbabilityPct, fairOdds, confidence (HIGH / LEAN / LONG-SHOT) and a
52
+ # dated verdict.
53
+ # @return [Hash]
54
+ def get_picks(league)
55
+ get_json("/#{check_league(league)}/picks")
56
+ end
57
+
58
+ # Game-line selections only for a league.
59
+ # @return [Hash]
60
+ def get_best_bets(league)
61
+ get_json("/#{check_league(league)}/best-bets")
62
+ end
63
+
64
+ # Player-prop selections only for a league.
65
+ # @return [Hash]
66
+ def get_prop_bets(league)
67
+ get_json("/#{check_league(league)}/prop-bets")
68
+ end
69
+
70
+ # The settled record - wins and losses - with hit rate and ROI by sport.
71
+ # @return [Hash]
72
+ def get_results
73
+ get_json("/results")
74
+ end
75
+
76
+ # Settled model performance by sport and market (hit rate, ROI, CLV).
77
+ # @return [Hash]
78
+ def get_scorecard
79
+ get_json("/scorecard")
80
+ end
81
+
82
+ # Predicted final scores for every game the model prices in a sport.
83
+ # Served as CSV; parsed here into an array of row hashes with numeric
84
+ # values where the column is numeric.
85
+ # @return [Array<Hash>]
86
+ def get_predicted_scores(sport)
87
+ s = normalise(sport)
88
+ text = fetch("/predicted-scores/#{s}", "csv", "text/csv")
89
+ parse_csv(text)
90
+ end
91
+
92
+ private
93
+
94
+ def normalise(value)
95
+ value.to_s.strip.gsub(%r{\A/+|/+\z}, "").downcase
96
+ end
97
+
98
+ def check_league(league)
99
+ lg = normalise(league)
100
+ unless LEAGUES.include?(lg)
101
+ raise ArgumentError, "Unknown league #{league.inspect}. Valid options: #{LEAGUES.join(', ')}"
102
+ end
103
+
104
+ lg
105
+ end
106
+
107
+ def get_json(path)
108
+ JSON.parse(fetch(path, "json", "application/json"))
109
+ end
110
+
111
+ def fetch(path, format, accept)
112
+ uri = URI.parse("#{BASE_URL}#{path}?format=#{format}")
113
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
114
+ open_timeout: 15, read_timeout: 30) do |http|
115
+ http.request(Net::HTTP::Get.new(uri, "User-Agent" => USER_AGENT, "Accept" => accept))
116
+ end
117
+ unless response.is_a?(Net::HTTPSuccess)
118
+ raise Error, "betbetter: HTTP #{response.code} for #{path}"
119
+ end
120
+
121
+ response.body.to_s.force_encoding(Encoding::UTF_8)
122
+ end
123
+
124
+ # Minimal RFC4180-ish parser - enough for this feed (quoted fields may hold
125
+ # commas and doubled quotes), so the gem stays standard-library only.
126
+ def parse_csv(text)
127
+ lines = text.split(/\r?\n/).reject { |l| l.empty? || l.start_with?("#") }
128
+ return [] if lines.length < 2
129
+
130
+ header = split_row(lines[0])
131
+ lines[1..].map do |line|
132
+ values = split_row(line)
133
+ row = {}
134
+ header.each_with_index { |col, i| row[col] = coerce(values[i]) }
135
+ row
136
+ end
137
+ end
138
+
139
+ def split_row(line)
140
+ fields = []
141
+ field = +""
142
+ in_quotes = false
143
+ i = 0
144
+ while i < line.length
145
+ ch = line[i]
146
+ if in_quotes
147
+ if ch == '"'
148
+ if line[i + 1] == '"'
149
+ field << '"'
150
+ i += 1
151
+ else
152
+ in_quotes = false
153
+ end
154
+ else
155
+ field << ch
156
+ end
157
+ elsif ch == '"'
158
+ in_quotes = true
159
+ elsif ch == ","
160
+ fields << field
161
+ field = +""
162
+ else
163
+ field << ch
164
+ end
165
+ i += 1
166
+ end
167
+ fields << field
168
+ fields
169
+ end
170
+
171
+ def coerce(value)
172
+ return nil if value.nil? || value.empty?
173
+ return Integer(value) if value.match?(/\A-?\d+\z/)
174
+ return Float(value) if value.match?(/\A-?\d*\.\d+([eE][-+]?\d+)?\z/)
175
+
176
+ value
177
+ end
178
+ end
179
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: betbetter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Edward Glush
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Win probabilities, fair odds and plain-English verdicts for every selection
14
+ the Bet Better models publish, across 12 sports. No API key. Data licensed CC BY
15
+ 4.0. Model estimates, not betting advice. 18+.
16
+ email:
17
+ - edward@betbetter.world
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - lib/betbetter.rb
25
+ - lib/betbetter/version.rb
26
+ homepage: https://gitlab.com/betbetterworld/betbetter-ruby
27
+ licenses:
28
+ - MIT
29
+ metadata:
30
+ homepage_uri: https://gitlab.com/betbetterworld/betbetter-ruby
31
+ source_code_uri: https://gitlab.com/betbetterworld/betbetter-ruby
32
+ bug_tracker_uri: https://gitlab.com/betbetterworld/betbetter-ruby/-/issues
33
+ documentation_uri: https://betbetter.world/api
34
+ rubygems_mfa_required: 'true'
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 3.0.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.5.22
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Zero-dependency Ruby client for the free Bet Better sports model API.
54
+ test_files: []