#!/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)