Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/migrations/0009_tag_normalized_type.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Add normalized_tournament_type column to tags table
-- This allows tags to normalize all tournaments to a single type for points calculation

ALTER TABLE tags ADD COLUMN normalized_tournament_type TEXT;
18 changes: 9 additions & 9 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@
"p-limit": "^4.0.0",
"toucan-js": "^3.4.0",
"unique-names-generator": "^4.7.1",
"zod": "^3.24.1"
"zod": "^3.25.76"
},
"devDependencies": {
"@sentry/cli": "^2.40.0",
"@cloudflare/workers-types": "^4.20241230.0",
"@cloudflare/workers-types": "^4.20251121.0",
"@jest/types": "^29.6.3",
"@sentry/cli": "^2.58.2",
"@types/jest": "^29.5.14",
"@types/node": "^20.17.11",
"@types/node": "^20.19.25",
"@types/object-hash": "^3.0.6",
"@types/react-syntax-highlighter": "^15.5.13",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-import": "^2.32.0",
"jest": "^29.7.0",
"kysely": "^0.26.3",
"miniflare": "^3.20241218.0",
"ts-jest": "^29.2.5",
"miniflare": "^3.20250718.2",
"ts-jest": "^29.4.5",
"ts-node": "^10.9.2",
"typescript": "^5.7.2",
"wrangler": "^3.99.0"
"typescript": "^5.9.3",
"wrangler": "^3.114.15"
}
}
6 changes: 6 additions & 0 deletions api/src/lib/ranking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum Tournament {
CircuitBreakerInvitational = "circuit breaker invitational",
PlayersCircuit = "players circuit",
Intercontinental = "intercontinental championship",
CommunityTournament = "community tournament",
}

// Default configuration (Season 0)
Expand All @@ -32,6 +33,7 @@ export const DEFAULT_CONFIG: Record<string, Record<Tournament, number>> = {
[Tournament.CasualTournamentKit]: 1,
[Tournament.DistrictChampionship]: 1.2,
[Tournament.MegaCityChampionship]: 1.5,
[Tournament.CommunityTournament]: 1,
},
// Flat points added to the total point pool that gets awarded to 1st place
// Each tournament gets a different point total to reflect the tournament prestige
Expand All @@ -47,6 +49,7 @@ export const DEFAULT_CONFIG: Record<string, Record<Tournament, number>> = {
[Tournament.CasualTournamentKit]: 15,
[Tournament.DistrictChampionship]: 0,
[Tournament.MegaCityChampionship]: 0,
[Tournament.CommunityTournament]: 0,
},
// Sets a baseline number of players a tournament must have in order to distribute any points at all
// This means that small tournaments are not eligible for payouts
Expand All @@ -62,6 +65,7 @@ export const DEFAULT_CONFIG: Record<string, Record<Tournament, number>> = {
[Tournament.CasualTournamentKit]: 8,
[Tournament.DistrictChampionship]: 8,
[Tournament.MegaCityChampionship]: 8,
[Tournament.CommunityTournament]: 8,
},
// Defines the max number of tournaments a person can get points for
// We take the top values if a person attends more than the defined max
Expand All @@ -77,6 +81,7 @@ export const DEFAULT_CONFIG: Record<string, Record<Tournament, number>> = {
[Tournament.CasualTournamentKit]: 5,
[Tournament.DistrictChampionship]: 3,
[Tournament.MegaCityChampionship]: 2,
[Tournament.CommunityTournament]: 1,
},
// Defines the bottom anchor point which means the last place player will receive less than the value provided
// This is used to help set the rate of decay and the payout slope. A higher number indicates a more gradual slope
Expand All @@ -92,6 +97,7 @@ export const DEFAULT_CONFIG: Record<string, Record<Tournament, number>> = {
[Tournament.CasualTournamentKit]: 1,
[Tournament.DistrictChampionship]: 1,
[Tournament.MegaCityChampionship]: 1,
[Tournament.CommunityTournament]: 1,
},
};

Expand Down
38 changes: 35 additions & 3 deletions api/src/models/results.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { g } from "../g.js";
import { DEFAULT_CONFIG } from "../lib/ranking.js";
import { DEFAULT_CONFIG, calculatePointDistribution } from "../lib/ranking.js";
import { traceDeco } from "../lib/tracer.js";
import type {
Faction,
Expand All @@ -18,8 +18,10 @@ export type ResultExpanded = ResultsTable & {
disabled: number;
format: Format;
tournament_type: TournamentType;
season_id: number | null;
count_for_tournament_type: number;
is_valid: boolean;
normalized_tournament_type_used?: TournamentType | null;
};

type GetExpandedOptions = {
Expand Down Expand Up @@ -83,6 +85,7 @@ export class Results {
"tournaments.name as tournament_name",
"tournaments.players_count as players_count",
"tournaments.format as format",
"tournaments.season_id as season_id",
])
// Only fetch results for non-disabled users
.where("users.disabled", "=", 0)
Expand Down Expand Up @@ -130,6 +133,11 @@ export class Results {
includeLimits = true;
}

// Check if any tag has a normalized tournament type
const normalizedType = tagModels.find(
(tag) => tag.normalized_tournament_type != null,
)?.normalized_tournament_type;

// TODO: yes we could do this in sql, but this is so much easier
const results: ResultExpanded[] = [];
const initialResults = await q.execute();
Expand All @@ -138,12 +146,36 @@ export class Results {
DEFAULT_CONFIG.MAX_TOURNAMENTS_PER_TYPE[
initialResults[i].tournament_type
];
results.push({

const resultToAdd: ResultExpanded = {
...initialResults[i],
is_valid: includeLimits
? initialResults[i].count_for_tournament_type <= max
: true,
});
normalized_tournament_type_used: normalizedType
? (normalizedType as TournamentType)
: null,
};

// Recalculate points if a normalized tournament type is set
if (normalizedType) {
const { points } = calculatePointDistribution(
initialResults[i].players_count,
normalizedType as TournamentType,
undefined,
initialResults[i].season_id ?? undefined,
);

// Determine placement index (same logic as ingestion)
const placementIndex =
(initialResults[i].rank_cut || initialResults[i].rank_swiss) - 1;

if (placementIndex >= 0 && placementIndex < points.length) {
resultToAdd.points_earned = points[placementIndex];
}
}

results.push(resultToAdd);
}

const sortedResults: ResultExpanded[] = [];
Expand Down
5 changes: 5 additions & 0 deletions api/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export const ResultComponent = z
format: FormatComponent,
count_for_tournament_type: z.number().default(0),
is_valid: z.boolean(),
normalized_tournament_type_used:
TournamentTypeComponent.nullable().optional(),
})
.openapi("Result");
export type ResultComponentType = z.infer<typeof ResultComponent>;
Expand Down Expand Up @@ -205,6 +207,7 @@ export const GetTagsResponseComponent = z
owner_name: z.string(),
count: z.number(),
use_tournament_limits: z.coerce.boolean(),
normalized_tournament_type: z.string().nullable().optional(),
})
.openapi("GetTagsResponse");
export type GetTagsResponseComponentType = z.infer<
Expand All @@ -218,6 +221,7 @@ export const TagComponent = z
normalized: z.string(),
owner_id: z.number(),
use_tournament_limits: z.coerce.boolean(),
normalized_tournament_type: z.string().nullable().optional(),
})
.openapi("Tag");
export type TagComponentType = z.infer<typeof TagComponent>;
Expand Down Expand Up @@ -485,6 +489,7 @@ export const InsertTagSchema = {

export const UpdateTagBody = z.object({
use_tournament_limits: z.boolean(),
normalized_tournament_type: z.string().nullable().optional(),
});

export type UpdateTagBodyType = z.infer<typeof UpdateTagBody>;
Expand Down
5 changes: 5 additions & 0 deletions api/src/routes/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ export class UpdateTag extends OpenAPIRoute {
}
tag.use_tournament_limits = body.use_tournament_limits ? 1 : 0;

// Update normalized_tournament_type if provided in the body
if (body.normalized_tournament_type !== undefined) {
tag.normalized_tournament_type = body.normalized_tournament_type;
}

try {
const updatedTag = await Tags.update(tag);
return json(TagComponent.parse(updatedTag));
Expand Down
1 change: 1 addition & 0 deletions api/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export interface TagsTable {
normalized: string;
owner_id: number;
use_tournament_limits: number;
normalized_tournament_type: string | null;
}

export type Tag = Selectable<TagsTable>;
Expand Down
34 changes: 17 additions & 17 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,37 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@cloudflare/workers-types": "^4.20241230.0",
"@cloudflare/workers-types": "^4.20251121.0",
"@svgr/webpack": "^8.1.0",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/typography": "^0.5.19",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.0",
"autoprefixer": "^10.4.20",
"axios": "^1.7.9",
"autoprefixer": "^10.4.22",
"axios": "^1.13.2",
"babel-plugin-named-exports-order": "^0.0.2",
"file-loader": "^6.2.0",
"form-data": "^4.0.1",
"postcss": "^8.4.49",
"form-data": "^4.0.5",
"postcss": "^8.5.6",
"postcss-loader": "^7.3.4",
"prop-types": "^15.8.1",
"react-scripts": "^5.0.1",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.17",
"tailwindcss": "^3.4.18",
"typescript": "^4.9.5",
"webpack": "^5.97.1",
"wrangler": "^3.99.0"
"webpack": "^5.103.0",
"wrangler": "^3.114.15"
},
"dependencies": {
"@floating-ui/react": "^0.26.28",
"@fontsource/inter": "^5.1.1",
"@fontsource/jetbrains-mono": "^5.1.2",
"@fontsource/inter": "^5.2.8",
"@fontsource/jetbrains-mono": "^5.2.8",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@fortawesome/react-fontawesome": "^0.2.6",
"@headlessui/react": "^1.7.19",
"@heroicons/react": "^2.2.0",
"@tanstack/react-query": "^5.62.14",
"@types/node": "^20.17.11",
"@tanstack/react-query": "^5.90.10",
"@types/node": "^20.19.25",
"@visx/axis": "^3.12.0",
"@visx/curve": "^3.12.0",
"@visx/event": "^3.12.0",
Expand All @@ -63,9 +63,9 @@
"moment": "^2.30.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.1",
"react-syntax-highlighter": "^15.6.1",
"recharts": "^2.15.0",
"react-router-dom": "^6.30.2",
"react-syntax-highlighter": "^15.6.6",
"recharts": "^2.15.4",
"tailwind-merge": "^2.6.0",
"web-vitals": "^2.1.4"
},
Expand Down
1 change: 1 addition & 0 deletions app/src/client/models/GetTagsResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export type GetTagsResponse = {
owner_name: string;
count: number;
use_tournament_limits?: boolean | null;
normalized_tournament_type?: string | null;
};

1 change: 1 addition & 0 deletions app/src/client/models/Result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ export type Result = {
format: (Format & string);
count_for_tournament_type?: number;
is_valid: boolean;
normalized_tournament_type_used?: (TournamentType & string | null);
};

1 change: 1 addition & 0 deletions app/src/client/models/Tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export type Tag = {
normalized: string;
owner_id: number;
use_tournament_limits?: boolean | null;
normalized_tournament_type?: string | null;
};

1 change: 1 addition & 0 deletions app/src/client/services/TagsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class TagsService {
tagId: number,
requestBody?: {
use_tournament_limits: boolean;
normalized_tournament_type?: string | null;
},
): CancelablePromise<any> {
return __request(OpenAPI, {
Expand Down
Loading