datateam-moss 0.0.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Gemeente Amsterdam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.1
2
+ Name: datateam_moss
3
+ Version: 0.0.10
4
+ Summary: Functions needed for datateam MOSS (municipality of Amsterdam)
5
+ Author-email: Robbin Breeuwer <r.j.breeuwer@amsterdam.nl>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+
10
+ # Introduction
11
+ Functions for datateam MOSS+
12
+
13
+ DISCLAIMER: Repo is in PoC phase \n
14
+ DISCLAIMER: The functions can run on Databricks using a Personal Compute Cluster
@@ -0,0 +1,5 @@
1
+ # Introduction
2
+ Functions for datateam MOSS+
3
+
4
+ DISCLAIMER: Repo is in PoC phase \n
5
+ DISCLAIMER: The functions can run on Databricks using a Personal Compute Cluster
@@ -0,0 +1,13 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "datateam_moss"
7
+ version = "0.0.10"
8
+ authors = [
9
+ { name="Robbin Breeuwer", email="r.j.breeuwer@amsterdam.nl" },
10
+ ]
11
+ description = "Functions needed for datateam MOSS (municipality of Amsterdam)"
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,275 @@
1
+ # Databricks notebook source
2
+ # Inladen packages
3
+ import sys
4
+ from pyspark.sql.functions import *
5
+ from pyspark.sql.types import *
6
+ from datetime import datetime
7
+ from pyspark.sql import SparkSession, Row, DataFrame
8
+ from pyspark.sql.window import Window
9
+ import time
10
+
11
+ def voeg_aangepaste_surrogaatsleutel_toe(df: DataFrame, max_bestaande_surrogaatsleutel=None):
12
+ """
13
+ Voeg een aangepaste surrogaatsleutelkolom toe aan een PySpark DataFrame.
14
+
15
+ Args:
16
+ df (DataFrame): Het oorspronkelijke DataFrame waarop de surrogaatsleutel moet worden toegevoegd.
17
+ max_bestaande_surrogaatsleutel (int, optioneel): De maximale bestaande surrogaatsleutel in het DataFrame.
18
+ Als niet opgegeven, wordt de surrogaatsleutel gestart vanaf 1.
19
+
20
+ Returns:
21
+ DataFrame: Een DataFrame met de aangepaste surrogaatsleutelkolom toegevoegd.
22
+
23
+ Voorbeeld:
24
+ # Gebruik van de functie om een surrogaatsleutel toe te voegen aan een DataFrame
25
+ df = voeg_aangepaste_surrogaatsleutel_toe(df)
26
+ Laatste update: 23-11-2023
27
+ """
28
+ # Creëer een window-specificatie om te sorteren op een constante waarde (0)
29
+ window_spec = Window().orderBy(lit(0))
30
+
31
+ # Gebruik row_number() om een unieke waarde te genereren voor elke rij, beginnend vanaf max_bestaande_surrogaatsleutel + 1
32
+ df = df.withColumn("surrogaat_sleutel", row_number().over(window_spec) + (max_bestaande_surrogaatsleutel or 0))
33
+ return df
34
+
35
+ def controleer_unieke_waarden(df: DataFrame, rij_id_var: str):
36
+ """
37
+ Controleert of alle waarden in een specifieke kolom uniek zijn in het gegeven DataFrame.
38
+
39
+ Parameters:
40
+ - df: DataFrame: Het DataFrame waarin de controle wordt uitgevoerd.
41
+ - rij_id_var: str: De naam van de kolom waarvan de unieke waarden worden gecontroleerd.
42
+
43
+ Returns:
44
+ - None
45
+
46
+ Als het aantal unieke waarden in de kolom niet gelijk is aan het totale aantal rijen,
47
+ wordt er een melding geprint dat niet alle waarden in de kolom uniek zijn.
48
+
49
+ Laatste update: 23-11-2023
50
+ """
51
+ # Tel het aantal unieke waarden in de kolom
52
+ aantal_unieke = df.select(col(rij_id_var)).distinct().count()
53
+
54
+ # Krijg het totale aantal rijen in het DataFrame
55
+ totaal_aantal = df.count()
56
+
57
+ # Controleer of het aantal unieke waarden gelijk is aan het totale aantal rijen
58
+ if aantal_unieke != totaal_aantal:
59
+ foutmelding = f"Niet alle waarden in de kolom '{rij_id_var}' zijn uniek."
60
+ raise ValueError(foutmelding)
61
+
62
+ def tijdzone_amsterdam():
63
+ """
64
+ Pakt de huidige tijd en convert het naar het volgende tijdsformat yyyy-MM-dd HH:mm:ss en Amsterdamse tijdzone
65
+ Laatste update: 04-12-2023
66
+ """
67
+ # Bepaal de datum van de wijziging
68
+ huidige_datum = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
69
+
70
+ # Zet de string om naar een tijdstempel
71
+ timestamp_expr = to_timestamp(lit(huidige_datum), "yyyy-MM-dd HH:mm:ss")
72
+
73
+ # Stel de tijdzone in op "Europe/Amsterdam"
74
+ huidige_datum_tz = from_utc_timestamp(timestamp_expr, "Europe/Amsterdam")
75
+
76
+ return huidige_datum_tz
77
+
78
+ def initialiseer_historisering(df: DataFrame, schema_catalog: str, rij_id_var: str, naam_nieuw_df: str):
79
+ """
80
+ Bereid gegevens voor op historisering door historische data te markeren met relevante metadata.
81
+
82
+ Parameters:
83
+ - df (str): De naam van de temporary view die het huidige DataFrame vertegenwoordigt.
84
+ - schema_catalog (str): Naam van het schema waar de tabel instaat of opgeslagen moet worden
85
+ - rij_id_var (str): Naam van de kolom die wordt gebruikt om unieke rijen te identificeren.
86
+ - naam_nieuw_df (str): Naam van dataframe in de catalogus
87
+
88
+ Laatste update: 04-12-2023
89
+ """
90
+ # Kijk of de opgegeven identifier uniek is
91
+ controleer_unieke_waarden(df = df, rij_id_var = rij_id_var)
92
+
93
+ # Controleer of het nieuwe DataFrame de vereiste kolommen bevat: begindatum_record, einddatum_record, record_actief, surrogaat_sleutel
94
+ df.show()
95
+ time.sleep(5)
96
+ antwoord = input("Heb je de tabel bekeken en wil je deze voor de eerste keer opslaan met de vereiste kolommen? Type dan 'ja'")
97
+
98
+ if antwoord.lower() == "ja":
99
+ vereiste_kolommen = ['geldig_van', 'geldig_tot', 'surrogaat_sleutel', 'record_actief', "actie"] #, "laad_datum", "gewijzigd_op"]
100
+ ontbrekende_kolommen = [kolom for kolom in vereiste_kolommen if kolom not in df.columns]
101
+
102
+ if ontbrekende_kolommen:
103
+ foutmelding = f"Ontbrekende kolommen: {', '.join(ontbrekende_kolommen)}. Deze zullen aan het DataFrame worden toegevoegd..."
104
+ print(foutmelding)
105
+
106
+ # Roep de functie tijdzone_amsterdam aan om de correcte tijdsindeling te krijgen
107
+ huidige_datum_tz = tijdzone_amsterdam()
108
+
109
+ # Werk de einddatum, record_actief en begindatum kolommen bij in het huidige DataFrame
110
+ temp_df = (df
111
+ .withColumn("geldig_van", huidige_datum_tz)
112
+ .withColumn("geldig_tot", to_timestamp(lit("9999-12-31 23:59:59"), "yyyy-MM-dd HH:mm:ss"))
113
+ .withColumn("record_actief", lit(True))
114
+ .withColumn("actie", lit("insert"))
115
+ )
116
+
117
+ # Voeg nieuwe surrogaatsleutels toe op basis van de maximale sleutel in de DWH
118
+ output = voeg_aangepaste_surrogaatsleutel_toe(temp_df)
119
+ output.display()
120
+
121
+ # Sla de gegevens op in delta-formaat in het opgegeven schema
122
+ print(f"De tabel wordt nu opgeslagen in {schema_catalog} onder de naam: {naam_nieuw_df}")
123
+ output.write.saveAsTable(f'{schema_catalog}.{naam_nieuw_df}', mode='overwrite')
124
+
125
+ def bepaal_veranderde_records(huidig_df: DataFrame, nieuw_df: DataFrame, rij_id_var: str):
126
+ """
127
+ Identificeert en retourneert unieke rij-identificatoren van gewijzigde records tussen twee DataFrames op basis van een rij-id en verschillende kolommen.
128
+
129
+ Parameters:
130
+ huidig_df (DataFrame): Het huidige DataFrame met de bestaande gegevens.
131
+ nieuw_df (DataFrame): Het nieuwe DataFrame met bijgewerkte gegevens.
132
+ rij_id_var (str): De naam van de kolom die als rij-id wordt gebruikt om records te identificeren.
133
+
134
+ Returns:
135
+ set: Een verzameling met unieke rij-identificatoren voor de gewijzigde records tussen de huidige en nieuwe DataFrames.
136
+
137
+ Voorbeeld:
138
+ huidig_df = spark.createDataFrame([(1, "Alice"), (2, "Bob")], ["account_id", "name"])
139
+ nieuw_df = spark.createDataFrame([(2, "Bobby"), (3, "Charlie")], ["account_id", "name"])
140
+ unieke_rij_id_veranderd = bepaal_veranderde_records(huidig_df, nieuw_df, "account_id")
141
+ print(unieke_rij_id_veranderd) # Output: {2, 3}
142
+ Laatste update: 23-11-2023
143
+ """
144
+
145
+ # Pas huidig DataFrame aan voor de vergelijking
146
+ uitzondering_kolom = ["geldig_van", "geldig_tot", "surrogaat_sleutel", "record_actief", "actie"]
147
+ huidig_df_temp = huidig_df.drop(*uitzondering_kolom)
148
+
149
+ # Controleer op basis van de nieuwe gegevens welke rijen in de huidige dataset zijn gewijzigd
150
+ veranderde_df = nieuw_df.subtract(huidig_df_temp)
151
+
152
+ # Haal de unieke rij-identificatoren op
153
+ unieke_rij_id = veranderde_df.select(rij_id_var).distinct()
154
+ unieke_rij_id_lijst = [rij[0] for rij in unieke_rij_id.collect()]
155
+ unieke_rij_id_lijst = set(unieke_rij_id_lijst)
156
+
157
+ return unieke_rij_id_lijst
158
+
159
+ def updaten_historisering_dwh(nieuw_df: DataFrame, rij_id_var: str, schema_catalog: str, naam_nieuw_df: str, huidig_dwh: str = None):
160
+ """
161
+ Voegt historische gegevens toe aan een PySpark DataFrame om wijzigingen bij te houden.
162
+
163
+ Args:
164
+ nieuw_df (DataFrame): DataFrame met gewijzigde gegevens.
165
+ rij_id_var (str): Naam van de kolom die wordt gebruikt om unieke rijen te identificeren.
166
+ naam_nieuw_df (str): Naam van de tabel in het opgegeven schema.
167
+ huidig_dwh (str, optioneel): Naam van het huidige DWH DataFrame. Indien niet opgegeven, wordt
168
+ de huidige tabelnaam van 'nieuw_df' gebruikt (komt overeen met het DWH).
169
+ Laatste update: 4-12-2023
170
+ """
171
+ # Lees de huidige versie van de opgegeven tabel in
172
+ if huidig_dwh is None:
173
+ huidig_dwh_tabel = spark.read.table(f"{schema_catalog}.{naam_nieuw_df}").cache()
174
+ else:
175
+ huidig_dwh_tabel = spark.read.table(f"{schema_catalog}.{huidig_dwh}").cache()
176
+
177
+ # Roep de functie tijdzone_amsterdam aan om de correcte tijdsindeling te krijgen
178
+ huidige_datum_tz = tijdzone_amsterdam()
179
+
180
+ # Bepaal de juiste volgorde van de kolommen
181
+ dwh_kolom_volgorde = huidig_dwh_tabel.columns
182
+
183
+ # Roep de functie bepaal_veranderde_records aan om de records te krijgen van rijen die veranderd zijn
184
+ unieke_lijst_id = bepaal_veranderde_records(huidig_dwh_tabel, nieuw_df, rij_id_var)
185
+
186
+ # Converteer de set van unieke id's naar een dataframe die je kunt broadcasten over verschillende workers
187
+ id_df = spark.createDataFrame([(id,) for id in unieke_lijst_id], ["account_id"])
188
+
189
+ # Dataframes opsplitsen in delen die wel aangepast moeten worden en ander deel niet.
190
+ # Dit doen wij voor de efficiëntie en optimalisatie van de functie
191
+ # Aangezien we met de id's filteren kan het contra-intuïtief zijn om bij geen_aanpassing een left_anti join te gebruiken
192
+ huidig_dwh_tabel_geen_aanpassing = huidig_dwh_tabel.join(broadcast(id_df), "account_id", "left_anti")
193
+ huidig_dwh_tabel_wel_aanpassing = huidig_dwh_tabel.join(broadcast(id_df), "account_id")
194
+
195
+ # Pas de records in het DWH aan die veranderd zijn
196
+ temp_huidig_dwh_tabel = (huidig_dwh_tabel_wel_aanpassing
197
+ .withColumn("nieuwe_geldig_tot", huidige_datum_tz)
198
+ .withColumn("record_actief_update", lit(False))
199
+ .drop("geldig_tot").withColumnRenamed("nieuwe_geldig_tot", "geldig_tot")
200
+ .drop("record_actief").withColumnRenamed("record_actief_update", "record_actief")
201
+ .select(*dwh_kolom_volgorde)
202
+ )
203
+
204
+ # Voeg de 2 delen van het DWH weer samen
205
+ rejoined_huidig_dwh_tabel = huidig_dwh_tabel_geen_aanpassing.union(temp_huidig_dwh_tabel)
206
+
207
+ # Dataframes filteren op de id's die aangepast zijn en met de nieuwe gegevens weer toegevoegd worden aan de DWH
208
+ temp_nieuw_df_wel_aanpassing = nieuw_df.join(broadcast(id_df), "account_id")
209
+ temp_verandert_df = (temp_nieuw_df_wel_aanpassing
210
+ .withColumn("surrogaat_sleutel", lit(None))
211
+ .withColumn("record_actief", lit(True))
212
+ .withColumn("geldig_van", huidige_datum_tz)
213
+ .withColumn("geldig_tot", to_timestamp(lit("9999-12-31 23:59:59"), "yyyy-MM-dd HH:mm:ss"))
214
+ .withColumn("actie", lit("insert"))
215
+ )
216
+
217
+ # Voeg nieuwe surrogaatsleutels toe
218
+ max_surrogaat_sleutel = rejoined_huidig_dwh_tabel.select(max(rejoined_huidig_dwh_tabel["surrogaat_sleutel"])).first()[0]
219
+ temp_verandert_df = voeg_aangepaste_surrogaatsleutel_toe(temp_verandert_df, max_surrogaat_sleutel)
220
+
221
+ # Sorteer beide dataframe op kolommen, zodat ze samengevoegd kunnen worden
222
+ gesorteerde_kolommen = sorted(rejoined_huidig_dwh_tabel.columns, key=lambda kol: kol.lower())
223
+ df1 = rejoined_huidig_dwh_tabel.select(*gesorteerde_kolommen)
224
+ df2 = temp_verandert_df.select(*gesorteerde_kolommen)
225
+ output = df1.union(df2)
226
+ output = output.select(*dwh_kolom_volgorde)
227
+
228
+ # Sla de gegevens op in delta-formaat in de zilveren laag
229
+ print(f"De tabel wordt nu opgeslagen in {schema_catalog} onder de naam: {naam_nieuw_df}")
230
+ output.write.saveAsTable(f'{schema_catalog}.{naam_nieuw_df}', mode='overwrite')
231
+ return
232
+
233
+ def toepassen_historisering(nieuw_df, schema_catalog: str, rij_id_var: str, naam_nieuw_df=None, huidig_dwh: str = None):
234
+ """
235
+ Deze regisseurfunctie roept op basis van bepaalde criteria andere functies aan en heeft hiermee de controle over de uitvoering van het historiseringsproces.
236
+
237
+ Deze functie gaat ervan uit dat je een string opgeeft die verwijst naar een SQL temporary view of Python DataFrame. Wanneer jij bij nieuw_df een Python DataFrame opgeeft, moet je verplicht naam_nieuw_df invullen. Aangezien Python geen objectnaam kan afleiden van objecten.
238
+ Args:
239
+ nieuw_df (str of object): Naam van het nieuwe DataFrame dat verwijst naar een temporary view met gewijzigde gegeven of een Python DataFrame
240
+ schema_catalog (str): Naam van het schema waar de tabel instaat of opgeslagen moet worden.
241
+ rij_id_var (str): Naam van de kolom die wordt gebruikt om unieke rijen te identificeren.
242
+ naam_nieuw_df (str, verplicht bij opgegeven Python DataFrames): Naam van DataFrame/Tabel zoals die opgeslagen is in het opgegeven schema/catalog
243
+ huidig_dwh (str, optioneel): Naam van het huidige DWH DataFrame. Indien niet opgegeven, wordt
244
+ de huidige tabelnaam van 'nieuw_df' gebruikt (komt overeen met het DWH).
245
+
246
+ Laatste update: 04-12-2023
247
+
248
+ Raises:
249
+ ValueError: Als de tabel/dataframe-naam niet kan worden afgeleid vanuit het object.
250
+ Indien je bij nieuw_df een Python DataFrame meegeeft, moet je de naam van de tabel geven
251
+ zoals bij naam_nieuw_df.
252
+ """
253
+ # Bepaal of de input een SQL temporary view is of een PySpark dataframe.
254
+ if type(nieuw_df) == str:
255
+ naam_nieuw_df = nieuw_df.replace("_temp_view", "")
256
+ temp_nieuw_df = spark.table(nieuw_df).cache()
257
+ elif type(nieuw_df) != str and (naam_nieuw_df is not None and naam_nieuw_df != ""):
258
+ temp_nieuw_df = nieuw_df.cache()
259
+ else:
260
+ raise ValueError("Python kan de tabel/dataframe-naam niet afleiden vanuit het object. Indien je bij nieuw_df een Python DataFrame meegeeft, moet je de naam van de tabel geven zoals bij naam_nieuw_df")
261
+
262
+ # Haal metadata op uit de Unity Catalog
263
+ tabellen_catalog = spark.sql(f"SHOW TABLES IN {schema_catalog}")
264
+
265
+ # Maak een set van alle tabellen in het opgegeven schema
266
+ set_tabellen_catalog = {row["tableName"] for row in tabellen_catalog.collect()}
267
+
268
+ # Controleer of de opgegeven tabel al bestaat in het opgegeven schema
269
+ if naam_nieuw_df in set_tabellen_catalog:
270
+ print(f"De tabel: {naam_nieuw_df} bevindt zich in de Unity Catalogus onder het volgende schema: {schema_catalog}")
271
+ updaten_historisering_dwh(nieuw_df=temp_nieuw_df, schema_catalog=schema_catalog, rij_id_var=rij_id_var, naam_nieuw_df=naam_nieuw_df)
272
+ else:
273
+ print(f"Dit is de eerste keer dat je de tabel: {naam_nieuw_df} wilt historiseren. Historisering wordt nu toegepast...")
274
+ initialiseer_historisering(df=temp_nieuw_df, schema_catalog=schema_catalog, rij_id_var=rij_id_var, naam_nieuw_df=naam_nieuw_df)
275
+ return "Historisering is toegepast!"
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.1
2
+ Name: datateam-moss
3
+ Version: 0.0.10
4
+ Summary: Functions needed for datateam MOSS (municipality of Amsterdam)
5
+ Author-email: Robbin Breeuwer <r.j.breeuwer@amsterdam.nl>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+
10
+ # Introduction
11
+ Functions for datateam MOSS+
12
+
13
+ DISCLAIMER: Repo is in PoC phase \n
14
+ DISCLAIMER: The functions can run on Databricks using a Personal Compute Cluster
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/algemeen/__init__.py
5
+ src/algemeen/historisering.py
6
+ src/datateam_moss.egg-info/PKG-INFO
7
+ src/datateam_moss.egg-info/SOURCES.txt
8
+ src/datateam_moss.egg-info/dependency_links.txt
9
+ src/datateam_moss.egg-info/top_level.txt
10
+ src/onderwijs/__init__.py
@@ -0,0 +1,2 @@
1
+ algemeen
2
+ onderwijs