Skip to content

Commit e48a315

Browse files
committed
define milestones in db
1 parent 82bc59c commit e48a315

3 files changed

Lines changed: 126 additions & 2 deletions

File tree

src/libkernelbot/db_types.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,12 @@ class SubmissionItem(TypedDict):
5656
runs: List[RunItem]
5757

5858

59-
__all__ = [LeaderboardItem, LeaderboardRankedEntry, RunItem, SubmissionItem]
59+
class MilestoneItem(TypedDict):
60+
id: int
61+
name: str
62+
code: str
63+
description: str
64+
created_at: datetime.datetime
65+
66+
67+
__all__ = [LeaderboardItem, LeaderboardRankedEntry, RunItem, SubmissionItem, MilestoneItem]

src/libkernelbot/leaderboard_db.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55

66
import psycopg2
77

8-
from libkernelbot.db_types import LeaderboardItem, LeaderboardRankedEntry, RunItem, SubmissionItem
8+
from libkernelbot.db_types import (
9+
LeaderboardItem,
10+
LeaderboardRankedEntry,
11+
MilestoneItem,
12+
RunItem,
13+
SubmissionItem,
14+
)
915
from libkernelbot.run_eval import CompileResult, RunResult, SystemInfo
1016
from libkernelbot.task import LeaderboardDefinition, LeaderboardTask
1117
from libkernelbot.utils import (
@@ -238,6 +244,55 @@ def delete_leaderboard(self, leaderboard_name: str, force: bool = False):
238244
logger.exception("Could not delete leaderboard %s.", leaderboard_name, exc_info=e)
239245
raise KernelBotError(f"Could not delete leaderboard {leaderboard_name}.") from e
240246

247+
def create_milestone(
248+
self,
249+
leaderboard_id: int,
250+
name: str,
251+
code: str,
252+
description: str = None,
253+
) -> int:
254+
"""Create a new milestone for a leaderboard"""
255+
try:
256+
self.cursor.execute(
257+
"""
258+
INSERT INTO leaderboard.milestones (
259+
leaderboard_id, name, code, description
260+
)
261+
VALUES (%s, %s, %s, %s)
262+
RETURNING id
263+
""",
264+
(leaderboard_id, name, code, description),
265+
)
266+
milestone_id = self.cursor.fetchone()[0]
267+
self.connection.commit()
268+
return milestone_id
269+
except psycopg2.Error as e:
270+
self.connection.rollback()
271+
logger.exception("Error creating milestone", exc_info=e)
272+
raise KernelBotError("Error creating milestone") from e
273+
274+
def get_leaderboard_milestones(self, leaderboard_id: int) -> "list[MilestoneItem]":
275+
"""Get all milestones for a leaderboard"""
276+
self.cursor.execute(
277+
"""
278+
SELECT id, name, code, description, created_at
279+
FROM leaderboard.milestones
280+
WHERE leaderboard_id = %s
281+
ORDER BY created_at
282+
""",
283+
(leaderboard_id,),
284+
)
285+
return [
286+
{
287+
"id": row[0],
288+
"name": row[1],
289+
"code": row[2],
290+
"description": row[3],
291+
"created_at": row[4],
292+
}
293+
for row in self.cursor.fetchall()
294+
]
295+
241296
def create_submission(
242297
self,
243298
leaderboard: str,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Add milestone table for better milestone tracking
3+
"""
4+
5+
from yoyo import step
6+
7+
__depends__ = {"20250617_01_c5mrF-task-split"} # Update to latest migration
8+
9+
steps = [
10+
step(
11+
"""
12+
CREATE TABLE IF NOT EXISTS leaderboard.milestones (
13+
id SERIAL PRIMARY KEY,
14+
leaderboard_id INTEGER NOT NULL REFERENCES leaderboard.leaderboard(id) ON DELETE CASCADE,
15+
name TEXT NOT NULL,
16+
code TEXT NOT NULL,
17+
description TEXT,
18+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
19+
UNIQUE(leaderboard_id, name)
20+
)
21+
""",
22+
"DROP TABLE leaderboard.milestones",
23+
),
24+
step("CREATE INDEX ON leaderboard.milestones (leaderboard_id)"),
25+
# add alternative ID column that references milestones;
26+
# we don't really care about being careful preserving milestone
27+
# runs, so we can simply DELETE CASCADE
28+
step(
29+
"""
30+
ALTER TABLE leaderboard.runs
31+
ADD COLUMN milestone_id INTEGER REFERENCES leaderboard.milestones(id) ON DELETE CASCADE;
32+
""",
33+
"ALTER TABLE leaderboard.runs DROP COLUMN milestone_id;",
34+
),
35+
# as we now have two possible ids, exactly one of them can be NULL
36+
step(
37+
"ALTER TABLE leaderboard.runs ALTER COLUMN submission_id DROP NOT NULL;",
38+
"""
39+
DELETE FROM leaderboard.runs WHERE submission_id IS NULL;
40+
ALTER TABLE leaderboard.runs ALTER COLUMN submission_id SET NOT NULL;
41+
""",
42+
),
43+
step(
44+
"""
45+
ALTER TABLE leaderboard.runs
46+
ADD CONSTRAINT runs_single_parent CHECK (
47+
(submission_id IS NOT NULL AND milestone_id IS NULL) OR
48+
(submission_id IS NULL AND milestone_id IS NOT NULL)
49+
);
50+
""",
51+
"ALTER TABLE leaderboard.runs DROP CONSTRAINT runs_single_parent;",
52+
),
53+
# ensure we have fast indexing for regular submissions
54+
step(
55+
"""
56+
CREATE INDEX IF NOT EXISTS runs_submission_id_idx ON leaderboard.runs(submission_id)
57+
WHERE submission_id IS NOT NULL;
58+
""",
59+
"DROP INDEX IF EXISTS leaderboard.runs_submission_id_idx",
60+
),
61+
]

0 commit comments

Comments
 (0)