Seed script

This commit is contained in:
2026-05-17 16:34:08 +02:00
parent 314496438a
commit a7b20d997e

489
scripts/seed.cs Normal file
View File

@@ -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<string, string>(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<Dictionary<string, string>> 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<Dictionary<string, string>>();
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
var row = new Dictionary<string, string>();
foreach (var h in csv.HeaderRecord!)
row[h] = csv.GetField(h) ?? "";
rows.Add(row);
}
return rows;
}
async Task<long> Insert(string sql, Dictionary<string, object?> 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<string, long>(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<string, long>(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<long> 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<string, long>(StringComparer.OrdinalIgnoreCase);
var partnerDetails = rows04.ToDictionary(r => r["Organization"].Trim(), StringComparer.OrdinalIgnoreCase);
var allOrgs = new HashSet<string>(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<string, long>();
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.");