Code source de pyspc.data.prevision.prevision19

#!/usr/bin/python3
# -*- coding: utf-8 -*-
########################################################################
#
# This file is part of python module <pyspc>.
# Copyright (C) 2013-2021  R. Marty
#   (renaud.marty@developpement-durable.gouv.fr)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see COPYING.txt).
# If not, see <http://www.gnu.org/licenses/>.
#
########################################################################
"""
Données d'observation et de prévision - Prévisions du SPC LCI - Version 2019
"""
import collections
from datetime import datetime as dt, timedelta as td
import os.path
import pandas as pnd

import pyspc.core.exception as _exception
from pyspc.io.dbase.mdb import Mdb
from pyspc.io.dbase.sdb import Sdb

from pyspc.convention.prevision import (
    P19_DATATYPES,
    P19_ATTS_MODEL, P19_SQL_MODEL, P19_SQL_INSERTMODEL, P19_TABLES_MODEL,
    P19_ATTS_META, P19_SQL_META, P19_SQL_INSERTMETA, P19_TABLES_META,
    P19_SQL_UNIQUEMETA, P19_STATUS_META,
    P19_ATTS_DATA, P19_SQL_DATA, P19_SQL_INSERTDATA, P19_TABLES_DATA,
)


[docs] class Prevision19(Mdb, Sdb): """ Classe destinée à traiter la base Prévision du SPC LCI, période 2019-202. Attributes ---------- filename : str Chemin de la base de données sql : str Requête courante au format SQL subtype : str Sous-type de base de données """
[docs] def __init__(self, filename=None): """ Initialisation de l'instance Prevision17. Parameters ---------- filename : str Chemin de la base de données """ _exception.check_str(filename) self.filename = filename self._constructor_parent.__init__(self, filename=filename) self.subtype = os.path.splitext(os.path.basename(self.filename))[-1] self.sql = None
def __str__(self): """Afficher les méta-données.""" cname = self.__class__.__name__ text = """ ************************************* ********* {cname} ******************* ************************************* * NOM FICHIER = {filename} * TYPE FICHIER = {subtype} * REQUETE SQL = \n{sql} ************************************* """ return text.format(cname=cname, **vars(self)) @property def _constructor_parent(self): """Importer Sdb ou Mdb selon le fichier.""" if self.filename.endswith('.sqlite'): return Sdb if self.filename.endswith('.mdb'): return Mdb raise ValueError('Extension du fichier incorrecte')
[docs] def connect(self): """ Créer la connexion à la base de données et le curseur. <mdb> ou <sqlite3> """ return self._constructor_parent.connect(self)
[docs] def execute(self, warning=True): """Exécution de la requête SQL.""" return self._constructor_parent.execute(self, warning=warning)
[docs] def lastrowid(self): """Récupérer l'identifiant du dernier enregistrement.""" return self._constructor_parent.lastrowid(self)
[docs] @classmethod def get_datatypes(cls): """ Type de données des bases Prevision 2019. Returns ------- list Type de données des bases Prevision 2019 """ return sorted(P19_DATATYPES)
@staticmethod def _check_codes(codes=None): """ Contrôle des identifiants. Parameters ---------- codes : list Liste des identifiants des sites/stations """ if isinstance(codes, str): codes = [codes] _exception.check_listlike(codes) return codes @staticmethod def _check_dtime(dtime): """ Contrôler si la date est sous la forme d'un objet datetime. Parameters ---------- dtime : datetime Date """ if dtime is None: return None _exception.check_dt(dtime) return True @staticmethod def _check_series(nseries=None): """ Contrôle des identifiants. Parameters ---------- nseries : list Liste des clés primaire des séries """ if isinstance(nseries, int): nseries = [nseries] _exception.check_listlike(nseries) return nseries @staticmethod def _check_valid_released(valid, released): """ Contrôler si les arguments valid et released sont cohérents. Parameters ---------- valid : bool Prévision expertisée ? released : bool Prévision diffusée ? Raises ------ ValueError si released = True et si valid = False """ if released and not valid: raise ValueError(f'Incohérence entre {valid=} et {released=}')
[docs] def read_fcst(self, codes=None, first_dt=None, last_dt=None, valid=False, released=False, unique=False, warning=True): """ Récupération des prévisions (séries+valeurs) de la base Prevision19. Parameters ---------- codes : list Liste des identifiants des sites/stations first_dt : datetime Première instant de prévision à considérer last_dt : datetime Dernier instant de prévision à considérer valid : bool Seulement les prévisions validées (True) ou toutes les prévisions produites (False) Défaut: False released : bool Seulement les prévisions diffusées (True). Défaut: False unique : bool Imposer l'unicité des séries (True). Défaut: False warning : bool Afficher les avertissements ? défaut: True Returns ------- dfs : dict Dictionnaire de DataFrame {nserie: df} See Also -------- Prevision19.get_series Prevision19.get_values """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- # Gestion des dates self._check_dtime(dtime=first_dt) self._check_dtime(dtime=last_dt) # Autres contrôles self._check_codes(codes=codes) self._check_valid_released(valid=valid, released=released) # --------------------------------------------------------------------- # 1- Recherche des séries # --------------------------------------------------------------------- # appel get_series _, series = self.read_series( codes=codes, first_dt=first_dt, last_dt=last_dt, valid=valid, released=released, warning=warning ) if self.check_sql_return(content=series, warning=warning) is None: return None # si unicité demandée : appel unique_series nseries = list(series.keys()) if unique: nseries = self.unique_series( nseries=nseries, valid=valid, warning=warning ) # --------------------------------------------------------------------- # 2- Recherche des valeurs # --------------------------------------------------------------------- # appel get_values dfs = self.read_values( nseries=nseries, valid=valid, warning=warning ) # --------------------------------------------------------------------- # 3- Fusion des données et des méta-données # --------------------------------------------------------------------- for n in dfs: # Ajout des méta-données dans les noms des colonnes runtime = dt.strptime(series[n]['DateDerObs'], '%Y%m%d%H%M') if valid: dfs[n].columns = [(series[n]['CodeStation'], runtime, series[n]['CodeModele'], series[n]['Source'], bool(series[n]['Diffuse'] is not None), c) for c in dfs[n].columns] elif unique: dfs[n].columns = [(series[n]['CodeStation'], runtime, series[n]['CodeModele'], c) for c in dfs[n].columns] else: dfs[n].columns = [(series[n]['CodeStation'], runtime, series[n]['CodeModele'], series[n]['NSerie'], c) for c in dfs[n].columns] # Conversion échéance -> date de validité dfs[n].index = [runtime + i * td(hours=1) for i in dfs[n].index] dfs[n].index.name = P19_ATTS_DATA[valid][1] # --------------------------------------------------------------------- # 4- Retour # --------------------------------------------------------------------- return dfs
[docs] def read_models(self): """ Extraire les informations sur les modèles de prévision. Returns ------- atts : list Liste des attributs infos : dict Dictionnaire des modèles """ # --------------------------------------------------------------------- # 1- Requête SQL # --------------------------------------------------------------------- # définition des éléments de la requête SQL atts = P19_ATTS_MODEL[self.subtype] table = P19_TABLES_MODEL[self.subtype] sql_atts = ",".join(['"' + att + '"' for att in atts]) # définition de la requête SQL self.sql = P19_SQL_MODEL.format(sql_atts, table) # --------------------------------------------------------------------- # 2- Appliquer la requête SQL MODELE # --------------------------------------------------------------------- self.connect() content = self.execute() self.close() # --------------------------------------------------------------------- # 3- Traitement du résultat de la requête SQL # --------------------------------------------------------------------- if self.check_sql_return(content=content, warning=True) is None: return atts, {} infos = collections.OrderedDict() for c in content: infos.setdefault(c[0], {a: c[k] for k, a in enumerate(atts)}) # --------------------------------------------------------------------- # 4- Retour # --------------------------------------------------------------------- return atts, infos
[docs] def read_series(self, codes=None, first_dt=None, last_dt=None, valid=False, released=False, warning=True): """ Récupération des séries de la base Prevision19. Parameters ---------- codes : list Liste des identifiants des sites/stations first_dt : datetime Première instant de prévision à considérer last_dt : datetime Dernier instant de prévision à considérer valid : bool Seulement les prévisions validées (True) ou toutes les prévisions produites (False) Défaut: False released : bool Seulement les prévisions diffusées (True). Défaut: False warning : bool Afficher les avertissements ? défaut: True Returns ------- atts : list Liste des attributs infos : dict Dictionnaire des séries (nserie: informations) Examples -------- >>> from datetime import datetime as dt >>> from pyspc.data.prevision import Prevision19 >>> f = 'data/io/dbase/PRV_201801.mdb' >>> reader = Prevision19(filename=f) Exemple de prévision validée >>> header, content = reader.read_series( ... codes=['K1321810'], ... first_dt=dt(2018, 1, 3), ... last_dt=dt(2018, 1, 5), ... valid=True ... ) >>> header ['NSerie', 'CodeStation', 'CodeModele', 'DateDerObs', 'Source', 'Diffuse'] >>> content.keys() [5, 11] >>> content[5] {'NSerie': 5, 'CodeStation': 'K1321810', 'CodeModele': 5200, 'DateDerObs': '201801041200', 'Source': 'expresso', 'Diffuse': None} Exemple de prévision brute >>> header, content = reader.read_series( ... codes=['K1321810'], ... first_dt=dt(2018, 1, 3), ... last_dt=dt(2018, 1, 5), ... valid=False ... ) >>> header ['NSerie', 'CodeStation', 'CodeModele', 'DateDerObs'] >>> content.keys() [5, 7, 11, 16, 17, 18, 19, 20, 21, 41, 49, 50] >>> content[11] {'NSerie': 11, 'CodeStation': 'K1321810', 'CodeModele': 2011, 'DateDerObs': '201801041200'} """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- # Gestion des dates self._check_dtime(dtime=first_dt) self._check_dtime(dtime=last_dt) # Autres contrôles self._check_codes(codes=codes) self._check_valid_released(valid=valid, released=released) # --------------------------------------------------------------------- # 1- Requête SQL # --------------------------------------------------------------------- # définition des éléments de la requête SQL atts = P19_ATTS_META[valid] sql_atts = ",".join(['"' + att + '"' for att in atts]) sql_codes = ",".join(["'" + code + "'" for code in codes]) # Séries validées/expertisées table = P19_TABLES_META[valid] # définition de la condition SQL sql_where = f'("CodeStation" IN ({sql_codes}))' fmt = None if self.subtype == '.mdb': fmt = "'%Y%m%d%H%M'" elif self.subtype == '.sqlite': fmt = '%Y%m%d%H%M' if isinstance(first_dt, dt): sql_where += f' AND ("{P19_ATTS_META[valid][3]}" >= {first_dt.strftime(fmt)})' if isinstance(last_dt, dt): sql_where += f' AND ("{P19_ATTS_META[valid][3]}" <= {last_dt.strftime(fmt)})' if released: sql_where += ' AND "Diffuse" IS NOT NULL' # définition de la requête SQL self.sql = P19_SQL_META.format(sql_atts, sql_where, atts[0], table) # --------------------------------------------------------------------- # 2- Appliquer la requête SQL MODELE # --------------------------------------------------------------------- self.connect() content = self.execute() self.close() # --------------------------------------------------------------------- # 3- Traitement du résultat de la requête SQL # --------------------------------------------------------------------- if self.check_sql_return(content=content, warning=warning) is None: return atts, {} # initialisation infos = collections.OrderedDict() for c in content: infos.setdefault(c[0], {a: c[k] for k, a in enumerate(atts)}) # --------------------------------------------------------------------- # 4- Retour # --------------------------------------------------------------------- return atts, infos
[docs] def read_values(self, nseries=None, valid=False, warning=True): """ Récupération des valeurs de la base Prevision19. Parameters ---------- nseries : list Liste des clés primaire des séries valid : bool Prévision expertisée (True). Défaut: False warning : bool Afficher les avertissements ? défaut: True Returns ------- dfs : dict de pnd.DataFrame Dictionnaire de tableaux des données. La clé correspond à la clé primaire de la série Examples -------- >>> from pyspc.data.prevision import Prevision19 >>> f = 'data/io/dbase/PRV_201801.mdb' >>> reader = Prevision19(filename=f) Exemple de prévision validée >>> content = reader.read_values(nseries=[5, 10], valid=True) >>> content.keys() [5, 10] >>> content[5] Val Val10 Val50conv Val90conv DateVal 1 291100 286974 3570 3591 2 303300 288238 3617 3662 3 310000 284618 3644 3718 4 314200 284729 3663 3757 5 316500 284179 3676 3772 6 317300 283146 3683 3790 7 317100 281603 3688 3801 8 316600 280230 3693 3813 9 316400 279473 3701 3823 10 316300 279096 3708 3830 11 316500 279013 3718 3838 12 316800 278710 3728 3845 13 317100 277547 3738 3854 14 317300 276099 3744 3857 15 317300 274709 3751 3860 16 317000 273145 3753 3864 17 316300 271238 3753 3862 18 315100 269355 3751 3861 19 313500 266835 3747 3858 20 311900 264816 3742 3855 21 310300 262527 3732 3852 22 308800 261759 3724 3847 23 307400 259705 3715 3844 24 306200 258442 3708 3841 25 305100 256756 3699 3837 26 304100 255721 3692 3833 27 302900 256539 3681 3825 28 301500 254997 3670 3819 29 299700 253210 3659 3812 30 297300 251872 3642 3803 ... ... ... ... ... 90 175400 143395 3064 3187 91 172100 141029 3030 3174 92 168800 138663 2996 3153 93 165800 136513 2965 3118 94 162800 134323 2934 3083 95 159900 132194 2905 3050 96 157100 130139 2877 3017 97 154500 128231 2851 2988 98 151900 126338 2825 2958 99 149400 124522 2799 2930 100 146900 122707 2760 2901 101 144600 121046 2727 2875 102 142300 119426 2694 2849 103 140100 117876 2663 2824 104 138000 116398 2633 2800 105 135900 114925 2603 2762 106 133900 113522 2574 2730 107 132000 112199 2547 2700 108 130100 110925 2523 2676 109 128300 109718 2500 2652 110 126500 108520 2478 2629 111 124900 107459 2458 2609 112 123500 106540 2440 2591 113 122100 105649 2423 2573 114 120900 104886 2408 2558 115 119900 104244 2395 2545 116 119000 103664 2384 2533 117 118400 103277 2376 2526 118 117800 102890 2369 2518 119 117200 102506 2361 2510 [119 rows x 8 columns] Exemple de prévision brute >>> content = reader.read_values(nseries=[5], valid=False) >>> content.keys() [5] >>> content[5] Val Val10 Val50conv Val90conv DateVal 1 285980 285302 3551 3554 2 289725 283901 3565 3586 3 293533 282428 3579 3619 4 297315 280947 3593 3649 5 300997 279243 3607 3679 6 304510 277259 3620 3709 7 307850 275259 3631 3736 8 311005 272988 3641 3757 9 313843 270341 3650 3769 10 316239 268840 3657 3779 11 317679 266483 3660 3786 12 318116 263263 3658 3792 13 317580 260975 3655 3794 14 316076 257903 3648 3795 15 313783 254209 3637 3793 16 310886 250056 3625 3791 17 307196 245304 3609 3786 18 302824 240054 3591 3780 19 298130 235662 3572 3770 20 293132 231052 3552 3759 21 287668 226098 3530 3744 22 282257 221211 3509 3720 23 277231 216648 3489 3697 24 272664 212466 3471 3677 25 268426 209155 3455 3657 26 264412 206018 3440 3637 27 260553 203003 3426 3619 28 256941 200180 3412 3601 29 253487 197481 3399 3585 30 250104 194837 3386 3569 ... ... ... ... ... 91 133697 97562 2547 2950 92 130935 95570 2510 2919 93 128244 93628 2474 2888 94 125624 91738 2439 2859 95 123071 89896 2405 2830 96 120584 88102 2372 2802 97 118161 86354 2339 2760 98 115800 84650 2307 2722 99 113501 82993 2276 2686 100 111263 81382 2245 2651 101 109084 79814 2215 2616 102 106968 78291 2186 2583 103 104914 76815 2158 2550 104 102919 75383 2130 2518 105 100980 73993 2102 2487 106 99097 72642 2075 2457 107 97266 71329 2049 2428 108 95486 70054 2024 2400 109 93754 68813 1999 2372 110 92083 67617 1975 2345 111 90497 66483 1952 2320 112 88973 65394 1930 2295 113 87501 64342 1908 2272 114 86077 63324 1887 2249 115 84697 62338 1867 2226 116 83359 61381 1848 2205 117 82065 60458 1829 2184 118 80828 59575 1810 2164 119 79650 58734 1793 2145 120 78526 57905 1776 2124 [120 rows x 8 columns] """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- nseries = self._check_series(nseries=nseries) # --------------------------------------------------------------------- # 1- Requête SQL # --------------------------------------------------------------------- # définition des éléments de la requête SQL atts = P19_ATTS_DATA[valid] sql_atts = ",".join(['"' + att + '"' for att in atts]) sql_nseries = ",".join([f"{nserie}" for nserie in nseries]) # Séries validées/expertisées table = P19_TABLES_DATA[valid] # définition de la requête SQL self.sql = P19_SQL_DATA.format( sql_atts, sql_nseries, atts[0], atts[1], table) # --------------------------------------------------------------------- # 2- Appliquer la requête SQL MODELE # --------------------------------------------------------------------- self.connect() content = self.execute() self.close() # --------------------------------------------------------------------- # 3- Traitement du résultat de la requête SQL # --------------------------------------------------------------------- if self.check_sql_return(content=content, warning=warning) is None: return None # Créer le pnd.DataFrame df = {c: [x[k] for x in content] for k, c in enumerate(P19_ATTS_DATA[valid])} df = pnd.DataFrame(df) # Définir l'index par la colonnes DateVal df = df.set_index(keys=P19_ATTS_DATA[valid][1], drop=True) # Ajout de NSerie dans les colonnes df = df.pivot(columns=P19_ATTS_DATA[valid][0]) # colonne (X, NSerie) -> (NSerie, X) df.columns = df.columns.swaplevel() # dictionnaire de dataframe {nserie: df} nseries = set(nseries).intersection({c[0] for c in df.columns}) dfs = {n: df.xs(n, axis=1) for n in sorted(nseries)} # --------------------------------------------------------------------- # 4- Retour # --------------------------------------------------------------------- return dfs
[docs] def insert_fcst(self, fcst=None, valid=False): """ Insérer les informations sur les prévisions Parameters ---------- fcst : dict Dictionnaire de DataFrame {id: df} valid : bool Seulement les prévisions validées (True) ou toutes les prévisions produites (False) Défaut: False Returns ------- rows : dict Dictionnaire de correspondance entre id et les clés primaires des séries et des valeurs See Also -------- Prevision19.get_series Prevision19.get_values Prevision19.insert_series Prevision19.insert_values """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- _exception.check_dict(fcst) # --------------------------------------------------------------------- # 1- Recherche de la dernière série # --------------------------------------------------------------------- table = P19_TABLES_META[valid] self.connect() count = self._dbase_cursor.execute(f'SELECT COUNT(*) FROM {table}') init_count = count.fetchall()[0][0] self.close() # --------------------------------------------------------------------- # 1- Boucle sur les prévisions à insérer # --------------------------------------------------------------------- rows = collections.OrderedDict() count = -1 for k, df in fcst.items(): _exception.check_dataframe(df) count += 1 n = init_count + count + 1 # ----------------------------------------------------------------- # 1.1- Insertion de la série # ----------------------------------------------------------------- # Station : unique station = list({c[0] for c in df.columns}) # Runtime : unique runtime = list({c[1] for c in df.columns}) # Scénario : unique scen = list({c[2] for c in df.columns}) if len(station) != 1 or len(runtime) != 1 or len(scen) != 1: continue scen = scen[0] runtime = runtime[0] station = station[0] # Si prévision expertisée if valid: series = { n: { 'NSerie': n, 'CodeStation': station, 'CodeModele': scen, 'DateDerObs': runtime.strftime('%Y%m%d%H%M'), 'Source': list({c[3] for c in df.columns})[0], 'Diffuse': list({c[4] for c in df.columns})[0] } } else: series = { n: { 'NSerie': n, 'CodeStation': station, 'CodeModele': scen, 'DateDerObs': runtime.strftime('%Y%m%d%H%M'), } } sr = self.insert_series(series=series, valid=valid) if n not in sr and sr[n] != n: continue rows.setdefault(k, {'series': sr[n]}) # ----------------------------------------------------------------- # 1.2- Insertion des valeurs # ----------------------------------------------------------------- df.columns = [c[-1] for c in df.columns] df.index = [int((i - runtime) / td(hours=1)) for i in df.index] vr = self.insert_values(values={n: df}, valid=valid) rows[k]['values'] = list(vr.values()) # --------------------------------------------------------------------- # 2- Retour # --------------------------------------------------------------------- return rows
[docs] def insert_models(self, models=None): """ Insérer les informations sur les modèles de prévision. Parameters ---------- models : dict Dictionnaire des modèles {"id_modeles": {"CodeModele": 'x', "Nom": 'x', "CodePOM": 'x'}} Returns ------- rows : dict Dictionnaire de correspondance entre les clés de <models> et les clés primaires """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- _exception.check_dict(models) # --------------------------------------------------------------------- # 1- Requête SQL # --------------------------------------------------------------------- # définition des éléments de la requête SQL atts = P19_ATTS_MODEL[self.subtype][-3:] sql_atts = ",".join(['"' + att + '"' for att in atts]) table = P19_TABLES_MODEL[self.subtype] # --------------------------------------------------------------------- # 2- Connexion base # --------------------------------------------------------------------- self.connect() # --------------------------------------------------------------------- # 3- Appliquer les requêtes SQL INSERT MODELE # --------------------------------------------------------------------- if self.subtype == '.mdb': count = self._dbase_cursor.execute(f'SELECT COUNT(*) FROM {table}') init_count = count.fetchall()[0][0] # initialisation rows = collections.OrderedDict() row_count = 0 for k, m in models.items(): row_count += 1 values = [m.get(a, None) for a in atts] sql_values = ",".join(["'" + v + "'" if isinstance(v, str) else f'{v}' for v in values]) self.sql = P19_SQL_INSERTMODEL.format(table, sql_atts, sql_values) # exécution de la requête SQL self.execute() self.commit() # valider l'enregistrement if self.subtype == '.sqlite': rows.setdefault(k, self.lastrowid()) elif self.subtype == '.mdb': rows.setdefault(k, init_count + row_count) # --------------------------------------------------------------------- # 4- Contrôles d'insertion # --------------------------------------------------------------------- if self.subtype == '.mdb': count = self._dbase_cursor.execute( f'SELECT COUNT(*) FROM {table}') final_count = count.fetchall()[0][0] if (final_count - init_count) != len(models): _exception.Warning( __name__, "Incohérence entre le nb de modèles à insérer et " f"le nb de modèles au final, après commit\n{self.sql}") if len(rows) != len(models): _exception.Warning( __name__, "Incohérence entre le nb de modèles à insérer et " "le nb de modèles au final, après commit\n{self.sql}") # --------------------------------------------------------------------- # 5- Fermeture # --------------------------------------------------------------------- self.close() # --------------------------------------------------------------------- # 6- Retour # --------------------------------------------------------------------- return rows
[docs] def insert_series(self, series=None, valid=False): """ Insérer les informations sur les séries de prévision. Parameters ---------- series : dict Dictionnaire des séries {NSerie: {attribut: valeur}} valid : bool Seulement les prévisions validées (True) ou toutes les prévisions produites (False) Défaut: False Returns ------- rows : dict Dictionnaire de correspondance entre NSerie et la clé primaire de la série See Also -------- Prevision19.get_series """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- _exception.check_dict(series) # --------------------------------------------------------------------- # 1- Requête SQL # --------------------------------------------------------------------- atts = P19_ATTS_META[valid] sql_atts = ",".join(['"' + att + '"' for att in atts]) table = P19_TABLES_META[valid] # --------------------------------------------------------------------- # 2- Connexion base # --------------------------------------------------------------------- self.connect() # --------------------------------------------------------------------- # 3- Appliquer les requêtes SQL INSERT SERIES # --------------------------------------------------------------------- # initialisation rows = collections.OrderedDict() # Boucle sur les séries à insérer for k, s in series.items(): values = [s.get(a, None) for a in atts] sql_values = ",".join(["'" + v + "'" if isinstance(v, str) else f'{v}' for v in values]) self.sql = P19_SQL_INSERTMETA.format(table, sql_atts, sql_values) self.sql = self.sql.replace('None', 'NULL') # exécution de la requête SQL self.execute() self.commit() # valider l'enregistrement if self.subtype == '.sqlite': rows.setdefault(k, self.lastrowid()) elif self.subtype == '.mdb': nserie = self._dbase_cursor.execute( f'SELECT Max({table}.NSerie) FROM {table}') nserie = nserie.fetchall()[0][0] rows.setdefault(k, nserie) # --------------------------------------------------------------------- # 4- Contrôles d'insertion # --------------------------------------------------------------------- if len(rows) != len(series): _exception.Warning( __name__, "Incohérence entre le nb de modèles à insérer et " f"le nb de modèles au final, après commit\n{self.sql}") # --------------------------------------------------------------------- # 5- Fermeture # --------------------------------------------------------------------- self.close() # --------------------------------------------------------------------- # 6- Retour # --------------------------------------------------------------------- return rows
[docs] def insert_values(self, values=None, valid=False): """ Insérer les informations sur les valeurs de prévision. Parameters ---------- values : dict Dictionnaire des tableaux de valeurs {NSerie: DataFrame} valid : bool Seulement les prévisions validées (True) ou toutes les prévisions produites (False) Défaut: False Returns ------- rows : dict Dictionnaire de correspondance entre NSerie et les clés primaires des valeurs See Also -------- Prevision19.get_values """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- _exception.check_dict(values) # --------------------------------------------------------------------- # 1- Requête SQL # --------------------------------------------------------------------- atts = P19_ATTS_DATA[valid] sql_atts = ",".join(['"' + att + '"' for att in atts]) table = P19_TABLES_DATA[valid] # --------------------------------------------------------------------- # 2- Connexion base # --------------------------------------------------------------------- self.connect() # --------------------------------------------------------------------- # 3- Appliquer les requêtes SQL INSERT VALUES # --------------------------------------------------------------------- # initialisation rows = collections.OrderedDict() # Boucle sur les tableaux de valeurs (DataFrame) à insérer for n in sorted(values.keys()): df = values[n] _exception.check_dataframe(df) for i in sorted(df.index): v = {atts[0]: n, atts[1]: i} v.update(df.loc[i].to_dict()) v = [v.get(a, None) for a in atts] sql_values = ",".join( ["'" + x + "'" if isinstance(x, str) else f'{x}' for x in v] ) self.sql = P19_SQL_INSERTDATA.format( table, sql_atts, sql_values) self.sql = self.sql.replace('None', 'NULL') self.sql = self.sql.replace('nan', 'NULL') # exécution de la requête SQL self.execute() self.commit() # valider l'enregistrement if self.subtype == '.sqlite': rows.setdefault((n, i), self.lastrowid()) elif self.subtype == '.mdb': count = self._dbase_cursor.execute( f'SELECT COUNT(*) FROM {table}') count = count.fetchall()[0][0] rows.setdefault((n, i), count) # --------------------------------------------------------------------- # 5- Fermeture # --------------------------------------------------------------------- self.close() # --------------------------------------------------------------------- # 6- Retour # --------------------------------------------------------------------- return rows
[docs] def unique_series(self, nseries=None, valid=False, warning=True): """ Unicité des séries. Pour un triplet (code, model/scen, dtderobs), conserve la série ayant le statut le plus élevé: - diffusée > non-diffusée - expertisée > validée > pré-validée Parameters ---------- nseries : list Liste des clés primaire des séries valid : bool Prévision expertisée (True). Défaut: False warning : bool Afficher les avertissements ? défaut: True Returns ------- useries : list Liste des clés primaire des séries UNIQUES """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- nseries = self._check_series(nseries=nseries) # --------------------------------------------------------------------- # 1- Requête SQL # --------------------------------------------------------------------- # définition des éléments de la requête SQL atts = P19_ATTS_META[valid] sql_atts = ",".join(['"' + att + '"' for att in atts]) sql_nseries = ",".join([f"{nserie}" for nserie in nseries]) table = P19_TABLES_META[valid] # définition de la condition SQL self.sql = P19_SQL_UNIQUEMETA.format( sql_atts, sql_nseries, P19_ATTS_META[valid][0], table) # --------------------------------------------------------------------- # 2- Appliquer la requête SQL MODELE # --------------------------------------------------------------------- self.connect() content = self.execute() self.close() # --------------------------------------------------------------------- # 3- Traitement du résultat de la requête SQL # --------------------------------------------------------------------- if self.check_sql_return(content=content, warning=warning) is None: return [] # initialisation infos = collections.OrderedDict() tmpinfos = collections.OrderedDict() useries = [] if valid: for c in content: # Choix des éléments uniques key = (c[1], c[3]) diff = bool(c[5] is not None) status = P19_STATUS_META.get(c[4], 0) infos.setdefault(key, {}) infos[key][c[0]] = (diff, status) tmpinfos.setdefault(key, {}) tmpinfos[key][c[0]] = c for key in infos: target = sorted(infos[key].items(), key=lambda x: x[1]) useries.append(target[-1][0]) if warning: for t in target[:-1]: x = tmpinfos[key][t[0]] _exception.Warning( __name__, f"Série validée ignorée : {x[0]} => code={x[1]}, " f"model={x[2]}, dtderobs={x[3]}, src={x[4]}, " f"diff={x[5]}") else: for c in content: # Choix des éléments uniques key = (c[1], c[2], c[3]) if warning and key in infos: _exception.Warning( __name__, f"Série ignorée : {c[0]} => code={c[1]}, model={c[2]}" f", dtderobs={c[3]}") infos.setdefault(key, c[0]) useries = list(infos.values()) # --------------------------------------------------------------------- # 4- Retour # --------------------------------------------------------------------- return sorted(useries)