|
| 1 | +from collections import defaultdict |
| 2 | + |
1 | 3 | from pydantic import TypeAdapter |
2 | 4 | from sqlalchemy import select |
3 | 5 | from sqlalchemy.orm import Session |
@@ -36,61 +38,45 @@ async def get_latest_currency_id(self, db: Session) -> int: |
36 | 38 |
|
37 | 39 | return validate(latest_currency_id) |
38 | 40 |
|
39 | | - async def get_latest_hour(self, db: Session) -> int: |
40 | | - stmt = ( |
41 | | - select(model_Currency.createdHoursSinceLaunch) |
42 | | - .order_by(model_Currency.currencyId.desc()) |
43 | | - .limit(1) |
| 41 | + def _latest_hours_stmt(self, league_ids: list[int]): |
| 42 | + return ( |
| 43 | + select( |
| 44 | + model_Currency.leagueId, |
| 45 | + func.max(model_Currency.createdHoursSinceLaunch).label("latest_hour"), |
| 46 | + ) |
| 47 | + .where(model_Currency.leagueId.in_(league_ids)) |
| 48 | + .group_by(model_Currency.leagueId) |
44 | 49 | ) |
45 | 50 |
|
46 | | - db_latest_hour = db.execute(stmt).mappings().first() |
47 | | - if db_latest_hour is not None: |
48 | | - latest_hour = db_latest_hour["createdHoursSinceLaunch"] |
49 | | - else: |
50 | | - latest_hour = -1 |
| 51 | + async def get_latest_hours( |
| 52 | + self, db: Session, league_ids: list[int] |
| 53 | + ) -> dict[int, int]: |
| 54 | + stmt = self._latest_hours_stmt(league_ids) |
51 | 55 |
|
52 | | - validate = TypeAdapter(int).validate_python |
| 56 | + objs = db.execute(stmt).mappings().all() |
| 57 | + id_hour_map = {obj["leagueId"]: obj["latest_hour"] for obj in objs} |
53 | 58 |
|
54 | | - return validate(latest_hour) |
| 59 | + validate = TypeAdapter(dict[int, int]).validate_python |
55 | 60 |
|
56 | | - async def get_latest_currencies(self, db: Session) -> list[Currency]: |
57 | | - latest_hour = await self.get_latest_hour(db) |
| 61 | + return validate(id_hour_map) |
58 | 62 |
|
59 | | - # All rows from that hour, plus the next lower currencyId |
60 | | - hour_rows = ( |
61 | | - select( |
62 | | - model_Currency.currencyId, |
63 | | - model_Currency.createdHoursSinceLaunch, |
64 | | - func.lead(model_Currency.currencyId) |
65 | | - .over(order_by=model_Currency.currencyId.desc()) |
66 | | - .label("next_id"), |
67 | | - ).where(model_Currency.createdHoursSinceLaunch == latest_hour) |
68 | | - ).cte("hour_rows") |
69 | | - |
70 | | - # The first place where the IDs stop being consecutive |
71 | | - boundary = ( |
72 | | - select(hour_rows.c.next_id) |
73 | | - .where(hour_rows.c.currencyId - hour_rows.c.next_id > 1) |
74 | | - .order_by(hour_rows.c.currencyId.desc()) |
75 | | - .limit(1) |
76 | | - .scalar_subquery() |
77 | | - ) |
78 | | - # If no gap exists, include all rows for that hour |
79 | | - min_id = func.coalesce( |
80 | | - boundary, |
81 | | - select(func.min(hour_rows.c.currencyId)).scalar_subquery(), |
82 | | - ) |
83 | | - stmt = ( |
84 | | - select(model_Currency.__table__) |
85 | | - .where( |
86 | | - model_Currency.createdHoursSinceLaunch == latest_hour, |
87 | | - model_Currency.currencyId >= min_id, |
88 | | - ) |
89 | | - .order_by(model_Currency.currencyId.desc()) |
| 63 | + async def get_latest_currencies( |
| 64 | + self, db: Session, league_ids: list[int] |
| 65 | + ) -> dict[int, list[Currency]]: |
| 66 | + latest_hours = self._latest_hours_stmt(league_ids).subquery() |
| 67 | + |
| 68 | + stmt = select(model_Currency).join( |
| 69 | + latest_hours, |
| 70 | + (model_Currency.leagueId == latest_hours.c.leagueId) |
| 71 | + & (model_Currency.createdHoursSinceLaunch == latest_hours.c.latest_hour), |
90 | 72 | ) |
91 | | - latest_currencies = db.execute(stmt).mappings().all() |
| 73 | + currencies = db.scalars(stmt).all() |
92 | 74 |
|
93 | | - validate = TypeAdapter(list[Currency]).validate_python |
| 75 | + latest_currencies: dict[int, list[Currency]] = defaultdict(list) |
| 76 | + for currency in currencies: |
| 77 | + latest_currencies[currency.leagueId].append(currency) |
| 78 | + |
| 79 | + validate = TypeAdapter(dict[int, list[Currency]]).validate_python |
94 | 80 |
|
95 | 81 | return validate(latest_currencies) |
96 | 82 |
|
|
0 commit comments