diff --git a/.gitignore b/.gitignore index 6085122..1a9313b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,10 @@ venv/ env/ # Database / Docker Volumes -data/ +/data/ +ai-engine/data/**/*.csv +ai-engine/data/v26_shadow/ +ai-engine/data/__pycache__/ postgres-data/ redis-data/ diff --git a/ai-engine/data/__init__.py b/ai-engine/data/__init__.py new file mode 100644 index 0000000..d6f6516 --- /dev/null +++ b/ai-engine/data/__init__.py @@ -0,0 +1 @@ +# data package diff --git a/ai-engine/data/database.py b/ai-engine/data/database.py new file mode 100644 index 0000000..fac00f4 --- /dev/null +++ b/ai-engine/data/database.py @@ -0,0 +1,97 @@ +""" +Async Database Module — V2 Betting Engine +========================================== +Provides async SQLAlchemy sessions via asyncpg for the V2 router. + +Usage: + async with get_session() as session: + result = await session.execute(text("SELECT ...")) +""" + +from __future__ import annotations + +import os +from contextlib import asynccontextmanager +from typing import AsyncGenerator + +from dotenv import load_dotenv +from sqlalchemy.ext.asyncio import ( + AsyncEngine, + AsyncSession, + async_sessionmaker, + create_async_engine, +) + +load_dotenv() + +_engine: AsyncEngine | None = None +_session_maker: async_sessionmaker[AsyncSession] | None = None + + +def _get_async_dsn() -> str: + """ + Convert DATABASE_URL to asyncpg-compatible format. + + Handles: + 1. Prisma's ``?schema=public`` suffix → stripped + 2. ``postgresql://`` driver prefix → ``postgresql+asyncpg://`` + """ + dsn = os.getenv( + "DATABASE_URL", + "postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db", + ) + + # Strip Prisma's ?schema= parameter + if "?" in dsn: + base, query = dsn.split("?", 1) + kept_parts = [ + part for part in query.split("&") if part and not part.startswith("schema=") + ] + dsn = base if not kept_parts else f"{base}?{'&'.join(kept_parts)}" + + # Convert driver prefix for asyncpg + if dsn.startswith("postgresql://"): + dsn = dsn.replace("postgresql://", "postgresql+asyncpg://", 1) + elif dsn.startswith("postgres://"): + dsn = dsn.replace("postgres://", "postgresql+asyncpg://", 1) + + return dsn + + +def _ensure_engine() -> AsyncEngine: + global _engine, _session_maker + if _engine is None: + _engine = create_async_engine( + _get_async_dsn(), + pool_size=5, + max_overflow=5, + pool_timeout=10, + pool_pre_ping=True, + echo=False, + ) + _session_maker = async_sessionmaker( + bind=_engine, + class_=AsyncSession, + expire_on_commit=False, + ) + print("✅ Async database engine created (asyncpg)") + return _engine + + +@asynccontextmanager +async def get_session() -> AsyncGenerator[AsyncSession, None]: + """Provide an async session context manager.""" + _ensure_engine() + assert _session_maker is not None + async with _session_maker() as session: + yield session + + +async def dispose_engine() -> None: + """Shut down the async engine cleanly.""" + global _engine, _session_maker + if _engine is not None: + await _engine.dispose() + _engine = None + _session_maker = None + print("ℹ️ Async database engine disposed") diff --git a/ai-engine/data/db.py b/ai-engine/data/db.py new file mode 100644 index 0000000..71ca134 --- /dev/null +++ b/ai-engine/data/db.py @@ -0,0 +1,92 @@ +""" +Synchronous psycopg2 database helper for the AI Engine. +Uses a thread-safe connection pool for legacy V20+ endpoints. +""" + +from __future__ import annotations + +import os +from contextlib import contextmanager +from typing import Generator + +import psycopg2 +from psycopg2 import pool +from psycopg2.extensions import connection as PgConnection +from dotenv import load_dotenv + +load_dotenv() + +# Safe default with no credentials — will fail fast if not configured. +_DEFAULT_DSN = "postgresql://postgres:postgres@localhost:15432/boilerplate_db" + + +def get_clean_dsn() -> str: + """ + Return a psycopg2-compatible DSN from DATABASE_URL. + + Handles DSN cleanup issues that break raw usage: + 1. Prisma appends '?schema=public' which psycopg2 cannot parse. + """ + dsn: str = os.getenv("DATABASE_URL", _DEFAULT_DSN) + connect_timeout: str = os.getenv("PGCONNECT_TIMEOUT", "5").strip() or "5" + + # Strip Prisma's ?schema= query parameter while preserving any other query args. + if "?" in dsn: + base, query = dsn.split("?", 1) + kept_parts: list[str] = [ + part for part in query.split("&") if part and not part.startswith("schema=") + ] + dsn = base if not kept_parts else f"{base}?{'&'.join(kept_parts)}" + + # Force bounded DB connect attempts so API calls do not hang indefinitely. + if "connect_timeout=" not in dsn: + separator = "&" if "?" in dsn else "?" + dsn = f"{dsn}{separator}connect_timeout={connect_timeout}" + return dsn + + +class Database: + _pool: pool.ThreadedConnectionPool | None = None + + @classmethod + def initialize(cls) -> None: + if cls._pool is None: + dsn: str = get_clean_dsn() + try: + cls._pool = pool.ThreadedConnectionPool( + minconn=1, + maxconn=10, + dsn=dsn, + ) + print("✅ Database connection pool created") + except Exception as e: + print(f"❌ Failed to create DB pool: {e}") + raise + + @classmethod + def get_conn(cls) -> PgConnection: + if cls._pool is None: + cls.initialize() + assert cls._pool is not None # guaranteed by initialize() + return cls._pool.getconn() + + @classmethod + def return_conn(cls, conn: PgConnection) -> None: + if cls._pool: + cls._pool.putconn(conn) + + @classmethod + @contextmanager + def connection(cls) -> Generator[PgConnection, None, None]: + """Context manager for safe connection handling.""" + conn: PgConnection = cls.get_conn() + try: + yield conn + finally: + cls.return_conn(conn) + + @classmethod + def close_all(cls) -> None: + if cls._pool: + cls._pool.closeall() + print("ℹ️ Database connection pool closed") diff --git a/ai-engine/data/league_reliability.json b/ai-engine/data/league_reliability.json new file mode 100644 index 0000000..740678e --- /dev/null +++ b/ai-engine/data/league_reliability.json @@ -0,0 +1,726 @@ +{ + "version": "v1", + "description": "Per-league odds reliability scores computed from Brier Score analysis", + "min_matches_threshold": 50, + "total_leagues": 265, + "default_reliability": 0.35, + "lookup": { + "bx57cmq1edfq53ckfk791supi": 0.9476, + "55hcphd1ccc6eai1ms77460on": 0.9445, + "d9eaigzyfnfiraqc3ius757tl": 0.9402, + "1gxlzw2ezkyeykhcaa5x8ozkk": 0.9259, + "5jd0k2txwnq69frs79eulba8j": 0.9233, + "6694fff47wqxl10lrd9tb91f8": 0.9193, + "4jg7he1n3rb5dniq6hf49xorq": 0.9061, + "59tpnfrwnvhnhzmnvfyug68hj": 0.8988, + "ac42gi3penartj88fe9l6plpk": 0.8937, + "3j81qr7yc4gdnakfwnxf95ovh": 0.8771, + "9z5643nd06afqu01ea2wt8y4g": 0.8734, + "482ofyysbdbeoxauk19yg7tdt": 0.8722, + "ahl3vljaignq9ebaos4uqkrvo": 0.8696, + "8x3sbh85gc8qir50utw39jl04": 0.865, + "agpweohvn9tugnyl6ry4rhivp": 0.8428, + "4c1nfi2j1m731hcay25fcgndq": 0.8425, + "1j4ehtrbry9depwt6oghaq3lu": 0.8299, + "40yjcbx2sq6oq736iqqqczwt1": 0.8237, + "145hkd59i6foieuwr4mwi6wlq": 0.823, + "34pl8szyvrbwcmfkuocjm3r6t": 0.8227, + "cse5oqqt2pzfcy8uz6yz3tkbj": 0.8212, + "zs18qaehvhg3w1208874zvfa": 0.8176, + "57nu0wygurzkp6fuy5hhrtaa2": 0.8099, + "1eruend45vd20g9hbrpiggs5u": 0.8083, + "595nsvo7ykvoe690b1e4u5n56": 0.7987, + "6vq8j5p3av14nr3iuyi4okhjt": 0.793, + "486rhdgz7yc0sygziht7hje65": 0.7901, + "9hh6n2f84k31zmlcxyvmc1w2y": 0.789, + "3n5046abeu3x482ds3jwda238": 0.7863, + "8yi6ejjd1zudcqtbn07haahg6": 0.7752, + "byhmntnl1b4lxw0zz21im3zkd": 0.7719, + "2bmwykmdlcc2u1c40ytoc39vy": 0.7668, + "82jkgccg7phfjpd0mltdl3pat": 0.7643, + "2nttcoriwf5co73vmz1vr8frm": 0.7641, + "dr2xk7muj8aqcjdz2b3li1c0k": 0.759, + "4yngyfinzd6bb1k7anqtqs0wt": 0.7586, + "eog6knrkfei68si736fpquyzc": 0.756, + "eg6s9f1jj7jr6stmbosn0g6c8": 0.7538, + "ae1wva3zrzcp2zd15gpvsntg6": 0.7517, + "cesdwwnxbc5fmajgroc0hqzy2": 0.7466, + "8k1xcsyvxapl4jlsluh3eomre": 0.7463, + "bdtat25m14jy85y484z3e6lf": 0.7437, + "iu1vi94p4p28oozl1h9bvplr": 0.7411, + "1r097lpxe0xn03ihb7wi98kao": 0.7391, + "2kwbbcootiqqgmrzs6o5inle5": 0.7386, + "9fuwphq8kvugrlc3ckm7k8wes": 0.7358, + "civf31q1inxohs4a03y8reetf": 0.735, + "ili150pwfuf39f7yfdch9lhw": 0.7286, + "abs7n2ae3oydilk0tgmpnsj89": 0.7277, + "9nbpdi9q3ywcm4q0j5u0ekwcq": 0.7254, + "6by3h89i2eykc341oz7lv1ddd": 0.7252, + "4qehj8hfxmy6o2ohp4fxinnzo": 0.7244, + "9u4pm8x0lfmfq3r0pypmrls71": 0.7244, + "c7b8o53flg36wbuevfzy3lb10": 0.7144, + "89ovpy1rarewwzqvi30bfdr8b": 0.7068, + "4d5d3sf6805n5u6jdoa0hdlog": 0.7052, + "eqz64pn0qsp2y7aq4m9id3fn6": 0.7031, + "8q60vlvn3krynkob6igrncdjq": 0.703, + "6ihotpaocgiovlxw18e9r9prx": 0.7019, + "c0r21rtokgnbtc0o2rldjmkxu": 0.7013, + "1mpjd0vbxbtu9zw89yj09xk3z": 0.6996, + "4zwgbb66rif2spcoeeol2motx": 0.6995, + "bu1l7ckihyr0errxw61p0m05": 0.6995, + "cv3tuitw3ho3v0opjjxpn83b9": 0.6974, + "8r98daokeuzsamu5fmjtblqx5": 0.6922, + "dvstmwnvw0mt5p38twn9yttyb": 0.688, + "8y29fg2s85ppcb8uugm5ee8s4": 0.6866, + "19q13y6ruzo0o84ipblcuouzs": 0.6858, + "f4jc2cc5nq7flaoptpi5ua4k4": 0.6852, + "4oogyu6o156iphvdvphwpck10": 0.684, + "3e40pestup9xzagsu2o6c0i8u": 0.6824, + "4rls982p5uzil6x30mhyhv9f3": 0.6812, + "e21cf135btr8t3upw0vl6n6x0": 0.6771, + "65q4uwm6ol1rkf5dp89m8omny": 0.6754, + "46b141eaqq9q7o4gz5gtdpikk": 0.6752, + "75i269i1ak43magshljadydrh": 0.6741, + "3ab1uwtoyjopdj1y1fynyy9jg": 0.6737, + "4mbfidy8zum5u0aqjqo0vuqs2": 0.673, + "7wssxdqi4xihseeam8grqa2b8": 0.666, + "61fzfjogstjuukzcehighq7mu": 0.6641, + "6g8hw3acenrw828la7gwx4mvs": 0.663, + "e1kxdivp5g4cpldgpwvnzl1vv": 0.6626, + "9ikchyu9fb8bvx0s673jofj6s": 0.6622, + "a9vrdkelbgif0gtu3wxsr75xo": 0.6618, + "6sxm2iln2w45ux498pty9miw8": 0.6615, + "ea0h6cf3bhl698hkxhpulh2zz": 0.661, + "apdwh753fupxheygs8seahh7x": 0.6604, + "er5745q30wnr8jv9nr863omzg": 0.659, + "2z7257m7hj58zuxcjrsg4erzc": 0.6551, + "2o9svokc5s7diish3ycrzk7jm": 0.655, + "8usjlmziv3p2re0r2wwzezki9": 0.6549, + "c0yqkbilbbg70ij2473xymmqv": 0.6506, + "du6jsenbjql5e8f3yk880ox4g": 0.6494, + "cbdbziaqczfuyuwqsylqi26zd": 0.6478, + "725gd73msyt08xm76v7gkxj7u": 0.6445, + "enzlj1as2raqm4ids1zyb07y1": 0.6442, + "scf9p4y91yjvqvg5jndxzhxj": 0.6414, + "5z8v4mj6cjs9ex6hdrpourjzh": 0.6389, + "4zwjlzdszduqmxzusysvzymms": 0.6387, + "7nmz249q89qg5ezcvzlheljji": 0.6381, + "2mdmx668tyhy4u4z9zszwjv5v": 0.6345, + "4a7o9rf7ytl8g3ejwpblc6p5n": 0.6306, + "2ty8ihceabty8yddmu31iuuej": 0.6283, + "dy8zaksw5e9nwrs1p5ss4o1nu": 0.628, + "1b70m6qtxrp75b4vtk8hxh8c3": 0.6261, + "ajxs0e0g6ryg5ol8qvw3evrcz": 0.6249, + "a4fgj2rfbpf4ejo1qi624fefo": 0.6184, + "akmkihra9ruad09ljapsm84b3": 0.6182, + "907l7wtxdvugdo9i2249wcmr0": 0.6171, + "6lwpjhktjhl9g7x2w7njmzva6": 0.6164, + "ax1yf4nlzqpcji4j8epdgx3zl": 0.6163, + "6ybvtzejh91761lqe7y1csrqo": 0.6158, + "3btdfgw79qiz3jmyfudovtbu2": 0.6122, + "5cwsxtx37les6m10xj71htkgf": 0.6101, + "9p3nnxhdjahfn8qswpzy8oyc3": 0.61, + "2xg0qvif1rh7du6wmk2eleku3": 0.6091, + "1wwro3z1eb3fl601dju6inlc6": 0.6084, + "gfskxsdituog2kqp9yiu7bzi": 0.6076, + "zilopfej2h0n3vpan5tcynpo": 0.6051, + "2hsidwomhjsaaytdy9u5niyi4": 0.6012, + "1klyfth8tl6lu6ra7k8zmy2n2": 0.5996, + "cegl2ivkc25blcatxp4jmk1ec": 0.5993, + "7qf0jaayyxy3ruamsexv5p1kl": 0.5988, + "erpufio3qaujd9gkszcqvb0bf": 0.5972, + "cfesxhzb83yl8b779uv3revz1": 0.597, + "3ww12jab49q8q8mk9avdwjqgk": 0.5961, + "8t2o4huu2e48ij23dxnl9w5qx": 0.5928, + "5vq1bl8h8dxdr34w0jaanokto": 0.5919, + "ac112osli9fvox1epcg4ld3t6": 0.59, + "3frp1zxrqulrlrnk503n6l4l": 0.5808, + "c76z5d6j7dpi1e79tm8fpm39z": 0.5807, + "6ifaeunfdelecgticvxanikzu": 0.5796, + "81txfenlgw75nq3u2nfdkj92o": 0.5789, + "yv73ms6v1995b5wny16jcfi3": 0.5787, + "b3ufcd24wfnnd5j98ped6irfu": 0.5752, + "29actv1ohj8r10kd9hu0jnb0n": 0.5737, + "bfqezwfhot1l3p1cpk4oonh25": 0.5705, + "5taraea6mqjjldg9zxswo825y": 0.5696, + "7qdv1xae7ikfe8dft3oj29yqc": 0.5692, + "dm5ka0os1e3dxcp3vh05kmp33": 0.5678, + "ay4u6j7lfkcg7x21mx5q121j": 0.5676, + "7af85xa75vozt2l4hzi6ryts7": 0.5663, + "5k620c7y6dlbmcm88dt3eb7t": 0.5644, + "ejunkmfhjz9weugd2bqrkgobb": 0.564, + "3428tckxcirwwh3o3jgc1m8ji": 0.5597, + "d6zovb8puwgcmsg91iya6rbtm": 0.5593, + "2wolc27r8z03itcvwp43e38c5": 0.5592, + "alpfd99yd3lfv7bhjo0biuq7b": 0.5582, + "beqqnubkv05mamuwvimeum015": 0.5577, + "4w7x0s5gfs5abasphlha5de8k": 0.5558, + "9ynnnx1qmkizq1o3qr3v0nsuk": 0.554, + "722fdbecxzcq9788l6jqclzlw": 0.5539, + "287tckirbfj9nb8ar2k9r60vn": 0.5529, + "esrunz7rjb0td98mx9e5cedoy": 0.5516, + "32n2r9bl6x90psj0wa7bfs6vq": 0.5487, + "50ap4sua1xyut3mpu7ehesp63": 0.5483, + "5c96g1zm7vo5ons9c42uy2w3r": 0.5469, + "3p81ltz6845appgkbgkzxueii": 0.5454, + "3n9mk5b2mxmq831wfmv6pu86i": 0.5437, + "5zr0b05eyx25km7z1k03ca9jx": 0.5424, + "1owhvvge4wlx7e0e431b4vhqx": 0.5423, + "3iwftmprsznl6yribr11a8l9m": 0.5393, + "7r1f93t6ddrsa5n8v1nq6qlzm": 0.5393, + "1gwajyt0pk2jm5fx5mu36v114": 0.5389, + "581t4mywybx21wcpmpykhyzr3": 0.5388, + "6wubmo7di3kdpflluf6s8c7vs": 0.5375, + "bq89wbdvedtov6auzuh6rsv7s": 0.5363, + "byu00jvt1j6csyv4y1lkt2fm2": 0.5359, + "af79lqrc0ntom74zq13ccjslo": 0.5357, + "3ri6juw2w6ma0jezszdlv1uqm": 0.5356, + "3l29w00m506ex93t5bbh9cg2a": 0.5355, + "1zp1du9n4rj36p1ss9zbxtqfb": 0.5353, + "9chuiarcjofld1dkj9kysehmb": 0.5346, + "5aw6uyw4pz2bpj24t5z8aacim": 0.5333, + "by5nibd18nkt40t0j8a0j5yzx": 0.5332, + "4yzidekywejmxxp77gqmdgopg": 0.5323, + "7ntvbsyq31jnzoqoa8850b9b8": 0.5305, + "a7247po5qs29o3zsfmt222ydu": 0.5299, + "117yqo02rs8dykkxpm274w3bd": 0.5298, + "193wqkyb0v5jnsblhvd2ocmyo": 0.5296, + "8jh0jejuxfhrpawnoztz2jlv4": 0.5295, + "5y0z0l2epprzbscvzsgldw8vu": 0.5288, + "47s2kt0e8m444ftqvsrqa3bvq": 0.5268, + "2hj3286pqov1g1g59k2t2qcgm": 0.5245, + "7swf4kpu3v38i2it4h94c5s9k": 0.5227, + "78wml3z5wrfxe5iky50tiotgu": 0.5196, + "f39uq10c8xhg5e6rwwcf6lhgc": 0.5186, + "bbajzna018c79opa1kl5kmkqo": 0.5172, + "4davonpqws4a4ejl1awu98zdg": 0.5168, + "1fedahp0rws09tj451onten8r": 0.5163, + "aho73e5udydy96iun3tkzdzsi": 0.5149, + "3aa4mumjl6zyetg6o9hwd5hhx": 0.5125, + "7cwemnr3vi40znjq451zxkus6": 0.5115, + "ajm86skyzse4ym8g6fpgzncxa": 0.5112, + "bgen5kjer2ytfp7lo9949t72g": 0.5102, + "8ey0ww2zsosdmwr8ehsorh6t7": 0.51, + "8najqkluatpaxvqws78b9s17c": 0.5082, + "8v97rcbthsxmzqk4ufxws9mug": 0.506, + "degxm4y6gmvp011ccyrev6z5p": 0.5049, + "3oa9e03e7w9nr8kqwqc3tlqz9": 0.5049, + "5dycj9wdhxh3n33qubw18ohlk": 0.5036, + "3is4bkgf3loxv9qfg3hm8zfqb": 0.5033, + "f47f3717z2vtpxfxrpdd4jl1x": 0.498, + "8ivsfwex4dfx1tvgsiq8askcx": 0.4972, + "8vbck9a4mxjms783lf72779uu": 0.4946, + "aql5z4osw5wmun0emnakfpwji": 0.4946, + "e6vzdkz6l236s9p288mharefy": 0.4925, + "4nidzmunvpvxk1ir9b6m8mpay": 0.4874, + "ein4fkggto3pdh5msp8huafiq": 0.4856, + "1q4ab2bpg5e8jl1g2udnakrju": 0.4852, + "8ztsv3pzrsyq5w1r3a0nfk1y5": 0.4842, + "1qd0wvt30rlswa4g6nu4na660": 0.4826, + "jznihqxle06xych9ygwiwnsa": 0.4796, + "2y8bntiif3a9y6gtmauv30gt": 0.4782, + "477yyajzheg2z8u7uick0e13e": 0.4706, + "bockl24qpr7ryjl8b6obukga": 0.4671, + "7mxwwunvot2pi69pj1yr1kh8i": 0.466, + "3w1hkk9k9gr8fwssyn4icvdfo": 0.4657, + "1txej2dzohnydl21zc9pgx6hy": 0.464, + "b8rae0ib0frjmwlca429bq19q": 0.4624, + "b5udgm9vakjqz8dcmy5b2g0xt": 0.4582, + "eitf7hulqfv1clb7toewkil24": 0.458, + "7hl0svs2hg225i2zud0g3xzp2": 0.4559, + "2aso72utuctat2ecs6nahjss6": 0.4521, + "3ymqchdzk8tt6lfphf26xfvh0": 0.4519, + "2yyjcbbryf1r10apyzl7c7jvp": 0.4507, + "bly7ema5au6j40i0grhl0pnub": 0.4476, + "b1rveez5u792gess9w3e7v5le": 0.4444, + "8sdpk4aerruf515yh76ezo7vi": 0.4434, + "32vph7vcjqgo1ksj1548di90n": 0.44, + "65ggsqdi6drpa4m8y3gkll25k": 0.4394, + "xaouuwuk8qyhv1libkeexwjh": 0.4347, + "6qitd9h242qkvjenaytfdnsf2": 0.4312, + "duuc1qczfnawwncru1ly6o66": 0.4213, + "b60nisd3qn427jm0hrg9kvmab": 0.4203, + "xwnjb1az11zffwty3m6vn8y6": 0.4197, + "dkarmrybx9vx10rg7cywumth0": 0.4158, + "75434tz9rc14xkkvudex742ui": 0.4137, + "c1d9p6b2e9zr5tqlzx3ktjplg": 0.4129, + "b73zounsynk9d3u1p9nvpu7i2": 0.4049, + "913mb508il6jzwtlj28fl892h": 0.4044, + "e0lck99w8meo9qoalfrxgo33o": 0.401, + "8dn0w8zh7nbn2i904603eigwf": 0.3984, + "ddyrh5latwfhesgfh4w401n92": 0.3973, + "avs3xposm3t9x1x2vzsoxzcbu": 0.3957, + "eu2g5j36zzxiazpd729osx0wm": 0.3924, + "67uya58idol2eq18ljecsru5o": 0.3912, + "23e698ls3x6vi9x8wl0mz7bsa": 0.3838, + "6321dlqv4ziuwqte4xpohijtw": 0.382, + "8o5tv5viv4hy1qg9jp94k7ayb": 0.381, + "53tknno09wqihmwxrqcuwq9sa": 0.3782, + "82wo38rqeizxlfjjhfjy4rx7u": 0.3781, + "dvtl8sf1262pd2aqgu641qa7u": 0.3767, + "663a54fmymndjeev47qm7d3nf": 0.3522, + "macko16888165594668885588": 0.3309, + "macko16698982162572521585": 0.3262, + "6lkj3o21cr4g7bql6tb3fk222": 0.3261, + "cu0rmpyff5692eo06ltddjo8a": 0.3161, + "1cnx2c8g3hhp8ssxnwwli0mjb": 0.3121, + "4vt0ldrcl6thpxpcs8zmpdq1g": 0.2926, + "etta63x1t7tnkn4jheisjwk4p": 0.2907, + "1n9l0ex47bu0762qg574hzjtd": 0.2626, + "6jgwiu2gq3dllmrwt45pfdn2z": 0.2416, + "392slbmf1kdqlr6sd1ckt71rs": 0.24, + "8z3180hhw2pj1i65uftlk54uz": 0.2096 + }, + "details": [ + { + "league_id": "bx57cmq1edfq53ckfk791supi", + "league_name": "CAF Konfederasyon Kupası", + "match_count": 98, + "brier_score": 0.3046, + "heavy_fav_win_pct": 84.1, + "fav_win_pct": 63.3, + "odds_reliability": 0.9476 + }, + { + "league_id": "55hcphd1ccc6eai1ms77460on", + "league_name": "Şampiyonlar Ligi Kadınlar", + "match_count": 89, + "brier_score": 0.3258, + "heavy_fav_win_pct": 83.3, + "fav_win_pct": 74.2, + "odds_reliability": 0.9445 + }, + { + "league_id": "d9eaigzyfnfiraqc3ius757tl", + "league_name": "Kupa", + "match_count": 78, + "brier_score": 0.3141, + "heavy_fav_win_pct": 81.2, + "fav_win_pct": 73.1, + "odds_reliability": 0.9402 + }, + { + "league_id": "1gxlzw2ezkyeykhcaa5x8ozkk", + "league_name": "Concacaf Orta Amerika Kupası", + "match_count": 88, + "brier_score": 0.3338, + "heavy_fav_win_pct": 79.4, + "fav_win_pct": 61.4, + "odds_reliability": 0.9259 + }, + { + "league_id": "5jd0k2txwnq69frs79eulba8j", + "league_name": "Kupa", + "match_count": 69, + "brier_score": 0.3223, + "heavy_fav_win_pct": 78.4, + "fav_win_pct": 66.7, + "odds_reliability": 0.9233 + }, + { + "league_id": "6694fff47wqxl10lrd9tb91f8", + "league_name": "Kupa", + "match_count": 55, + "brier_score": 0.3099, + "heavy_fav_win_pct": 78.8, + "fav_win_pct": 67.3, + "odds_reliability": 0.9193 + }, + { + "league_id": "4jg7he1n3rb5dniq6hf49xorq", + "league_name": "Premier Lig", + "match_count": 79, + "brier_score": 0.3333, + "heavy_fav_win_pct": 77.1, + "fav_win_pct": 64.6, + "odds_reliability": 0.9061 + }, + { + "league_id": "59tpnfrwnvhnhzmnvfyug68hj", + "league_name": "Libertadores Kupası", + "match_count": 180, + "brier_score": 0.3408, + "heavy_fav_win_pct": 76.2, + "fav_win_pct": 61.7, + "odds_reliability": 0.8988 + }, + { + "league_id": "ac42gi3penartj88fe9l6plpk", + "league_name": "Premier Lig", + "match_count": 185, + "brier_score": 0.3148, + "heavy_fav_win_pct": 70.7, + "fav_win_pct": 68.1, + "odds_reliability": 0.8937 + }, + { + "league_id": "3j81qr7yc4gdnakfwnxf95ovh", + "league_name": "Premier Lig", + "match_count": 106, + "brier_score": 0.333, + "heavy_fav_win_pct": 72.2, + "fav_win_pct": 60.4, + "odds_reliability": 0.8771 + }, + { + "league_id": "9z5643nd06afqu01ea2wt8y4g", + "league_name": "Kuu Bara Ligi", + "match_count": 110, + "brier_score": 0.3294, + "heavy_fav_win_pct": 70.3, + "fav_win_pct": 53.6, + "odds_reliability": 0.8734 + }, + { + "league_id": "482ofyysbdbeoxauk19yg7tdt", + "league_name": "Trendyol Süper Lig", + "match_count": 342, + "brier_score": 0.3627, + "heavy_fav_win_pct": 80.7, + "fav_win_pct": 59.6, + "odds_reliability": 0.8722 + }, + { + "league_id": "ahl3vljaignq9ebaos4uqkrvo", + "league_name": "Kupa", + "match_count": 105, + "brier_score": 0.331, + "heavy_fav_win_pct": 70.4, + "fav_win_pct": 63.8, + "odds_reliability": 0.8696 + }, + { + "league_id": "8x3sbh85gc8qir50utw39jl04", + "league_name": "UEFA Kadınlar Euro 2025 Elemeleri", + "match_count": 88, + "brier_score": 0.3421, + "heavy_fav_win_pct": 75.5, + "fav_win_pct": 61.4, + "odds_reliability": 0.865 + }, + { + "league_id": "agpweohvn9tugnyl6ry4rhivp", + "league_name": "Eredivisie Kadınlar", + "match_count": 51, + "brier_score": 0.3356, + "heavy_fav_win_pct": 72.0, + "fav_win_pct": 56.9, + "odds_reliability": 0.8428 + }, + { + "league_id": "4c1nfi2j1m731hcay25fcgndq", + "league_name": "Avrupa Ligi", + "match_count": 242, + "brier_score": 0.3625, + "heavy_fav_win_pct": 77.6, + "fav_win_pct": 61.6, + "odds_reliability": 0.8425 + }, + { + "league_id": "1j4ehtrbry9depwt6oghaq3lu", + "league_name": "Süper Lig", + "match_count": 84, + "brier_score": 0.3201, + "heavy_fav_win_pct": 65.9, + "fav_win_pct": 60.7, + "odds_reliability": 0.8299 + }, + { + "league_id": "40yjcbx2sq6oq736iqqqczwt1", + "league_name": "DK Elemeler", + "match_count": 88, + "brier_score": 0.3383, + "heavy_fav_win_pct": 68.6, + "fav_win_pct": 55.7, + "odds_reliability": 0.8237 + }, + { + "league_id": "145hkd59i6foieuwr4mwi6wlq", + "league_name": "Pro Lig", + "match_count": 143, + "brier_score": 0.3546, + "heavy_fav_win_pct": 73.8, + "fav_win_pct": 60.1, + "odds_reliability": 0.823 + }, + { + "league_id": "34pl8szyvrbwcmfkuocjm3r6t", + "league_name": "LaLiga", + "match_count": 364, + "brier_score": 0.3773, + "heavy_fav_win_pct": 80.2, + "fav_win_pct": 56.6, + "odds_reliability": 0.8227 + }, + { + "league_id": "cse5oqqt2pzfcy8uz6yz3tkbj", + "league_name": "CAF Şampiyonlar Ligi", + "match_count": 91, + "brier_score": 0.3513, + "heavy_fav_win_pct": 73.9, + "fav_win_pct": 57.1, + "odds_reliability": 0.8212 + }, + { + "league_id": "zs18qaehvhg3w1208874zvfa", + "league_name": "1. Lig", + "match_count": 225, + "brier_score": 0.3744, + "heavy_fav_win_pct": 82.1, + "fav_win_pct": 59.6, + "odds_reliability": 0.8176 + }, + { + "league_id": "57nu0wygurzkp6fuy5hhrtaa2", + "league_name": "1. Lig", + "match_count": 286, + "brier_score": 0.3626, + "heavy_fav_win_pct": 72.9, + "fav_win_pct": 59.1, + "odds_reliability": 0.8099 + }, + { + "league_id": "1eruend45vd20g9hbrpiggs5u", + "league_name": "Botola Pro", + "match_count": 265, + "brier_score": 0.3625, + "heavy_fav_win_pct": 72.9, + "fav_win_pct": 50.2, + "odds_reliability": 0.8083 + }, + { + "league_id": "595nsvo7ykvoe690b1e4u5n56", + "league_name": "UEFA Uluslar Ligi", + "match_count": 67, + "brier_score": 0.3687, + "heavy_fav_win_pct": 83.3, + "fav_win_pct": 50.7, + "odds_reliability": 0.7987 + }, + { + "league_id": "6vq8j5p3av14nr3iuyi4okhjt", + "league_name": "Süper Lig Kadınlar", + "match_count": 70, + "brier_score": 0.356, + "heavy_fav_win_pct": 73.5, + "fav_win_pct": 58.6, + "odds_reliability": 0.793 + }, + { + "league_id": "486rhdgz7yc0sygziht7hje65", + "league_name": "Kupa", + "match_count": 62, + "brier_score": 0.3704, + "heavy_fav_win_pct": 81.1, + "fav_win_pct": 66.1, + "odds_reliability": 0.7901 + }, + { + "league_id": "9hh6n2f84k31zmlcxyvmc1w2y", + "league_name": "2. Lig", + "match_count": 204, + "brier_score": 0.357, + "heavy_fav_win_pct": 69.2, + "fav_win_pct": 62.3, + "odds_reliability": 0.789 + }, + { + "league_id": "3n5046abeu3x482ds3jwda238", + "league_name": "WE Lig Kadınlar", + "match_count": 102, + "brier_score": 0.3761, + "heavy_fav_win_pct": 85.4, + "fav_win_pct": 58.8, + "odds_reliability": 0.7863 + }, + { + "league_id": "8yi6ejjd1zudcqtbn07haahg6", + "league_name": "Premier Lig", + "match_count": 302, + "brier_score": 0.3712, + "heavy_fav_win_pct": 72.1, + "fav_win_pct": 56.3, + "odds_reliability": 0.7752 + }, + { + "league_id": "byhmntnl1b4lxw0zz21im3zkd", + "league_name": "Kupa", + "match_count": 96, + "brier_score": 0.3528, + "heavy_fav_win_pct": 68.2, + "fav_win_pct": 58.3, + "odds_reliability": 0.7719 + }, + { + "league_id": "2bmwykmdlcc2u1c40ytoc39vy", + "league_name": "Açık Kupası", + "match_count": 93, + "brier_score": 0.3807, + "heavy_fav_win_pct": 84.6, + "fav_win_pct": 66.7, + "odds_reliability": 0.7668 + }, + { + "league_id": "82jkgccg7phfjpd0mltdl3pat", + "league_name": "Süper Lig", + "match_count": 289, + "brier_score": 0.3782, + "heavy_fav_win_pct": 74.0, + "fav_win_pct": 57.4, + "odds_reliability": 0.7643 + }, + { + "league_id": "2nttcoriwf5co73vmz1vr8frm", + "league_name": "Nesine 2. Lig", + "match_count": 525, + "brier_score": 0.3782, + "heavy_fav_win_pct": 71.8, + "fav_win_pct": 55.2, + "odds_reliability": 0.7641 + }, + { + "league_id": "dr2xk7muj8aqcjdz2b3li1c0k", + "league_name": "Meistaradeildin", + "match_count": 129, + "brier_score": 0.3714, + "heavy_fav_win_pct": 73.6, + "fav_win_pct": 61.2, + "odds_reliability": 0.759 + }, + { + "league_id": "4yngyfinzd6bb1k7anqtqs0wt", + "league_name": "Premier Lig", + "match_count": 195, + "brier_score": 0.3772, + "heavy_fav_win_pct": 74.4, + "fav_win_pct": 57.4, + "odds_reliability": 0.7586 + }, + { + "league_id": "eog6knrkfei68si736fpquyzc", + "league_name": "Lig Kupası", + "match_count": 120, + "brier_score": 0.3632, + "heavy_fav_win_pct": 69.9, + "fav_win_pct": 66.7, + "odds_reliability": 0.756 + }, + { + "league_id": "eg6s9f1jj7jr6stmbosn0g6c8", + "league_name": "Süper Lig", + "match_count": 108, + "brier_score": 0.3657, + "heavy_fav_win_pct": 71.2, + "fav_win_pct": 55.6, + "odds_reliability": 0.7538 + }, + { + "league_id": "ae1wva3zrzcp2zd15gpvsntg6", + "league_name": "Ulusal Lig", + "match_count": 278, + "brier_score": 0.3783, + "heavy_fav_win_pct": 72.7, + "fav_win_pct": 55.0, + "odds_reliability": 0.7517 + }, + { + "league_id": "cesdwwnxbc5fmajgroc0hqzy2", + "league_name": "Hazırlık Maçları Ülkeler", + "match_count": 235, + "brier_score": 0.3669, + "heavy_fav_win_pct": 67.6, + "fav_win_pct": 56.2, + "odds_reliability": 0.7466 + }, + { + "league_id": "8k1xcsyvxapl4jlsluh3eomre", + "league_name": "Premier Lig", + "match_count": 328, + "brier_score": 0.385, + "heavy_fav_win_pct": 74.2, + "fav_win_pct": 45.7, + "odds_reliability": 0.7463 + }, + { + "league_id": "bdtat25m14jy85y484z3e6lf", + "league_name": "Kupa", + "match_count": 90, + "brier_score": 0.3772, + "heavy_fav_win_pct": 75.7, + "fav_win_pct": 55.6, + "odds_reliability": 0.7437 + }, + { + "league_id": "iu1vi94p4p28oozl1h9bvplr", + "league_name": "1. Lig", + "match_count": 158, + "brier_score": 0.3729, + "heavy_fav_win_pct": 71.2, + "fav_win_pct": 50.0, + "odds_reliability": 0.7411 + }, + { + "league_id": "1r097lpxe0xn03ihb7wi98kao", + "league_name": "Serie A", + "match_count": 359, + "brier_score": 0.3732, + "heavy_fav_win_pct": 67.8, + "fav_win_pct": 56.5, + "odds_reliability": 0.7391 + }, + { + "league_id": "2kwbbcootiqqgmrzs6o5inle5", + "league_name": "Premier Lig", + "match_count": 369, + "brier_score": 0.3791, + "heavy_fav_win_pct": 70.2, + "fav_win_pct": 54.2, + "odds_reliability": 0.7386 + }, + { + "league_id": "9fuwphq8kvugrlc3ckm7k8wes", + "league_name": "Ligler Kupası", + "match_count": 143, + "brier_score": 0.3934, + "heavy_fav_win_pct": 81.6, + "fav_win_pct": 50.3, + "odds_reliability": 0.7358 + }, + { + "league_id": "civf31q1inxohs4a03y8reetf", + "league_name": "Premier Lig", + "match_count": 320, + "brier_score": 0.3721, + "heavy_fav_win_pct": 67.2, + "fav_win_pct": 57.8, + "odds_reliability": 0.735 + }, + { + "league_id": "ili150pwfuf39f7yfdch9lhw", + "league_name": "UEFA U21 Şampiyonası Elemeler", + "match_count": 112, + "brier_score": 0.3715, + "heavy_fav_win_pct": 70.4, + "fav_win_pct": 67.9, + "odds_reliability": 0.7286 + }, + { + "league_id": "abs7n2ae3oydilk0tgmpnsj89", + "league_name": "Azadegan Ligi", + "match_count": 217, + "brier_score": 0.3801, + "heavy_fav_win_pct": 71.4, + "fav_win_pct": 45.2, + "odds_reliability": 0.7277 + }, + { + "league_id": "9nbpdi9q3ywcm4q0j5u0ekwcq", + "league_name": "Serie D", + "match_count": 232, + "brier_score": 0.3718, + "heavy_fav_win_pct": 67.2, + "fav_win_pct": 54.7, + "odds_reliability": 0.7254 + } + ] +} \ No newline at end of file diff --git a/parse_errors.py b/parse_errors.py new file mode 100644 index 0000000..db37379 --- /dev/null +++ b/parse_errors.py @@ -0,0 +1,17 @@ +import json + +targets = [ + "bet_recommender.py", "score_calculator.py", "db.py", "upset_engine_v2.py", + "v20_ensemble.py", "v27_predictor.py", "betting_brain.py", + "single_match_orchestrator.py", "v26_shadow_engine.py" +] + +d = json.load(open("pyright_main_errors.json", encoding="utf-16")) +for diag in d["generalDiagnostics"]: + if diag["severity"] == "error": + fname = diag["file"] + if any(t in fname for t in targets): + # Print safely encoding to ascii to avoid charmap errors + safe_fname = fname.split('ai-engine')[1].encode('ascii', 'ignore').decode() + safe_msg = diag["message"].encode('ascii', 'ignore').decode() + print(f"{safe_fname} L{diag['range']['start']['line']+1}: {safe_msg}")