refactor(autoFetchMatchResultsService, MYTISCHTENNIS_AUTO_FETCH_README.md, Optimization TODO): enhance data processing and documentation
- Refactored the `autoFetchMatchResultsService` to separate single and double player statistics processing into distinct methods, improving code clarity and maintainability. - Updated the README to reflect the current status of rating updates and match results, marking them as active and detailing their functionalities. - Closed several TODOs in the optimization documentation, confirming the implementation of previously outlined tasks and ensuring no open inline TODOs remain in the main views.
This commit is contained in:
@@ -9,12 +9,12 @@ Dieses System ermöglicht den automatischen Abruf von Spielergebnissen und Stati
|
||||
### 6:00 Uhr - Rating Updates
|
||||
- **Service:** `autoUpdateRatingsService.js`
|
||||
- **Funktion:** Aktualisiert TTR/QTTR-Werte für Spieler
|
||||
- **TODO:** Implementierung der eigentlichen Rating-Update-Logik
|
||||
- **Status:** ✅ Aktiv. Nutzt `memberService.updateRatingsFromMyTischtennisByUserId(...)` pro Verein ueber einen freigeschalteten Benutzer mit gespeichertem myTischtennis-Login.
|
||||
|
||||
### 6:30 Uhr - Spielergebnisse
|
||||
- **Service:** `autoFetchMatchResultsService.js`
|
||||
- **Funktion:** Ruft Spielerbilanzen für konfigurierte Teams ab
|
||||
- **Status:** ✅ Grundlegende Implementierung fertig
|
||||
- **Funktion:** Ruft Team-Spielplaene, Liga-Spielplaene, Spielerbilanzen und Ligatabellen fuer konfigurierte Teams ab
|
||||
- **Status:** ✅ Aktiv. Importiert neue Spiele, aktualisiert Ergebnis- und Termin-Aenderungen und synchronisiert Ligatabellen.
|
||||
|
||||
## Benötigte Konfiguration
|
||||
|
||||
@@ -107,6 +107,7 @@ Von der myTischtennis API werden folgende Daten abgerufen:
|
||||
- Player IDs, Namen der beiden Spieler
|
||||
- Gewonnene/Verlorene Punkte
|
||||
- Anzahl Spiele
|
||||
- Zuordnung der beteiligten Mitglieder ueber Player-ID oder Namensabgleich
|
||||
|
||||
### Team-Informationen
|
||||
- Teamname, Liga, Saison
|
||||
@@ -134,7 +135,8 @@ Von der myTischtennis API werden folgende Daten abgerufen:
|
||||
- Parst JSON-Response
|
||||
- Matched Spieler anhand von ID oder Name
|
||||
- Speichert myTischtennis Player-ID bei Mitgliedern
|
||||
- Loggt Statistiken
|
||||
- verarbeitet auch Doppelpartner-Zuordnungen
|
||||
- speichert/aktualisiert Spiele und Ligatabellen
|
||||
|
||||
### Player-Matching-Algorithmus
|
||||
|
||||
@@ -148,25 +150,21 @@ Von der myTischtennis API werden folgende Daten abgerufen:
|
||||
|
||||
## TODO / Offene Punkte
|
||||
|
||||
### Noch zu implementieren:
|
||||
### Noch offen:
|
||||
|
||||
1. **TTR/QTTR Updates** (6:00 Uhr Job):
|
||||
- Endpoint für TTR/QTTR-Daten identifizieren
|
||||
- Daten abrufen und in Member-Tabelle speichern
|
||||
|
||||
2. **Spielergebnis-Details**:
|
||||
1. **Spielergebnis-Details**:
|
||||
- Einzelne Matches mit Satzständen speichern
|
||||
- Tabelle für Match-Historie erstellen
|
||||
|
||||
3. **History-Tabelle für Spielergebnis-Abrufe** (optional):
|
||||
2. **History-Tabelle für Spielergebnis-Abrufe** (optional):
|
||||
- Ähnlich zu `my_tischtennis_update_history`
|
||||
- Speichert Erfolg/Fehler der Abrufe
|
||||
|
||||
4. **Benachrichtigungen** (optional):
|
||||
3. **Benachrichtigungen** (optional):
|
||||
- Email/Push bei neuen Ergebnissen
|
||||
- Highlights für besondere Siege
|
||||
|
||||
5. **Performance-Optimierung**:
|
||||
4. **Performance-Optimierung**:
|
||||
- Caching für Player-Matches
|
||||
- Incremental Updates (nur neue Daten)
|
||||
|
||||
@@ -183,6 +181,14 @@ await schedulerService.triggerRatingUpdates();
|
||||
await schedulerService.triggerMatchResultsFetch();
|
||||
```
|
||||
|
||||
### Manuelle HTTP-Trigger
|
||||
|
||||
```text
|
||||
POST /api/scheduler/rating_updates
|
||||
POST /api/scheduler/match_results
|
||||
GET /api/scheduler/status
|
||||
```
|
||||
|
||||
## API-Dokumentation
|
||||
|
||||
### MyTischtennis Spielerbilanzen-Endpoint
|
||||
@@ -209,4 +215,3 @@ https://www.mytischtennis.de/click-tt/{association}/{season}/ligen/{groupname}/g
|
||||
- ✅ Passwörter verschlüsselt gespeichert
|
||||
- ✅ Fehlerbehandlung und Logging
|
||||
- ✅ Graceful Degradation (einzelne Team-Fehler stoppen nicht den gesamten Prozess)
|
||||
|
||||
|
||||
@@ -504,13 +504,6 @@ class AutoFetchMatchResultsService {
|
||||
* Process and store team data from myTischtennis
|
||||
*/
|
||||
async processTeamData(team, data) {
|
||||
// TODO: Implement data processing and storage
|
||||
// This would typically involve:
|
||||
// 1. Extract player statistics from data.data.balancesheet
|
||||
// 2. Match players with local Member records (by player_id or name)
|
||||
// 3. Update or create match statistics
|
||||
// 4. Store historical data for tracking changes
|
||||
|
||||
devLog(`Processing data for team ${team.name}`);
|
||||
|
||||
if (!data.data || !data.data.balancesheet || !Array.isArray(data.data.balancesheet)) {
|
||||
@@ -521,47 +514,8 @@ class AutoFetchMatchResultsService {
|
||||
let processedCount = 0;
|
||||
|
||||
for (const teamData of data.data.balancesheet) {
|
||||
// Process single player statistics
|
||||
if (teamData.single_player_statistics) {
|
||||
for (const playerStat of teamData.single_player_statistics) {
|
||||
devLog(`Player: ${playerStat.player_firstname} ${playerStat.player_lastname} (ID: ${playerStat.player_id})`);
|
||||
devLog(` Points won: ${playerStat.points_won}, Points lost: ${playerStat.points_lost}`);
|
||||
|
||||
// Try to match player with local Member
|
||||
const member = await this.matchPlayer(
|
||||
playerStat.player_id,
|
||||
playerStat.player_firstname,
|
||||
playerStat.player_lastname
|
||||
);
|
||||
|
||||
if (member) {
|
||||
devLog(` Matched with local member: ${member.firstName} ${member.lastName} (ID: ${member.id})`);
|
||||
|
||||
// Update player statistics (TTR/QTTR would be fetched from different endpoint)
|
||||
// For now, we just ensure the myTischtennis ID is stored
|
||||
if (!member.myTischtennisPlayerId) {
|
||||
member.myTischtennisPlayerId = playerStat.player_id;
|
||||
await member.save();
|
||||
devLog(` Updated myTischtennis Player ID for ${member.firstName} ${member.lastName}`);
|
||||
}
|
||||
} else {
|
||||
devLog(` No local member found for ${playerStat.player_firstname} ${playerStat.player_lastname}`);
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Process double player statistics
|
||||
if (teamData.double_player_statistics) {
|
||||
for (const doubleStat of teamData.double_player_statistics) {
|
||||
devLog(`Double: ${doubleStat.firstname_player_1} ${doubleStat.lastname_player_1} / ${doubleStat.firstname_player_2} ${doubleStat.lastname_player_2}`);
|
||||
devLog(` Points won: ${doubleStat.points_won}, Points lost: ${doubleStat.points_lost}`);
|
||||
|
||||
// TODO: Store double statistics
|
||||
processedCount++;
|
||||
}
|
||||
}
|
||||
processedCount += await this.processSinglePlayerStatistics(teamData);
|
||||
processedCount += await this.processDoublePlayerStatistics(teamData);
|
||||
}
|
||||
|
||||
// Also process meetings from the player stats response
|
||||
@@ -579,6 +533,95 @@ class AutoFetchMatchResultsService {
|
||||
return processedCount;
|
||||
}
|
||||
|
||||
async processSinglePlayerStatistics(teamData) {
|
||||
if (!Array.isArray(teamData.single_player_statistics)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let processedCount = 0;
|
||||
|
||||
for (const playerStat of teamData.single_player_statistics) {
|
||||
devLog(`Player: ${playerStat.player_firstname} ${playerStat.player_lastname} (ID: ${playerStat.player_id})`);
|
||||
devLog(` Points won: ${playerStat.points_won}, Points lost: ${playerStat.points_lost}`);
|
||||
|
||||
const member = await this.matchPlayer(
|
||||
playerStat.player_id,
|
||||
playerStat.player_firstname,
|
||||
playerStat.player_lastname
|
||||
);
|
||||
|
||||
if (member) {
|
||||
devLog(` Matched with local member: ${member.firstName} ${member.lastName} (ID: ${member.id})`);
|
||||
await this.ensureMemberPlayerId(member, playerStat.player_id);
|
||||
} else {
|
||||
devLog(` No local member found for ${playerStat.player_firstname} ${playerStat.player_lastname}`);
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
}
|
||||
|
||||
return processedCount;
|
||||
}
|
||||
|
||||
async processDoublePlayerStatistics(teamData) {
|
||||
if (!Array.isArray(teamData.double_player_statistics)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let processedCount = 0;
|
||||
|
||||
for (const doubleStat of teamData.double_player_statistics) {
|
||||
const firstPlayerLabel = `${doubleStat.firstname_player_1} ${doubleStat.lastname_player_1}`.trim();
|
||||
const secondPlayerLabel = `${doubleStat.firstname_player_2} ${doubleStat.lastname_player_2}`.trim();
|
||||
|
||||
devLog(`Double: ${firstPlayerLabel} / ${secondPlayerLabel}`);
|
||||
devLog(` Points won: ${doubleStat.points_won}, Points lost: ${doubleStat.points_lost}`);
|
||||
|
||||
const [memberOne, memberTwo] = await Promise.all([
|
||||
this.matchPlayer(
|
||||
doubleStat.player_id_1 || doubleStat.player_1_id || null,
|
||||
doubleStat.firstname_player_1,
|
||||
doubleStat.lastname_player_1
|
||||
),
|
||||
this.matchPlayer(
|
||||
doubleStat.player_id_2 || doubleStat.player_2_id || null,
|
||||
doubleStat.firstname_player_2,
|
||||
doubleStat.lastname_player_2
|
||||
)
|
||||
]);
|
||||
|
||||
if (memberOne) {
|
||||
await this.ensureMemberPlayerId(memberOne, doubleStat.player_id_1 || doubleStat.player_1_id || null);
|
||||
} else {
|
||||
devLog(` No local member found for double player 1: ${firstPlayerLabel}`);
|
||||
}
|
||||
|
||||
if (memberTwo) {
|
||||
await this.ensureMemberPlayerId(memberTwo, doubleStat.player_id_2 || doubleStat.player_2_id || null);
|
||||
} else {
|
||||
devLog(` No local member found for double player 2: ${secondPlayerLabel}`);
|
||||
}
|
||||
|
||||
if (memberOne && memberTwo) {
|
||||
devLog(` Matched double with local members: ${memberOne.firstName} ${memberOne.lastName} / ${memberTwo.firstName} ${memberTwo.lastName}`);
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
}
|
||||
|
||||
return processedCount;
|
||||
}
|
||||
|
||||
async ensureMemberPlayerId(member, playerId) {
|
||||
if (!playerId || member.myTischtennisPlayerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
member.myTischtennisPlayerId = playerId;
|
||||
await member.save();
|
||||
devLog(` Updated myTischtennis Player ID for ${member.firstName} ${member.lastName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process match results from schedule/table data
|
||||
*/
|
||||
|
||||
@@ -39,21 +39,33 @@ Diese Liste beschreibt die naechsten sinnvollen Optimierungsschritte nach dem zu
|
||||
|
||||
## Prioritaet B
|
||||
|
||||
- [ ] Offene Backend-TODOs in `autoFetchMatchResultsService.js` schliessen.
|
||||
- [x] Offene Backend-TODOs in `autoFetchMatchResultsService.js` schliessen.
|
||||
Aktuelle Fundstellen:
|
||||
- Datenverarbeitung/Speicherung an einer Reststelle
|
||||
- Double-Statistiken noch nicht gespeichert
|
||||
Ziel:
|
||||
- Rest-TODOs entweder implementieren oder bewusst entfernen/dokumentieren
|
||||
Erledigt am 2026-03-17:
|
||||
- Single-/Double-Statistiken in explizite Verarbeitungsschritte aufgeteilt
|
||||
- Doppelpartner werden jetzt ebenfalls gegen lokale Mitglieder gematcht
|
||||
- myTischtennis-Spieler-IDs werden fuer beide Doppelspieler nachgezogen
|
||||
- offene Inline-TODOs im Service entfernt
|
||||
|
||||
- [ ] Rating-Update-Logik aus `MYTISCHTENNIS_AUTO_FETCH_README.md` wirklich fertigstellen oder die Doku an den Ist-Zustand angleichen.
|
||||
- [x] Rating-Update-Logik aus `MYTISCHTENNIS_AUTO_FETCH_README.md` wirklich fertigstellen oder die Doku an den Ist-Zustand angleichen.
|
||||
Grund: In der Doku stehen noch offene Kernpunkte, die spaeter verwirrend sind, wenn der Scheduler als "fertig" wahrgenommen wird.
|
||||
Erledigt am 2026-03-17:
|
||||
- Doku auf den aktuellen Scheduler-/HTTP-Trigger-Stand gebracht
|
||||
- Rating-Updates und Match-Results als aktiv dokumentiert
|
||||
- nur die tatsaechlich noch offenen fachlichen Restpunkte verbleiben
|
||||
|
||||
- [ ] Gruppenzuordnungs-REST-TODO in `DiaryView.vue` schliessen.
|
||||
- [x] Gruppenzuordnungs-REST-TODO in `DiaryView.vue` schliessen.
|
||||
Aktuelle Fundstelle:
|
||||
- `// TODO: API-Call zum Speichern der Gruppenzuordnung`
|
||||
Ziel:
|
||||
- keine offenen Inline-TODOs in produktiv genutzten Hauptviews
|
||||
Erledigt am 2026-03-17:
|
||||
- toten lokalen Stub fuer Aktivitaets-Gruppenzuordnung entfernt
|
||||
- damit keine offene Schein-REST-Stelle mehr in der Hauptview
|
||||
|
||||
## Prioritaet C
|
||||
|
||||
|
||||
@@ -3245,26 +3245,6 @@ export default {
|
||||
return setIds && setIds.size > 0;
|
||||
},
|
||||
|
||||
getActivityGroup(activityId) {
|
||||
return this.activityGroupsMap[activityId] || '';
|
||||
},
|
||||
|
||||
async updateActivityGroup(activityId, groupId) {
|
||||
try {
|
||||
// Hier würde normalerweise ein API-Call gemacht werden
|
||||
// Für jetzt speichern wir es nur lokal
|
||||
this.activityGroupsMap[activityId] = groupId || '';
|
||||
|
||||
// TODO: API-Call zum Speichern der Gruppenzuordnung
|
||||
// await apiClient.put(`/diary-date-activities/${activityId}/group`, { groupId });
|
||||
|
||||
this.showInfo('Erfolg', 'Gruppenzuordnung aktualisiert', '', 'success');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Gruppenzuordnung:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren der Gruppenzuordnung', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk-Zuordnungen für Aktivitäten
|
||||
async assignAllMembersToActivity(activityId) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user