diff options
Diffstat (limited to 'src/scorecorder.zig')
-rw-r--r-- | src/scorecorder.zig | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/src/scorecorder.zig b/src/scorecorder.zig new file mode 100644 index 0000000..9d83d1f --- /dev/null +++ b/src/scorecorder.zig @@ -0,0 +1,174 @@ +const Round = struct { + events: []Event, +}; + +const Event = struct { + strEvent: []u8, + idHomeTeam: []u8, + strHomeTeam: []u8, + idAwayTeam: []u8, + strAwayTeam: []u8, + intHomeScore: ?[]u8 = null, + intAwayScore: ?[]u8 = null, + strStatus: []u8, + dateEvent: []u8, + strTimestamp: []u8, +}; + +const Config = struct { + round: i16, + season: i16, + all_rounds: bool = false, +}; + +var app: App = undefined; +var config: Config = undefined; +var client: zul.http.Client = undefined; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + // initialize a logging pool + try logz.setup(allocator, .{ + .level = .Info, + .pool_size = 100, + .buffer_size = 4096, + .large_buffer_count = 8, + .large_buffer_size = 16384, + .output = .stdout, + .encoding = .logfmt, + }); + defer logz.deinit(); + + app = try App.init(allocator); + defer app.deinit(); + + var args = try zul.CommandLineArgs.parse(allocator); + defer args.deinit(); + + client = zul.http.Client.init(allocator); + defer client.deinit(); + + config = .{ + .round = try weekToRound(if (args.contains("round")) try std.fmt.parseInt(i16, args.get("round").?, 10) else currentRound()), + .season = if (args.contains("season")) try std.fmt.parseInt(i16, args.get("season").?, 10) else currentSeason(), + .all_rounds = args.contains("all"), + }; + logz.info() + .int("round", config.round) + .int("season", config.season) + .boolean("all_rounds", config.all_rounds) + .log(); + + if (config.all_rounds) { + for (1..23) |round| { + try recordRound(allocator, @intCast(round)); + std.posix.nanosleep(1, 0); + } + } else { + try recordRound(allocator, config.round); + } +} + +fn recordRound(allocator: std.mem.Allocator, round: i16) !void { + var req = try client.request("https://www.thesportsdb.com/api/v1/json/3/eventsround.php"); + defer req.deinit(); + // id is the league id, with 4391 == NFL + try req.query("id", "4391"); + // the round - which corresponds to the week or playoff id + try req.query("r", try std.fmt.allocPrint(allocator, "{d}", .{round})); + // the season year + try req.query("s", try std.fmt.allocPrint(allocator, "{d}", .{config.season})); + + const res = try req.getResponse(.{}); + if (res.status != 200) { + // TODO: handle error + return; + } + var managed = try res.json(Round, allocator, .{ .ignore_unknown_fields = true }); + defer managed.deinit(); + const parsed_round = managed.value; + for (parsed_round.events) |event| { + logz.info() + .fmt("event", "{s} {s} @ {s} {s}", .{ event.strAwayTeam, event.intAwayScore orelse "0", event.strHomeTeam, event.intHomeScore orelse "0" }) + .string("date", event.strTimestamp) + .string("status", event.strStatus) + .log(); + + if (!std.mem.eql(u8, "", event.strStatus) and !std.mem.eql(u8, "FT", event.strStatus) and !std.mem.eql(u8, "AOT", event.strStatus) and !std.mem.eql(u8, "Match Finished", event.strStatus)) { + logz.info().fmt("event", "{s} in progress or not started.", .{event.strEvent}).log(); + continue; + } + + const home_score = try std.fmt.parseInt(usize, event.intHomeScore orelse "0", 10); + const away_score = try std.fmt.parseInt(usize, event.intAwayScore orelse "0", 10); + + const winner_id: []u8 = if (home_score > away_score) event.idHomeTeam else event.idAwayTeam; + + logz.info().string("winner", winner_id).log(); + try insertScore(winner_id, "win", event.strTimestamp); + + if (config.round == 160 or config.round == 125) { + logz.info().string("playoffs", "Home Team made it to the playoffs").log(); + try insertScore(event.idHomeTeam, "playoffs", event.strTimestamp); + logz.info().string("playoffs", "Away Team made it to the playoffs").log(); + try insertScore(event.idAwayTeam, "playoffs", event.strTimestamp); + } + + const category = switch (config.round) { + 160 => "divisional", + 125 => "conference", + 150 => "superbowl", + 200 => "champion", + else => null, + }; + + if (category) |c| { + logz.info().fmt("category", "Inserting {s} score for winner", .{c}).log(); + try insertScore(winner_id, c, event.strTimestamp); + } + } +} + +pub fn insertScore(external_id: []u8, category: []const u8, timestamp: []const u8) !void { + _ = app.pool.exec("SELECT record_external_score_by_year($1, $2, $3, $4, $5)", .{ external_id, config.season, config.round, @constCast(category), timestamp }) catch @panic("failed to record score"); +} + +fn currentRound() i16 { + var current_week = (app.pool.row("SELECT current_week()", .{}) catch @panic("can't fetch current week")) orelse unreachable; + defer current_week.deinit() catch {}; + return current_week.get(i16, 0); +} + +fn currentSeason() i16 { + const season_query = + \\SELECT date_part('year', started_at)::smallint + \\FROM seasons WHERE season_id = current_season_id(); + ; + var current_season = (app.pool.row(season_query, .{}) catch @panic("can't fetch current season")) orelse unreachable; + defer current_season.deinit() catch {}; + return current_season.get(i16, 0); +} + +fn weekToRound(week: i16) !i16 { + return switch (week) { + 1...18 => |w| w, + 19 => 160, + 20 => 125, + 21 => 150, + 23 => 200, + else => return error.UnknownRound, + }; +} + +test "weekToRound" { + try std.testing.expect(try weekToRound(1) == 1); + try std.testing.expectError(error.UnknownRound, weekToRound(55)); +} + +const App = @import("App.zig"); + +const logz = @import("logz"); +const std = @import("std"); +const zul = @import("zul"); |