


# Objectif du module artip
Fournir une classe permettant de produire une représentation d'intervalles temporels utilisable dans des modèles Power BI 
Le module sera utilisé dans des pipelines ETL Python pour construire des identifiants de période scalaires, par opposition au doublet (date de début, date de fin) qui serait normalement utilisé.
Il doit être possible d'utiliser plusieurs instances en même temps dans le même processus d'ETL: il ne doit y avoir aucune variable globale cachée pour assurer le fonctionnement, toutes les informations nécessaires doivent être encapsulées.

L’objectif est d’assurer :
- une représentation scalaire univoque d’un intervalle temporel qui peut être inséré dans une colonne de table Power BI
- la génération d'une structure de calendrier permettant de filtrer cette colonne de table dans Power BI
- l'optimisation de l'encombrement mémoire de la représentation d'une telle période
- l'optimisation de la complexité des calculs de filtrage de ces périodes dans Power BI.
- dit clairement la représentation de l'identifiant de période doit tenir dans un entier 64 bits pour permettre l'encodage VALUE dans le moteur Vertipaq de Power BI


# Conception

## Classe principale : `Artip`

Ce module exporte une classe nommée "artip" qui permet de gérer tous les besoins liés à l'analyse de périodes temporelles dans Power BI:
- l'instanciation de la classe en passant en argument les bornes de l'intervalle de temps le plus grand à représenter par l'instance (date minimale et date maximale) et la 
  granularité souhaitée (année, mois, jour, heure, minute, seconde)
- une méthode permettant la génération d'un identifiant de période définie par une date de début et une date de fin à la granularité définie lors de l'instanciation, période incluse strictement dans les bornes communiquées lors de la création de l'instance, 
- une méthode retournant un tuple de date ou datetime correspondant à un identifiant de période qui a été généré par artip
- une méthode permettant génération d'un LazyFrame Polars représentant un calendrier périodique qui sera enregistrable dans un fichier pour être utilisée dans Power BI
- une méthode permettant de fournir une représentation textuelle humainement lisible de l'identifiant de période passé en argument


## Entité utilitaire : l'identifiant de période 'PeriodId'

### Principes de construction d'un identifiant de période

#### Identifiant de temps de début de période (start_id) et identifiant de fin de période (end_id) 

Les paramètres start et end de la méthode make_period_id ne sont pas utilisés tels quels: on leur retranche systématiquement la valeur min_time et on ajoute 1 pour obtenir un identifiant de temps. Cet identifiant de temps calculé est donc dans l'intervalle [1, max_time - min_time + 1]. Et ces deux valeurs sont stockées en interne, nous les appellerons min_id et max_id.

On réserve la valeur zéro à un identifiant de temps correspondant à un temps avant min_time et  et la valeur 9...9 (autant de chiffres 9 que le logarithme naturel de max_time - min_time + 1, valeur interne que nous appellerons time_id_size) à un identifiant de temps correspondant à un temps après max_time. 

Ces valeurs réservées seront utilisées si start ou end sort de [min_time, max_time] pour le calcul des identifiants de début (start_id) et de fin (end_id) de période.

Je ne pense pas que start_id et end_id nécessitent d'être stockées quelque part. Ces noms servent simplement à définir le calcul de l'identifiant de période.

#### Longueur de période

La valeur de la longueur de la période est toujours définie par le calcul end_id - start_id. Si cette valeur est supérieure à period_max_size, la fonction make_period_id doit générer une erreur

Par contre la taille en nombre de chiffres de cette longueur est connue: soit elle est fournie dans l'argument period_max_size soit elle est calculée comme le logarithme naturel de max_id - min_id. On garde cette valeur en interne de la classe, appelons-là period_size.


#### Contruction de l'identifiant de période

Un identifiant de période est un nombre entier positif composé de deux suites de chiffres: <identifiant de début de la période><longueur de la période>
- Le nombre de chiffres de l'identifiant de début de la période est dépendant des arguments min_time et max_time et reste identique quel que soit l'identifiant généré par une même instance de Artip
- Le nombre de chiffres de la deuxième suite est calculé comme expliqué en 3.2
- Si start_id et end_id sont tous les deux égaux à zéro ou sont tous les deux égaux à 9...9, cela signifie que la période demandée n'est pas représentable. L'identifiant retourné vaut alors 0
- et donc pour calculer l'identifiant de période, on réalise le calcul start_id * period_size + end_id - start_id

## La table du calendrier périodique

### Définition de la table du calendrier périodique

Cette table représente un calendrier périodique permettant de filtrer les PeriodId. C'est une structure optimisée pour ne traiter que les périodes dont on a demandé la génération d'un identifiant à la classe artip.

Chaque ligne de cette table contient trois colonnes:
    - une date selon la granularité définie lors de la création de l'instance artip (un jour pour une granularité quotidienne, une minute pour une granularité à la minute, etc). Toutes les dates possibles selon les bornes définies lors de l'instanciation n'ont pas à être présentes dans cete table. Seules les dates contenues dans les périodes dont a demandé la génération d'un identifiant sont présentes.
    - un PeriodId dont la nature est définie ci-dessous. Si la date est présente dans plusieurs périodes connues, il y a autant de lignes contenant cette date que de périodes associées
    - une étiquette qui indique la relation de la date avec la période:
        - si la date est incluse dans la période corespondant à l'identifiant, l'étiquette a la valeur "in"
        - si la date est la première date de la période temporelle correspondant à l'identifiant, l'étiquette a la valeur "starts"
        - si la date est la dernière date de la période temporelle correspondant à l'identifiant, l'étiquette a la valeur "ends"


### Principe de construction de la table du calendrier périodique

Les PeriodId engendrés ont tous été mémorisés lors de chaque construction. Pour construire le calendrier périodique, il suffit de parcourir cette liste de PeriodId, et pour chaque période:
    - créer une ligne avec la date de début de période, l'étiquette "starts", et le PeriodId en question
    - créer une ligne avec la date de fin de période, l'étiquette "ends", et le PeriodId en question
    - pour chaque date comprise entre le début et la fin de période incluses, créer une ligne avec la date en question l'étiquette "in" et le PeriodId en question





# Implémentation

## Classe principale : `Artip`


La définition ci-dessous montre l'API publique de la classe. Bien entendu il faudra ajouter tous les champs internes qui vont stocker les informations nécessaires (listes d'identifiants de périodes, de dates, ce genre de choses) et qui sont dépendants de l'implémentation.

```python
class Artip:
    min_time:           date | datetime
    max_time:           date | datetime
    granularity:        str  # 'year', 'month',  'day', "hour", "minute", "second"
    period_max_size:    int  # nombre max de 'granularity' de la période. Si None une période peut être aussi grande que [min_time, max_time]


    def __init__(self, min_time: date | datetime, max_time: date | datetime, granularity: str = 'day', period_max_size: None): ...
    def make_period_id(self, start: date | datetime, end: date | datetime) -> PeriodId: ...
    def make_calendar_table(self) -> pl.LazyFrame:
    def get_period_range(self, period_id: PeriodId) -> tuple[datetime, datetime]:
```
Les valeurs de début et de fin passées au constructeur doivent être arrondies à la granularité. Par exemple si l'utilisateur passe un datetime avec une valeur hh:mm:ss différente de zéro, ces valeurs sont mises à zéro. De la même manière pour les mois, les années, les minutes, etc.

## Dataclass PeriodId

PeriodId est implémenté avec une dataclass car ce doit être une entité légère. Ce dataclass ne contient qu'un seul champ: l'entier identifiant une période. Lorsqu'on l'imprime, il faut afficher l'entier. Le test d'égalité entre deux instances doit être possible, mais aucune autre opération que l'affichage et le test d'égalité ne doit être permise.

Il ne doit pas être possible pour un utilisateur d'invoquer le constructeur de PeriodID, ce sont des objets uniquement produits par Artip.

## Méthode make_period_id

La méthode make_period_id implémente le calcul de l'identifiant de période tel que défini dans la section de conception. Elle s'assure de générer une erreur si la période n'est pas représentable pour cause de dépassement de la valeur period_max_size; S'il n'y a pas d'erreur cet identifiant est retourné.

Les valeurs de début et de fin passées à la fonction doivent être arrondies à la granularité. Par exemple si l'utilisateur passe un datetime avec une valeur hh:mm:ss différente de zéro, ces valeurs sont mises à zéro. De la même manière pour les mois, les années, les minutes, etc.

Elle stocke également dans l'instance les informations pertinentes qui permettront de construire la table retournée par la fonction make_calendar_table. Dans la mesure où make_calendar_table retourne un DataFrame polars, il peut être intéressant de stocker ces informations pertinentes dans un DataFrame Polars. Cependant ces informations risquent d'être en très grand nombre: le mode de stockage devra être très compact et le DataFrame n'est peut-être pas le format le plus approprié. Je te laisse le choix de la structure la mieux adaptée.

## Méthode make_calendar_table

L'implémentation respecte strictement le mode opératoire défini dans la section de conception. La méthode doit retourner un lazy frame Polars.

## Méthode get_period_range

L'implémentation respecte strictement le mode opératoire défini dans la section de conception


