Calcul du ratio de pays de l'UEFA

image

Il existe deux tournois populaires dans le football: la Ligue des champions et la Ligue Europa. Sur la base de leurs résultats, la soi-disant notation des associations de football est calculée. Sur la base de cette note, il est également déterminé combien d'équipes de chaque pays participeront aux tournois suivants.

Dans cet article, je vais créer une application basée sur la plateforme lsFusion ouverte et gratuite qui calculera cette note. Il stockera toutes ses données dans PostgreSQL, fournira une interface Web pour les modifier et les afficher avec des capacités de filtrage et de tri, ainsi que d'importer les résultats des correspondances à l'aide d'une API spéciale.

Tout le code pour implémenter cette application sera composé d'environ 300 lignes significatives.

Logique de domaine


La création de tout système d'information commence par la tâche de la logique du domaine.
Tout d'abord, il est logique de distinguer les répertoires les plus simples qui n'ont qu'un code et un nom:

  • Tournoi Ligue des Champions ou Ligue Europa.
  • Saison . 2018-2019 / 2017-2018 etc.
  • Ronde . Finale, demi-finale, phase de groupes, etc. Il peut être considéré comme une composition pour le tournoi, mais dans cette mise en œuvre, je l'ai choisi comme une entité distincte.
  • Pays Cette application est utilisée comme association de football. Par exemple, le club de Monaco est situé dans le pays de Monaco, mais joue en championnat de France.
  • Club Barcelone, Real Madrid, Manchester United, etc.

Puisque lsFusion utilise le même type de logique pour les déclarer, nous déclarerons un métacode (ou modèle de code) qui générera la logique correspondante:

Métacode de déclaration de répertoire
META defineMasterObject(object, caption, captions, nameLength)
CLASS ### object caption;

id '' = DATA INTEGER ( ### object);
object ( INTEGER id) = GROUP AGGR ### object o BY id(o); //

name '' = DATA ISTRING ( ### object) CHARWIDTH nameLength;
object ( ISTRING name) = GROUP AGGR ### object o BY name(o); //

FORM object caption
OBJECTS o = ### object PANEL
PROPERTIES (o) id, name

EDIT ### object OBJECT o
;

FORM object ## s captions
OBJECTS o = ### object
PROPERTIES (o) READONLY id, name
PROPERTIES (o) NEWSESSION NEW , EDIT , DELETE
;

FORM dialog ### object ## s captions
OBJECTS o = ### object
PROPERTIES (o) READONLY id, name

LIST ### object OBJECT o
;

NAVIGATOR {
NEW object ## s;
}
END


Il annoncera:

  • Classe avec prénom
  • Propriétés avec code et nom pour la nouvelle classe
  • Trois formulaires: l'édition d'un objet, un formulaire avec une liste de tous les objets, qui est ensuite ajouté au navigateur, une boîte de dialogue pour sélectionner cet objet. En tant que dialogue, vous pouvez utiliser le deuxième formulaire, mais l'utilisateur aura alors la possibilité de modifier les objets lors de son choix, ce qui peut entraîner des erreurs de la part des utilisateurs.

Quatre paramètres sont transmis au métacode:

  • Identifiant (objet) . Avec ce nom, des classes et des formulaires seront créés. La construction ### est utilisée pour rendre la première lettre de l'identifiant en majuscule dans le code résultant.
  • Nom au singulier . Utilisé pour le titre de la classe et du formulaire.
  • Le nom est au pluriel . Utilisé pour la zone de liste déroulante et la boîte de dialogue.
  • Longueur du nom . Dans les noms des différents objets, différentes longueurs sont attendues, ce qui est important lors de la construction de l'interface.

À l'aide du métacode créé, ajoutez les cinq entités décrites ci-dessus:
@defineMasterObject (tournament, '' , '' , 20 );
@defineMasterObject (season, '' , '' , 5 );
@defineMasterObject (round, '' , '' , 15 );
@defineMasterObject (country, '' , '' , 10 );
@defineMasterObject (team, '' , '' , 20 );


Le code généré, par exemple, pour un tournoi ressemblera à ceci:
CLASS Tournament '' ;

id '' = DATA INTEGER (Tournament);
tournament ( INTEGER id) = GROUP AGGR Tournament o BY id(o);

name '' = DATA ISTRING (Tournament) CHARWIDTH 20 ;
tournament ( ISTRING name) = GROUP AGGR Tournament o BY name(o);

FORM tournament ''
OBJECTS o = Tournament PANEL
PROPERTIES (o) id, name

EDIT Tournament OBJECT o
;

FORM tournaments ''
OBJECTS o = Tournament
PROPERTIES (o) READONLY id, name
PROPERTIES (o) NEWSESSION NEW , EDIT , DELETE
;

FORM dialogTournaments ''
OBJECTS o = Tournament
PROPERTIES (o) READONLY id, name

LIST Tournament OBJECT o
;

NAVIGATOR {
NEW tournaments;
}

Ajoutez le lien vers le pays à la logique de club générée. Pour ce faire, créez d'abord la propriété correspondante, puis placez-la sur les formulaires d'édition et de visualisation du club:
country = DATA Country (Team);
nameCountry '' (Team t) = name(country(t));

EXTEND FORM team PROPERTIES (o) nameCountry;
EXTEND FORM teams PROPERTIES (o) READONLY nameCountry;

Nous mettons toute la logique créée dans un module Master séparé (fichier Master.lsf).

Créez maintenant une entité League . Elle déterminera le tournoi d'une saison particulière. Par exemple, Champions League 2017-18 ou Europa League 2018-19. La ligue n'aura pas de nom, mais seulement des liens vers le tournoi et la saison. Par conséquent, nous n'utiliserons pas le métacode précédent, mais nous ferons la même logique et le placerons dans le nouveau module League:
MODULE League;

REQUIRE Master;

CLASS League '' ;

id '' = DATA INTEGER (League);
league ( INTEGER id) = GROUP AGGR League o BY id(o);

tournament = DATA Tournament (League);
nameTournament '' (League l)= name(tournament(l));

season = DATA Season(League);
nameSeason '' (League l)= name(season(l));

FORM league ''
OBJECTS o = League PANEL
PROPERTIES (o) id, nameTournament, nameSeason

EDIT League OBJECT o
;

FORM leagues ''
OBJECTS o = League
PROPERTIES (o) READONLY id, nameTournament, nameSeason
PROPERTIES (o) NEWSESSION NEW , EDIT , DELETE
;

FORM dialogLeagues ''
OBJECTS o = League
PROPERTIES (o) READONLY id, nameTournament, nameSeason

LIST League OBJECT o
;

NAVIGATOR {
NEW leagues;
}

Et enfin, ajoutez la logique des matchs. Pour ce faire, créez la classe Match , qui fera référence à la ligue et au tour. Pour lui, les clubs qui y ont participé, et le résultat sera également demandé. Nous mettons tout cela dans un module Match séparé:
MODULE Match;

REQUIRE League;

CLASS Match '' ;

id '' = DATA INTEGER (Match);
match ( INTEGER id) = GROUP AGGR Match o BY id(o);

dateTime '' = DATA DATETIME (Match);

league = DATA League (Match);

tournament (Match m) = tournament(league(m));
nameTournament '' (Match m) = name(tournament(m));

season(Match m) = season(league(m));
nameSeason '' (Match m) = name(season(m));

round = DATA Round (Match);
nameRound '' (Match m) = name(round(m));

homeTeam = DATA Team (Match);
nameHomeTeam '' (Match m) = name(homeTeam(m));

awayTeam = DATA Team (Match);
nameAwayTeam '' (Match m) = name(awayTeam(m));

goalsHome ' ()' = DATA INTEGER (Match);
goalsAway ' ()' = DATA INTEGER (Match);

FORM match ''
OBJECTS o = Match PANEL
PROPERTIES (o) id, dateTime, nameTournament, nameSeason, nameRound,
nameHomeTeam, goalsHome, goalsAway, nameAwayTeam

EDIT Match OBJECT o
;

FORM matches ''
OBJECTS o = Match
PROPERTIES (o) READONLY id, dateTime, nameTournament, nameSeason, nameRound,
nameHomeTeam, goalsHome, goalsAway, nameAwayTeam
PROPERTIES (o) NEWSESSION NEW , EDIT , DELETE

LIST Match OBJECT o
;

NAVIGATOR {
NEW matches;
}


Importation de données


Malheureusement, j'ai réussi à trouver une seule API publique et gratuite qui prend en charge toutes les Eurocups. Il s'agit de l'API Football . Cependant, il y a des problèmes:

  • Il n'y a pas de résultats avant 2016.
  • Il n'y a pas de qualification pour la Ligue Europa jusqu'en 2018.
  • Il y a certaines erreurs dans les données. Par exemple, Irtysh Pavlodar est affecté à la Russie, bien que ce club représente le Kazakhstan. En outre, Europa Fc, pour une raison quelconque, fait référence à l'Espagne au lieu de Gibraltar.


Les erreurs de données peuvent être corrigées manuellement à l'aide de formulaires créés précédemment. Cependant, puisque le calcul du coefficient total est basé sur les cinq dernières années, malheureusement, cela ne fonctionnera pas pour calculer le coefficient total à partir des données de l'API Football. Si quelqu'un dans les commentaires suggère où obtenir les données nécessaires dans n'importe quel format des années précédentes, je serai très reconnaissant. Mais, comme il existe des données complètes pour 2018, il sera possible de vérifier l'exactitude du calcul pendant au moins cette année.

L'API dont nous avons besoin est implémentée sous la forme de requêtes HTTP, où les paramètres sont transmis via l'URL, et une clé d'accès spéciale est indiquée dans l'en-tête. Déclarez la logique correspondante:
host = 'api-football-v1.p.rapidapi.com' ;
key ' API Football' = DATA STRING () CHARWIDTH 50 ;

url = 'https://' + host() + '/v2' ;

headers( TEXT name) = CASE
WHEN name = 'x-rapidapi-host' THEN host()
WHEN name = 'x-rapidapi-key' THEN key();

Toutes les actions d'importation de données seront placées sur le formulaire de ligues créé précédemment. Là, nous placerons la clé d'accès dans la barre d'outils du tableau avec la liste des ligues:
EXTEND FORM leagues
PROPERTIES () key DRAW o TOOLBAR
;


Tout d'abord, nous mettons en œuvre la liste des ligues. Pour cela, l'API Football a une URL spéciale: / ligues. Une requête GET lui renvoie JSON du formulaire:

La réponse
 { "api":{ "results":2, "leagues":[ { "league_id":1, "name":"2018 Russia World Cup", "country":"World", "country_code":null, "season":2018, "season_start":"2018-06-14", "season_end":"2018-07-15", "logo":"https://www.api-football.com/public/leagues/1.png", "flag":null, "standings":0, "is_current":1 }, { "league_id":2, "name":"Premier League", "country":"England", "country_code":"GB", "season":2018, "season_start":"2018-08-10", "season_end":"2019-05-12", "logo":"https://www.api-football.com/public/leagues/2.png", "flag":"https://www.api-football.com/public/flags/gb.svg", "standings":1, "is_current":1 } ] } } 


Pour générer une demande GET et enregistrer le corps de réponse, la construction suivante est utilisée:
LOCAL result = FILE ();
EXTERNAL HTTP GET url() + '/leagues' HEADERS headers TO result;

Il écrit le résultat dans la propriété de résultat local sans paramètres de type FILE.

Pour analyser un fichier au format JSON, un formulaire est construit dont la structure correspond à la structure JSON. Vous pouvez le générer dans l'EDI en utilisant l'élément de menu:

image

Pour le JSON ci-dessus, le formulaire ressemblera à ceci (en prenant en compte uniquement les valeurs qui seront importées):
GROUP api;

tournamentName = DATA LOCAL STRING ( INTEGER );
seasonName = DATA LOCAL STRING ( INTEGER );
leagueId = DATA LOCAL INTEGER ( INTEGER );

FORM importLeagues
OBJECTS leagues = INTEGER IN api
PROPERTIES (leagues) name = tournamentName, season = seasonName, league_id = leagueId
;

Pour importer directement à partir de la propriété de résultat JSON au format du formulaire importLeagues, utilisez la commande suivante:
IMPORT importLeagues JSON FROM result();

Après son exécution, les valeurs correspondantes du fichier JSON seront placées dans les propriétés tournoiName , seasonName et leagueId :

image

Autrement dit, la valeur de tournoiName (0) sera «Coupe du monde» et, dans tournoiName (1), ce sera «Premier League».

Malheureusement, API Football n'a pas du tout d'entité de tournoi. La seule façon de lier toutes les ligues est d'avoir un nom qui correspond aux ligues d'un même tournoi de saisons différentes. Pour ce faire, lors de l'importation, nous regroupons d'abord tous les noms des ligues importées et, sinon dans la base de données, créons de nouveaux tournois:
FOR [ GROUP SUM 1 BY tournamentName( INTEGER i)]( STRING tn) AND NOT tournament(tn) DO NEW t = Tournament {
name(t) <- tn;
}

Il n'y a pas non plus de codes pour les saisons, donc lors de l'importation de ligues, ils sont créés de la même manière. Une fois les objets manquants créés, les ligues sont importées directement. Les tournois et les saisons sont recherchés par nom en utilisant les propriétés construites précédemment via GROUP AGGR :
FOR leagueId( INTEGER i) AND NOT league(leagueId(i)) DO NEW l = League {
id(l) <- leagueId(i);
tournament(l) <- tournament(tournamentName(i));
season(l) <- season(seasonName(i));
}

Par défaut, les données seront chargées, mais ne seront enregistrées dans la base de données que lorsque l'utilisateur cliquera sur le bouton Enregistrer du formulaire. Si nécessaire, vous pouvez ajouter la commande APPLY à la fin de l'action afin qu'elle soit immédiatement enregistrée dans la base de données sans aperçu.

Et enfin, ajoutez l'action d'importation au formulaire de liste de ligue:
EXTEND FORM leagues
PROPERTIES () importLeagues DRAW o TOOLBAR
;

De même, nous importons des clubs et des matchs. Cependant, comme l'API offre la possibilité de les importer uniquement pour une ligue spécifique, l'action doit prendre la ligue en entrée:

Importer des clubs et des matchs
//
teamId = DATA LOCAL INTEGER ( INTEGER );
teamName = DATA LOCAL STRING ( INTEGER );
countryName = DATA LOCAL STRING ( INTEGER );

FORM importTeams
OBJECTS teams = INTEGER IN api
PROPERTIES (teams) team_id = teamId, name = teamName, country = countryName
;

importTeams ' ' (League l) {
LOCAL result = FILE ();
EXTERNAL HTTP GET url() + '/teams/league/' + id(l) HEADERS headers TO result;

IMPORT importTeams JSON FROM result();
FOR [ GROUP SUM 1 BY countryName( INTEGER i)]( STRING cn) AND NOT country(cn) DO NEW c = Country {
name(c) <- cn;
}

FOR teamId( INTEGER i) AND NOT team(teamId(i)) DO NEW t = Team {
id(t) <- teamId(i);
name(t) <- teamName(i);
country(t) <- country(countryName(i));
}
}

//

matchId = DATA LOCAL INTEGER ( INTEGER );
dateTime = DATA LOCAL STRING ( INTEGER );
roundName = DATA LOCAL STRING ( INTEGER );

GROUP homeTeam;
homeTeamId = DATA LOCAL INTEGER ( INTEGER );

GROUP awayTeam;
awayTeamId = DATA LOCAL INTEGER ( INTEGER );

goalsHome = DATA LOCAL INTEGER ( INTEGER );
goalsAway = DATA LOCAL INTEGER ( INTEGER );

FORM importMatches
OBJECTS fixtures = INTEGER IN api
PROPERTIES (fixtures) fixture_id = matchId, league_id = leagueId, event_date = dateTime, round = roundName,
homeTeamId IN homeTeam EXTID 'team_id' ,
awayTeamId IN awayTeam EXTID 'team_id' ,
goalsHomeTeam = goalsHome, goalsAwayTeam = goalsAway
;

importMatches ' ' (League l) {
LOCAL result = FILE ();
EXTERNAL HTTP GET url() + '/fixtures/league/' + id(l) HEADERS headers TO result;

IMPORT importMatches JSON FROM result();
FOR [ GROUP SUM 1 BY awayTeamId( INTEGER i)]( INTEGER id) AND NOT team(id) DO {
MESSAGE ' ' + id;
RETURN ;
}

FOR [ GROUP SUM 1 BY awayTeamId( INTEGER i)]( INTEGER id) AND NOT team(id) DO {
MESSAGE ' ' + id;
RETURN ;
}

FOR [ GROUP SUM 1 BY roundName( INTEGER i)]( STRING rn) AND NOT round(rn) DO NEW r = Round {
name(r) <- rn;
}

FOR matchId( INTEGER i) AND NOT match(matchId(i)) DO NEW m = Match {
id(m) <- matchId(i);
dateTime(m) <- toDateTimeFormat(left(dateTime(i), 19 ), 'yyyy-MM-ddThh24:mi:ss' );
league(m) <- league(leagueId(i));
round(m) <- round(roundName(i));
homeTeam(m) <- team(homeTeamId(i));
awayTeam(m) <- team(awayTeamId(i));
goalsHome(m) <- goalsHome(i);
goalsAway(m) <- goalsAway(i);
}
}

Il y a une particularité pour les matchs: les codes d'équipe vont dans des balises homeTeam et awayTeam supplémentaires. Des groupes correspondants sont créés pour eux par analogie avec l'API. De plus, à l'intérieur, ils ont les mêmes balises team_id . Étant donné que les propriétés portant le même nom ne peuvent pas être ajoutées au formulaire, le mot clé spécial EXTID est utilisé , qui définit le nom de la balise dans le JSON importé.

Pour que toutes les importations soient sous la même forme, et comme elles sont liées aux ligues, nous les prenons toutes sous la même forme. De plus, nous ajoutons des équipes et des matchs au formulaire afin de pouvoir voir ce qui est importé avant de sauvegarder:
EXTEND FORM leagues
OBJECTS t = Team
PROPERTIES (t) READONLY id, name

PROPERTIES importTeams(o) DRAW t TOOLBAR

OBJECTS m = Match
PROPERTIES (m) READONLY id, dateTime, nameRound, nameHomeTeam, goalsHome, goalsAway, nameAwayTeam
FILTERS league(m) = o

PROPERTIES importMatches(o) DRAW m TOOLBAR
;

DESIGN leagues {
OBJECTS {
NEW leagueDetails {
fill = 2 ;
type = SPLITH ;
MOVE BOX (t);
MOVE BOX (m);
}
}
}


Le formulaire résultant ressemblera à ceci:
image

Toutes les importations seront placées dans un module APIFootball séparé.

Calcul du coefficient


Nous procédons directement au calcul du coefficient de pays de l'UEFA. Pour cela, il est logique de mettre tout le code dans un module UEFA spécialement installé.

Tout d'abord, gardez à l'esprit que l'API Football fournit une interface pour importer tous les matchs, et pas seulement les Eurocups. Par conséquent, nous séparons les matchs d'Eurocup en fonction du nom du tournoi (il est plus correct d'avoir une propriété principale distincte pour cela, mais la mise en œuvre des propriétés peut toujours être modifiée sans modifier le reste de la logique):
isCL (Tournament t) = name(t) = 'Champions League' ;
isEL (Tournament t) = name(t) = 'Europa League' ;

isUL (Tournament t) = isCL(t) OR isEL(t);
isUL (Match m) = isUL(tournament(m));

Tout d'abord, calculons les points que chaque club reçoit au cours d'une saison pour les résultats de matchs spécifiques.
Pendant cette période, chaque équipe reçoit:
2 points en cas de victoire;
1 point en cas d'égalité.
Depuis 1999, ces points sont divisés en deux s'ils sont gagnés lors des tours de qualification, soit:
1 point en cas de victoire;
0,5 point pour une égalité.


Créez des propriétés auxiliaires qui déterminent la relation entre le match et le club:
played (Team t, Match m) = homeTeam(m) = t OR awayTeam(m) = t;
won (Team t, Match m) = (homeTeam(m) = t AND goalsHome(m) > goalsAway(m)) OR (awayTeam(m) = t AND goalsHome(m) < goalsAway(m));
draw (Team t, Match m) = played(t, m) AND goalsHome(m) = goalsAway(m);

Pour déterminer le nombre de points marqués dans chaque match, nous ajoutons la propriété principale du type numérique pour le tour, qui par défaut sera égale à un:
dataMatchCoeff = DATA NUMERIC [ 10 , 1 ] (Round);
matchCoeff ' ' (Round r) = OVERRIDE dataMatchCoeff(r), 1.0 ;

Ensuite, nous comptons les points pour les victoires et les nuls et additionnons:
wonPoints ' ' (Season s, Team t) =
GROUP SUM 2 * matchCoeff(round(Match m)) IF won(t, m) AND season(m) = s AND isUL(m);
drawPoints ' ' (Season s, Team t) =
GROUP SUM 1 * matchCoeff(round(Match m)) IF draw(t, m) AND season(m) = s AND isUL(m);
matchPoints ' ' (Season s, Team t) = wonPoints(s, t) (+) drawPoints(s, t) MATERIALIZED ;

Les points pour les matchs sont marqués comme MATÉRIALISÉS afin qu'ils soient enregistrés dans le tableau et non calculés à chaque fois.

Maintenant, vous devez compter les points bonus:
De plus, des points bonus sont accordés:
1 point est accordé si l'équipe atteint les quarts de finale, les demi-finales et les finales dans les coupes européennes;
4 points pour atteindre la phase de groupes de la Ligue des champions (jusqu'en 1996 - 2 points, de 1997 à 2003 - 1 point, de 2004 à 2008 - 3 points);
5 points en cas de départ d'une équipe pour les 1/8 de finale de la Ligue des champions (avant 2008 - 1 point).
Seuls les matchs joués sont pris en compte (les pertes techniques ne sont pas prises en compte). Les matchs se terminant par une série de tirs au but, lors du calcul du coefficient, sont considérés en fonction du résultat, qui est fixé par les résultats du match dans le temps principal et le temps supplémentaire.

Dans cette mise en œuvre, nous supposons que le club a participé au tour du tournoi s'il y a joué au moins un match. Pour ce faire, nous calculons le nombre de matches joués par le club au cours d'une saison, d'un tournoi ou d'une manche en particulier:
played '' (Season s, Tournament t, Round r, Team tm) =
GROUP SUM 1 IF played(tm, Match m) AND round(m) = r AND tournament(m) = t AND season(m) = s;


Vous devez maintenant déterminer le nombre de points à marquer pour le passage dans un tour particulier. Comme cela dépend du tournoi (par exemple, un passage en ⅛ Ligue des Champions se voit attribuer 5 points, mais rien en Ligue Europa). Pour ce faire, nous introduisons la propriété primaire:
bonusPoints ' ' = DATA NUMERIC [ 10 , 1 ] (Tournament, Round);

Calculons maintenant les points bonus et le nombre total de points pour le club pour la saison:
bonusPoints ' ' (Season s, Team tm) = GROUP SUM bonusPoints(Tournament t, Round r) IF played(s, t, r, tm) MATERIALIZED ;

points '' (Season s, Team tm) = matchPoints(s, tm) (+) bonusPoints(s, tm);

Enfin, nous passons directement au coefficient pays.
Pour calculer la note de l'association, tous les points marqués par les clubs participant à la Ligue des Champions et à la Ligue Europa sont additionnés, et le résultat est divisé par le nombre de clubs de cette association [2] [3].

Calculons le nombre de clubs de chaque association ayant participé à des compétitions européennes:
matchesUL ' ' (Season s, Team t) = GROUP SUM 1 IF played(t, Match m) AND season(m) = s AND isUL(m);
teams '' (Season s, Country c) = GROUP SUM 1 IF matchesUL(s, Team t) AND country(t) = c;

Maintenant, nous considérons le nombre total de points d'association pour la saison et divisons par le nombre de clubs:
totalPoints ' ()' (Season s, Country c) = GROUP SUM points(s, Team t) IF country(t) = c;
points '' (Season s, Country c) = trunc( NUMERIC [ 13 , 4 ](totalPoints(s, c)) / teams(s, c), 3 );


La note d'un pays est la somme des coefficients du pays pour les 5 années précédentes.


Pour ce faire, nous numérotons toutes les saisons à partir de la dernière par le code interne (nous supposons que ces dernières ont été ajoutées plus tard et ont un code plus grand):
index '' (Season s) = PARTITION SUM 1 IF s IS Season ORDER DESC s;

Si nécessaire, vous pouvez saisir un champ ou un numéro distinct par nom.
Il ne reste plus qu'à calculer la note finale du pays:
rating '' (Country c) = GROUP SUM points(Season s, c) IF index(s) <= 5 ;

Ci-dessus, nous avons annoncé des cotes pour les tournois et les tours. Ajoutez-les au formulaire d'édition de tournoi, tout en filtrant uniquement les tours qui se trouvaient dans ces tournois:
matches (Tournament t, Round r) = GROUP SUM 1 IF tournament(Match m) = t AND round(m) = r;

EXTEND FORM tournament
OBJECTS r = Round
PROPERTIES name(r) READONLY , matchCoeff(r), bonusPoints(o, r)
FILTERS matches(o, r)
;


Les paramètres de cotes, par exemple, pour la Ligue des champions, vous devez définir comme ceci:
image

Tirons un formulaire qui affichera le classement, où les équipes seront affichées pour chaque pays, et pour chaque équipe ses matchs:
FORM countryCoefficientUEFA ' UEFA'
OBJECTS s = Season
FILTERS index(s) <= 5

OBJECTS c = Country
PROPERTIES (c) READONLY name, rating
PROPERTIES (s, c) COLUMNS (s) points HEADER ' : ' + name(s), teams HEADER ' : ' + name(s)

OBJECTS t = Team
PROPERTIES (t) READONLY nameCountry, name
PROPERTIES (s,t) COLUMNS (s) HEADER name(s) points BACKGROUND matchesUL(s, t)
FILTERGROUP country
FILTER ' ' country(t) = c DEFAULT

OBJECTS m = Match
PROPERTIES (m) READONLY dateTime, nameTournament, nameSeason, nameRound,
nameHomeTeam,
goalsHome BACKGROUND goalsHome(m) > goalsAway(m),
goalsAway BACKGROUND goalsHome(m) < goalsAway(m),
nameAwayTeam
FILTERS played(t, m)
ORDER dateTime(m) DESC
;

DESIGN countryCoefficientUEFA {
OBJECTS {
NEW countryDetails {
type = SPLITH ;
fill = 0.5 ;
MOVE BOX (t);
MOVE BOX (m);
}
}

}

NAVIGATOR {
NEW countryCoefficientUEFA;
}

Le formulaire résultant ressemblera à ceci:
image
La couleur dans les tableaux des clubs montre quand il a participé aux saisons, et dans le tableau des matchs - qui a gagné.
L'image montre que les notes pour 2018 sont calculées exactement de la même manière que sur Wikipedia. Pour les années précédentes, comme mentionné ci-dessus, l'API Football ne fournit pas toutes les informations.

Résumé



Nous avons construit une petite application qui est entièrement décrite par le code ci-dessus et stocke ses données dans PostgreSQL, fournit une interface Web pour visualiser et éditer les données. Dans le même temps, il fonctionnera efficacement sur de gros volumes, car tous les formulaires lisent uniquement la fenêtre visible. Les filtres, le tri, le téléchargement vers Excel, etc. sont également prêts à l'emploi.

Il convient de noter la facilité avec laquelle la tâche de calcul du coefficient a été décomposée en propriétés individuelles. Une fois exécutée, toute cette logique sera traduite en requêtes SQL et tous les calculs seront effectués directement sur le serveur de base de données en utilisant toutes les optimisations du SGBD.

Un exemple du fonctionnement de l'application avec les données qui y sont chargées peut être trouvé à l' adresse : https://demo.lsfusion.org/euroleague . Connexion invité sans mot de passe. L'utilisateur est en mode lecture seule.

Ceux qui le souhaitent peuvent tout régler eux-mêmes localement et, par exemple, modéliser les coefficients en entrant les résultats des futurs matchs. Tous les modules d'application décrits ci-dessus sont hébergés sur github . Après l'installation automatique, il vous suffit de faire apparaître ces fichiers dans le dossier approprié à partir des instructions et de redémarrer le serveur.

Pour télécharger des données à partir de l'API Football, vous devez vous inscrire auprès d'eux et obtenir la clé API. Cela nécessite une carte, mais si vous ne faites pas plus de 50 demandes par jour, rien ne sera déduit de celle-ci.

De plus, vous pouvez exécuter cette application en ligne dans la section appropriée du site. Dans l'onglet Plate-forme, sélectionnez l'exemple de calcul des cotes de l'UEFA et cliquez sur Jouer.

Soit dit en passant, si quelqu'un a besoin d'implémenter un système simple pour lequel Excel ne convient plus, écrivez dans les commentaires. Afin d'apprendre les capacités de la plateforme, nous allons essayer de l'implémenter et écrire l'article correspondant.

Source: https://habr.com/ru/post/fr467279/


All Articles