490 lines
20 KiB
C#
490 lines
20 KiB
C#
#: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.");
|