summaryrefslogtreecommitdiffstats
path: root/src/scorecorder.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/scorecorder.zig')
-rw-r--r--src/scorecorder.zig174
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");