feat(TournamentStats): enhance internal tournament statistics with member profile integration
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 37s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 37s
- Integrated member profile data (birth date and gender) into the internal tournament statistics calculations for more accurate age class filtering. - Removed deprecated age class filtering functions and streamlined the statistics computation process. - Updated the InternalTournamentStats component to reflect changes in age class options and improved sorting logic. - Enhanced localization strings across multiple languages to support new terminology related to age classes and gender, improving user accessibility and understanding.
This commit is contained in:
147
backend/utils/ttInternalStatsBuckets.js
Normal file
147
backend/utils/ttInternalStatsBuckets.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Feste TT-Einzel-Filter (J9–J19, Erwachsene × Weiblich/Alle).
|
||||
* Zuordnung über **Teilnehmer/Mitglied**: Geburtsdatum + Geschlecht, Saison-Stichtag 01.01.
|
||||
* (vgl. frontend/src/utils/ttAgeClass.js)
|
||||
*/
|
||||
|
||||
const YOUTH_BANDS = [9, 11, 13, 15, 19];
|
||||
|
||||
/**
|
||||
* @param {string|undefined} isoDateStr Turnierdatum (DATEONLY)
|
||||
* @returns {number} erstes Saisonjahr (01.08.–31.07.)
|
||||
*/
|
||||
export function getSeasonStartYearFromTournamentDate(isoDateStr) {
|
||||
if (!isoDateStr) {
|
||||
const n = new Date();
|
||||
const month = n.getMonth() + 1;
|
||||
const year = n.getFullYear();
|
||||
return month >= 8 ? year : year - 1;
|
||||
}
|
||||
const d = new Date(String(isoDateStr).length <= 10 ? `${isoDateStr}T12:00:00` : isoDateStr);
|
||||
if (Number.isNaN(d.getTime())) {
|
||||
const n = new Date();
|
||||
const month = n.getMonth() + 1;
|
||||
const year = n.getFullYear();
|
||||
return month >= 8 ? year : year - 1;
|
||||
}
|
||||
const month = d.getMonth() + 1;
|
||||
const year = d.getFullYear();
|
||||
return month >= 8 ? year : year - 1;
|
||||
}
|
||||
|
||||
function getStichtagDate(seasonStartYear, classNum) {
|
||||
const y = seasonStartYear - (classNum - 1);
|
||||
return new Date(y, 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} ms Geburtszeitpunkt
|
||||
* @param {number} seasonStartYear
|
||||
* @returns {'J9'|'J11'|'J13'|'J15'|'J19'|'adult'}
|
||||
*/
|
||||
function getExclusiveJugendLabelFromBirthTime(ms, seasonStartYear) {
|
||||
const c = (k) => getStichtagDate(seasonStartYear, k).getTime();
|
||||
const c9 = c(9);
|
||||
const c11 = c(11);
|
||||
const c13 = c(13);
|
||||
const c15 = c(15);
|
||||
const c19 = c(19);
|
||||
if (ms < c19) return 'adult';
|
||||
if (ms >= c9) return 'J9';
|
||||
if (ms >= c11 && ms < c9) return 'J11';
|
||||
if (ms >= c13 && ms < c11) return 'J13';
|
||||
if (ms >= c15 && ms < c13) return 'J15';
|
||||
if (ms >= c19 && ms < c15) return 'J19';
|
||||
return 'adult';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|Date|null|undefined} birthDate – wie Member.birthDate (Klartext nach get)
|
||||
* @returns {number|null} ms seit Epoch
|
||||
*/
|
||||
export function parseMemberBirthDateToMs(birthDate) {
|
||||
if (birthDate == null || birthDate === '') return null;
|
||||
if (birthDate instanceof Date && !Number.isNaN(birthDate.getTime())) {
|
||||
return birthDate.getTime();
|
||||
}
|
||||
const t = String(birthDate).trim();
|
||||
if (/^\d{4}-\d{2}-\d{2}/.test(t)) {
|
||||
const [a, b, c] = t.split(/[T\s]/)[0].split('-').map(Number);
|
||||
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c)) {
|
||||
const d = new Date(a, b - 1, c);
|
||||
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
||||
}
|
||||
}
|
||||
if (t.includes('.')) {
|
||||
const parts = t.split('.');
|
||||
if (parts.length >= 3) {
|
||||
const day = parseInt(parts[0], 10);
|
||||
const month = parseInt(parts[1], 10);
|
||||
const year = parseInt(parts[2], 10);
|
||||
if (Number.isFinite(day) && Number.isFinite(month) && Number.isFinite(year)) {
|
||||
const d = new Date(year, month - 1, day);
|
||||
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** TT: nur Weiblich vs. Alle (alles außer female → open) */
|
||||
function memberGenderMode(gender) {
|
||||
return gender === 'female' ? 'female' : 'open';
|
||||
}
|
||||
|
||||
/**
|
||||
* Kanonischer Filter-Schlüssel aus Vereinsmitglied (Teilnehmer).
|
||||
* @param {{ birthDate?: string|Date|null, gender?: string|null }|null|undefined} memberProfile
|
||||
* @param {string|undefined} tournamentDateIso
|
||||
* @returns {string|null} z. B. tt|13|open; null ohne auswertbares Geburtsdatum
|
||||
*/
|
||||
export function memberToCanonicalTtBucket(memberProfile, tournamentDateIso) {
|
||||
if (!memberProfile) return null;
|
||||
const ms = parseMemberBirthDateToMs(memberProfile.birthDate);
|
||||
if (ms == null) return null;
|
||||
const seasonStartYear = getSeasonStartYearFromTournamentDate(tournamentDateIso);
|
||||
const jc = getExclusiveJugendLabelFromBirthTime(ms, seasonStartYear);
|
||||
const gMode = memberGenderMode(memberProfile.gender);
|
||||
if (jc === 'adult') return `tt|adult|${gMode}`;
|
||||
const n = parseInt(jc.slice(1), 10);
|
||||
if (!YOUTH_BANDS.includes(n)) return `tt|adult|${gMode}`;
|
||||
return `tt|${n}|${gMode}`;
|
||||
}
|
||||
|
||||
export function getCanonicalTtAgeClassOptions() {
|
||||
const out = [];
|
||||
for (const n of YOUTH_BANDS) {
|
||||
out.push({
|
||||
key: `tt|${n}|female`,
|
||||
band: 'youth',
|
||||
bandNum: n,
|
||||
genderMode: 'female',
|
||||
isNoClass: false,
|
||||
});
|
||||
out.push({
|
||||
key: `tt|${n}|open`,
|
||||
band: 'youth',
|
||||
bandNum: n,
|
||||
genderMode: 'open',
|
||||
isNoClass: false,
|
||||
});
|
||||
}
|
||||
out.push({
|
||||
key: 'tt|adult|female',
|
||||
band: 'adult',
|
||||
bandNum: null,
|
||||
genderMode: 'female',
|
||||
isNoClass: false,
|
||||
});
|
||||
out.push({
|
||||
key: 'tt|adult|open',
|
||||
band: 'adult',
|
||||
bandNum: null,
|
||||
genderMode: 'open',
|
||||
isNoClass: false,
|
||||
});
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user