From a7b20d997eae3f6ed26f81073acc2b32681cd2c8 Mon Sep 17 00:00:00 2001 From: StewKI Date: Sun, 17 May 2026 16:34:08 +0200 Subject: [PATCH] Seed script --- scripts/seed.cs | 489 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 scripts/seed.cs diff --git a/scripts/seed.cs b/scripts/seed.cs new file mode 100644 index 0000000..2fcdbfe --- /dev/null +++ b/scripts/seed.cs @@ -0,0 +1,489 @@ +#:package MySqlConnector@2.3.7 +#:package CsvHelper@33.0.1 + +using System.Globalization; +using CsvHelper; +using CsvHelper.Configuration; +using MySqlConnector; + +// ── Config ──────────────────────────────────────────────────────────────────── + +const string Dsn = "Server=127.0.0.1;Port=13306;Database=ewc2025;Uid=root;Pwd=ewc2025root;"; + +static string FindDataDir() +{ + var dir = Directory.GetCurrentDirectory(); + while (dir is not null) + { + var candidate = Path.Combine(dir, "data"); + if (Directory.Exists(candidate) && + Directory.GetFiles(candidate, "01_EWC2025*").Length > 0) + return candidate; + dir = Path.GetDirectoryName(dir); + } + throw new DirectoryNotFoundException("Cannot find 'data' directory with EWC2025 CSV files."); +} + +var dataDir = FindDataDir(); + +// Games_Competing and Games_Won in the CSVs use abbreviated names that don't +// always match the canonical name stored in the game table. +var gameAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) +{ + ["Mobile Legends"] = "Mobile Legends: Bang Bang", + ["PUBG"] = "PUBG: Battlegrounds", + ["Mobile Legends: Bang Bang - Men"] = "Mobile Legends: Bang Bang", + ["Mobile Legends: Bang Bang - Women"] = "Mobile Legends: Bang Bang", + ["Naraka: Bladepoint - Solo"] = "Naraka: Bladepoint", + ["Naraka: Bladepoint - Trios"] = "Naraka: Bladepoint", +}; + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +await using var conn = new MySqlConnection(Dsn); +await conn.OpenAsync(); +Console.WriteLine("Connected to MySQL."); + +List> ReadCsv(string filename) +{ + using var reader = new StreamReader(Path.Combine(dataDir, filename)); + using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture) + { + MissingFieldFound = null, + HeaderValidated = null, + }); + var rows = new List>(); + csv.Read(); + csv.ReadHeader(); + while (csv.Read()) + { + var row = new Dictionary(); + foreach (var h in csv.HeaderRecord!) + row[h] = csv.GetField(h) ?? ""; + rows.Add(row); + } + return rows; +} + +async Task Insert(string sql, Dictionary p) +{ + await using var cmd = new MySqlCommand(sql, conn); + foreach (var (k, v) in p) + cmd.Parameters.AddWithValue(k, v ?? DBNull.Value); + await cmd.ExecuteNonQueryAsync(); + return cmd.LastInsertedId; +} + +static string? Nullable(string s) => string.IsNullOrWhiteSpace(s) ? null : s.Trim(); +static int? NullInt(string s) => int.TryParse(s.Trim(), out var v) ? v : null; +static decimal? NullDec(string s) => decimal.TryParse(s.Trim(), NumberStyles.Any, + CultureInfo.InvariantCulture, out var v) ? v : null; +static bool YesNo(string s) => s.Trim().Equals("Yes", StringComparison.OrdinalIgnoreCase); + +// Strip gendered/variant suffix to get the canonical game name used in file 01 +string CanonicalGame(string name) +{ + if (gameAliases.TryGetValue(name.Trim(), out var mapped)) return mapped; + return name.Trim(); +} + +// ── Load CSVs ───────────────────────────────────────────────────────────────── + +var rows01 = ReadCsv("01_EWC2025_Event_Tournament_Summary.csv"); +var rows02 = ReadCsv("02_EWC2025_Medalists.csv"); +var rows03 = ReadCsv("03_EWC2025_Club_Championship_Standings.csv"); +var rows04 = ReadCsv("04_EWC2025_Club_Partner_Program.csv"); +var rows05 = ReadCsv("05_EWC2025_Player_Roster.csv"); +var rows06 = ReadCsv("06_EWC2025_Prize_Pool_Distribution.csv"); +var rows07 = ReadCsv("07_EWC2025_Calendar_Schedule.csv"); +var rows08 = ReadCsv("08_EWC2025_Country_Results.csv"); +var rows09 = ReadCsv("09_EWC2025_Point_System.csv"); +var rows10 = ReadCsv("10_EWC2025_Game_by_Game_Results.csv"); + +// ── 1. game ─────────────────────────────────────────────────────────────────── + +Console.WriteLine("[1/13] game"); +var gameId = new Dictionary(StringComparer.OrdinalIgnoreCase); + +foreach (var r in rows01) +{ + var name = r["Game"].Trim(); + if (gameId.ContainsKey(name)) continue; + gameId[name] = await Insert( + "INSERT INTO game (name, game_type, platform) VALUES (@n, @gt, @pl)", + new() { ["@n"] = name, ["@gt"] = r["Game_Type"].Trim(), ["@pl"] = r["Platform"].Trim() }); +} + +// ── 2. country ──────────────────────────────────────────────────────────────── + +Console.WriteLine("[2/13] country"); +var countryId = new Dictionary(StringComparer.OrdinalIgnoreCase); + +foreach (var r in rows08) +{ + var name = r["Country"].Trim(); + countryId[name] = await Insert( + """ + INSERT INTO country + (name, region, gold_medals, silver_medals, bronze_medals, total_medals, total_players, top_game) + VALUES (@n, @reg, @g, @s, @b, @t, @pl, @tg) + """, + new() + { + ["@n"] = name, + ["@reg"] = r["Region"].Trim(), + ["@g"] = int.Parse(r["Gold_Medals"]), + ["@s"] = int.Parse(r["Silver_Medals"]), + ["@b"] = int.Parse(r["Bronze_Medals"]), + ["@t"] = int.Parse(r["Total_Medals"]), + ["@pl"] = int.Parse(r["Total_Players"]), + ["@tg"] = Nullable(r["Top_Game"]), + }); +} + +async Task EnsureCountry(string name) +{ + if (countryId.TryGetValue(name, out var id)) return id; + id = await Insert( + "INSERT INTO country (name, region) VALUES (@n, 'Unknown')", + new() { ["@n"] = name }); + countryId[name] = id; + return id; +} + +// ── 3. point_system ─────────────────────────────────────────────────────────── + +Console.WriteLine("[3/13] point_system"); +foreach (var r in rows09) +{ + await Insert( + "INSERT INTO point_system (placement, points, system_type, description) VALUES (@pl, @pts, @st, @desc)", + new() + { + ["@pl"] = int.Parse(r["Placement"]), + ["@pts"] = int.Parse(r["Points"]), + ["@st"] = r["System_Type"].Trim(), + ["@desc"] = Nullable(r["Description"]), + }); +} + +// ── 4. prize_pool_category ──────────────────────────────────────────────────── + +Console.WriteLine("[4/13] prize_pool_category"); +foreach (var r in rows06) +{ + await Insert( + "INSERT INTO prize_pool_category (category, amount_usd, percentage, description, num_recipients) VALUES (@c, @a, @p, @d, @n)", + new() + { + ["@c"] = r["Category"].Trim(), + ["@a"] = int.Parse(r["Amount_USD"]), + ["@p"] = decimal.Parse(r["Percentage"], CultureInfo.InvariantCulture), + ["@d"] = Nullable(r["Description"]), + ["@n"] = NullInt(r["Num_Recipients"]), + }); +} + +// ── 5. organization ─────────────────────────────────────────────────────────── + +Console.WriteLine("[5/13] organization"); +var orgId = new Dictionary(StringComparer.OrdinalIgnoreCase); + +var partnerDetails = rows04.ToDictionary(r => r["Organization"].Trim(), StringComparer.OrdinalIgnoreCase); + +var allOrgs = new HashSet(StringComparer.OrdinalIgnoreCase); +foreach (var r in rows04) allOrgs.Add(r["Organization"].Trim()); +foreach (var r in rows03) allOrgs.Add(r["Organization"].Trim()); +foreach (var r in rows02) allOrgs.Add(r["Team_Organization"].Trim()); +foreach (var r in rows05) { if (!string.IsNullOrWhiteSpace(r["Team"])) allOrgs.Add(r["Team"].Trim()); } +foreach (var r in rows10) +{ + allOrgs.Add(r["Team_1"].Trim()); + allOrgs.Add(r["Team_2"].Trim()); + if (!string.IsNullOrWhiteSpace(r["Winner"])) allOrgs.Add(r["Winner"].Trim()); +} + +foreach (var name in allOrgs.Where(n => !string.IsNullOrWhiteSpace(n))) +{ + long? cid = null; + string status = "None"; + string? region = null, hq = null, ceo = null; + bool? top8 = null; + int? founded = null; + decimal? followers = null; + + if (partnerDetails.TryGetValue(name, out var p)) + { + var cname = p["Country"].Trim(); + if (!string.IsNullOrWhiteSpace(cname)) cid = await EnsureCountry(cname); + status = p["Club_Partner_Status"].Trim(); + region = Nullable(p["Region"]); + top8 = YesNo(p["Top_8_2024"]); + founded = NullInt(p["Founded"]); + hq = Nullable(p["HQ_Location"]); + ceo = Nullable(p["CEO"]); + followers = NullDec(p["Social_Media_Followers_M"]); + } + + orgId[name] = await Insert( + """ + INSERT INTO organization + (name, region, country_id, club_partner_status, top_8_2024, + founded_year, hq_location, ceo, social_media_followers_m) + VALUES (@n, @reg, @cid, @st, @t8, @fy, @hq, @ceo, @fol) + """, + new() + { + ["@n"] = name, + ["@reg"] = region, + ["@cid"] = cid, + ["@st"] = status, + ["@t8"] = top8, + ["@fy"] = founded, + ["@hq"] = hq, + ["@ceo"] = ceo, + ["@fol"] = followers, + }); +} + +// ── 6. tournament ───────────────────────────────────────────────────────────── + +Console.WriteLine("[6/13] tournament"); + +// Key: "GameName|StartDate" — unique because MLBB Men/Women have different start dates +var tournamentKey = new Dictionary(); + +foreach (var r in rows01) +{ + var gameName = r["Game"].Trim(); + var gender = r["Gender"].Trim() switch { "Men" => "Men", "Women" => "Women", _ => "Open" }; + var id = await Insert( + """ + INSERT INTO tournament + (game_id, event_name, start_date, end_date, prize_pool_usd, + num_participants, winner, runner_up, club_championship_points, gender) + VALUES (@gid, @en, @sd, @ed, @prize, @num, @w, @ru, @ccp, @gen) + """, + new() + { + ["@gid"] = gameId[gameName], + ["@en"] = r["Event_Name"].Trim(), + ["@sd"] = r["Start_Date"].Trim(), + ["@ed"] = r["End_Date"].Trim(), + ["@prize"] = int.Parse(r["Prize_Pool_USD"]), + ["@num"] = int.Parse(r["Num_Participants"]), + ["@w"] = Nullable(r["Winner"]), + ["@ru"] = Nullable(r["Runner_Up"]), + ["@ccp"] = YesNo(r["Club_Championship_Points"]), + ["@gen"] = gender, + }); + tournamentKey[$"{gameName}|{r["Start_Date"].Trim()}"] = id; +} + +// Unified lookup: given the raw game name from files 02/07/10 (may include suffixes), +// return the matching tournament_id. +long? ResolveTournament(string rawGame) +{ + var canonical = CanonicalGame(rawGame); + // Try direct match via schedule rows (which share the same naming as files 02/10) + foreach (var r in rows07) + { + if (!r["Game"].Trim().Equals(rawGame, StringComparison.OrdinalIgnoreCase)) continue; + var key = $"{canonical}|{r["Start_Date"].Trim()}"; + if (tournamentKey.TryGetValue(key, out var tid)) return tid; + } + // Fallback: first tournament whose game name matches canonical + foreach (var (k, v) in tournamentKey) + if (k.StartsWith($"{canonical}|", StringComparison.OrdinalIgnoreCase)) return v; + return null; +} + +// ── 7. schedule ─────────────────────────────────────────────────────────────── + +Console.WriteLine("[7/13] schedule"); +foreach (var r in rows07) +{ + var tid = ResolveTournament(r["Game"].Trim()); + if (tid is null) + { + Console.WriteLine($" WARN: no tournament for schedule row '{r["Game"]}'"); + continue; + } + await Insert( + "INSERT INTO schedule (tournament_id, week_number, venue, time_zone, duration_days) VALUES (@tid, @w, @v, @tz, @dur)", + new() + { + ["@tid"] = tid.Value, + ["@w"] = int.Parse(r["Week"]), + ["@v"] = r["Venue"].Trim(), + ["@tz"] = r["Time_Zone"].Trim(), + ["@dur"] = int.Parse(r["Duration_Days"]), + }); +} + +// ── 8. player ───────────────────────────────────────────────────────────────── + +Console.WriteLine("[8/13] player"); +foreach (var r in rows05) +{ + var cname = r["Country"].Trim(); + var gname = r["Game"].Trim(); + var tname = r["Team"].Trim(); + await Insert( + """ + INSERT INTO player + (player_id, player_name, country_id, region, organization_id, game_id, + role, age, experience_years, previous_team, tournament_place, + prize_earned_usd, social_media_followers_k) + VALUES (@pid, @pn, @cid, @reg, @oid, @gid, @role, @age, @exp, @prev, @place, @prize, @soc) + """, + new() + { + ["@pid"] = r["Player_ID"].Trim(), + ["@pn"] = r["Player_Name"].Trim(), + ["@cid"] = string.IsNullOrWhiteSpace(cname) ? null : (long?)await EnsureCountry(cname), + ["@reg"] = Nullable(r["Region"]), + ["@oid"] = orgId.TryGetValue(tname, out var oid) ? oid : null, + ["@gid"] = gameId.TryGetValue(gname, out var gid) ? gid : null, + ["@role"] = Nullable(r["Role"]), + ["@age"] = NullInt(r["Age"]), + ["@exp"] = NullInt(r["Experience_Years"]), + ["@prev"] = Nullable(r["Previous_Team"]), + ["@place"] = NullInt(r["Tournament_Place"]), + ["@prize"] = NullInt(r["Prize_Earned_USD"]) ?? 0, + ["@soc"] = NullInt(r["Social_Media_Followers_K"]) ?? 0, + }); +} + +// ── 9. medalist ─────────────────────────────────────────────────────────────── + +Console.WriteLine("[9/13] medalist"); +foreach (var r in rows02) +{ + var tid = ResolveTournament(r["Event"].Trim()); + if (tid is null) + { + Console.WriteLine($" WARN: no tournament for medalist event '{r["Event"]}'"); + continue; + } + var orgName = r["Team_Organization"].Trim(); + var cname = r["Country"].Trim(); + await Insert( + "INSERT INTO medalist (tournament_id, medal, organization_id, player_name, country_id, role) VALUES (@tid, @m, @oid, @pn, @cid, @role)", + new() + { + ["@tid"] = tid.Value, + ["@m"] = r["Medal"].Trim(), + ["@oid"] = orgId.TryGetValue(orgName, out var oid) ? oid : null, + ["@pn"] = r["Player"].Trim(), + ["@cid"] = string.IsNullOrWhiteSpace(cname) ? null : (long?)await EnsureCountry(cname), + ["@role"] = Nullable(r["Role"]), + }); +} + +// ── 10. match_result ────────────────────────────────────────────────────────── + +Console.WriteLine("[10/13] match_result"); +foreach (var r in rows10) +{ + var tid = ResolveTournament(r["Game"].Trim()); + if (tid is null) + { + Console.WriteLine($" WARN: no tournament for match '{r["Game"]}'"); + continue; + } + await Insert( + """ + INSERT INTO match_result + (tournament_id, match_type, team_1, team_2, winner, score, map, duration_minutes, mvp) + VALUES (@tid, @mt, @t1, @t2, @w, @sc, @map, @dur, @mvp) + """, + new() + { + ["@tid"] = tid.Value, + ["@mt"] = r["Match_Type"].Trim(), + ["@t1"] = r["Team_1"].Trim(), + ["@t2"] = r["Team_2"].Trim(), + ["@w"] = Nullable(r["Winner"]), + ["@sc"] = Nullable(r["Score"]), + ["@map"] = Nullable(r["Map"]), + ["@dur"] = NullInt(r["Duration_Minutes"]), + ["@mvp"] = Nullable(r["MVP"]), + }); +} + +// ── 11. club_championship_standing ──────────────────────────────────────────── + +Console.WriteLine("[11/13] club_championship_standing"); +foreach (var r in rows03) +{ + var name = r["Organization"].Trim(); + if (!orgId.TryGetValue(name, out var oid)) + { + Console.WriteLine($" WARN: org not found for standing '{name}'"); + continue; + } + await Insert( + """ + INSERT INTO club_championship_standing + (organization_id, `rank`, total_points, prize_money_usd, + tournament_wins, top_8_finishes, eligible_to_win) + VALUES (@oid, @rank, @pts, @prize, @wins, @top8, @elig) + """, + new() + { + ["@oid"] = oid, + ["@rank"] = int.Parse(r["Rank"]), + ["@pts"] = int.Parse(r["Total_Points"]), + ["@prize"] = int.Parse(r["Prize_Money_USD"]), + ["@wins"] = int.Parse(r["Tournament_Wins"]), + ["@top8"] = int.Parse(r["Top_8_Finishes"]), + ["@elig"] = YesNo(r["Eligible_to_Win"]), + }); +} + +// ── 12. organization_game_competing ─────────────────────────────────────────── + +Console.WriteLine("[12/13] organization_game_competing"); +foreach (var r in rows04) +{ + var name = r["Organization"].Trim(); + if (!orgId.TryGetValue(name, out var oid)) continue; + foreach (var raw in r["Games_Competing"].Trim('"').Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + var canonical = CanonicalGame(raw.Trim()); + if (!gameId.TryGetValue(canonical, out var gid)) + { + Console.WriteLine($" WARN: game '{raw.Trim()}' not found for org-competing '{name}'"); + continue; + } + await Insert( + "INSERT IGNORE INTO organization_game_competing (organization_id, game_id) VALUES (@oid, @gid)", + new() { ["@oid"] = oid, ["@gid"] = gid }); + } +} + +// ── 13. organization_game_won ───────────────────────────────────────────────── + +Console.WriteLine("[13/13] organization_game_won"); +foreach (var r in rows03) +{ + var name = r["Organization"].Trim(); + if (!orgId.TryGetValue(name, out var oid)) continue; + var gamesWon = r["Games_Won"].Trim('"').Trim(); + if (gamesWon.Equals("None", StringComparison.OrdinalIgnoreCase)) continue; + foreach (var raw in gamesWon.Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + var canonical = CanonicalGame(raw.Trim()); + if (!gameId.TryGetValue(canonical, out var gid)) + { + Console.WriteLine($" WARN: game '{raw.Trim()}' not found for org-won '{name}'"); + continue; + } + await Insert( + "INSERT IGNORE INTO organization_game_won (organization_id, game_id) VALUES (@oid, @gid)", + new() { ["@oid"] = oid, ["@gid"] = gid }); + } +} + +Console.WriteLine("Done. Database seeded.");