Hyppää sisältöön

BIG01: Juna-alusta

Tämä harjoitus poikkeaa muista harjoituksista siten, että alustan toteutukseen löytyy YouTube-soittolista avuksi. Soittolistan videoissa kasataan lähes identtinen alusta siitä, mitä sinä kasaat. Erona on eri data, mikä vaikuttaa pienissä määrin tiedon lataukseen, tiedon mallintamiseen ja visualisointiin.

Soittolistan videoilla kasataan alusta, joka:

  • Tuo feikkidataa jaateloauto-API:sta
  • Edustaa Data-alustat/Arkkitehtuuri mukaista yhden koneen ja yhden tietolähteen arkkitehtuuria
  • Mallintaa datan Kimballin tähtimallia muotoilevaksi Silver-kerrokseksi
  • Mallinta datan one big table -tyyppiseksi aggregaattitauluksi Gold-kerrokselle
  • Tarjoaa loppukäyttäjille Evidence BI-työkalun, jolla valittu bisnesongelma visualisoidaan

Sinun tehtäväsi on luoda mahdollisimman automatisoitu putki, joka hyödyntää dbt:tä, DuckDB:tä ja Evidenceä työkaluina, ja DuckDB-tietovarastoon tehdään medaljonkiarkkitehtuuri (bronze, silver, gold).

Muokkaa monorepoksi

Vuoden 2025 toteutuksessa tämä oli kurssin loppupuoliskon käytännön toteutus, ja täten se eli yksin omassa repositoriossaan. Vuodesta 2026 alkaen toteutuksessa on käytetty monorepoa. Älä siis toista laput silmillä opettajan komentoja vanhasta videosta: tee samat asiat nykyisen etunimisukunimi/ repositoriosi sisään, esimerkiksi hakemistoon etunimisukunimi/big01/.

Voi olla kannattavaa avata tuo hakemisto omaan VS Code -ikkunaan. Osa VS Coden ominaisuuksista, erityisesti Python virtuaaliympäristöön liittyvät, toimivat parhaiten kun ne ovat VS Coden näkökulmasta projektin juurihakemistossa.

Videon vaiheet

Soittolistan videoissa näkyy TODO-lista, jota opettaja seuraa. Alla on sama lista tarjottuna sinulle, siltä varalta, että se helpottaa tehtävän tekemistä tai videoiden seuraamista. Jos siitä ei ole apua, hyppää yli.

Demossa valittu bisnesongelma on: Jäätelöautojen viikoittainen kumulatiivinen sekä keskimääräinen (p50 ja p90) pysäkiltä myöhässä lähteminen (eli "departure lateness").

Luento 1: Jaateloauto REST API to staging

Part 1/3: Projektin aloitus

  • Valitse bisnesongelma
    • Tutustu REST API:n dokumentaatioon ja varmista että valitsemasi ongelmaa vastaava data on olemassa. Jos ei, vaihda bisnesongelma.
  • Alusta repositorio
  • Asenna uv
    • Asenna Python 3.12 (uv python install 3.12)
    • Pinnaa versio (uv python pin 3.12)
    • Alusta uv-projekti (uv init --lib --name "ope-data-alusta" .)
      • --lib selitetään myöhemmin hakemistorakenteessa.
    • Luo virtuaaliympäristö (uv sync)
    • ... ja varmista, että VS Code käyttää sitä Python Interpreterinä.
  • Päätä projektihakemiston rakenne, esim:
    • Data menee aina ./data/
      • Raakadata louhitaan ./data/lake/*/*
      • Tietovarasto sijoitetaan ./data/warehouse/jotain.db
    • Koodi sijoitetaan ./src/ (uv huolehti jo tästä)
    • Automaatioskriptit sijoitetaan ./scripts/
    • Tee tyhjistä hakemistoista pysyviä .gitkeep-tiedostojen avulla.
    • Puske GitLabiin (muista git status -u !!!)

HOX! Oikeaa tehtävää tehdessä tässä välissä olisi hyvä pitää dokumentaatio kunnossa. Onhan sinulla README.md-tiedosto vähintään jo otsikkotasolla päivitetty?

Part 2/3: Jaateloauto REST API

HOX! Tähän väliin käytännön vinkki: teille on täysin sallittua sijoittaa repositorioon MEMO.md tai ./notes/*.md tai ylläpitää muistiinpanoja Notionissa tai käyttää fyysistä muistivihkoa. Minä en sitä tee videoilla, mutta opiskellesssa suosittelen tekemään muistiinpanoja. Ethän aja itseäsi tilanteeseen, jossa "Ajoin komennon, jota en muista, ja sit tuli virhe, jota en muista, mutta nyt tämä ei enää toimi." Tätä kannattaa käyttää myös työelämässä.

HOX! Tee muistiinpanoja myös kurssipalautteeseen liittyvistä asioita. Näin osaat antaa parempaa palautetta kurssin lopuksi, kun ongelmakohdat eivät ole muistin varassa. Voit käytännössä copy-pasteta palautteen intran kaavakkeeseen. Kukaan meistä ei ole täydellinen, mutta me kaikki voimme kehittyä. Palaute auttaa tässä.

  • Kloonaa Jäätelöauto API
    • Lue README.md ja aja palvelu ylös.
    • Tutustu Swagger UI:iin (localhost:8000)
    • Tutustu Rautatieliikenne API:iin
      • Swagger
      • (Optional) GraphQL
      • Tsekkaa myös Keskusteluryhmä. Näet aitoa bugi-ilmoittelua!
  • Tutustu REST:n olemukseen JSONPlaceholder avulla.
    • Selain
    • HTTPie
    • Testaa samat vielä Jäätelöauto API:n datalla
    • Tsekkaa vielä HTTP verbit
  • Python Notebook Playground
    • Asenna IPykernel ja requests (uv add ipykernel requests)
    • Tee hakemisto Notebookeille (esim. ./notebooks/)
    • Luo uusi Notebook, rest_tutuksi.ipynb
    • Kokeile requests-kirjastoa (requests.get('url').json()
  • Bonus: kurkataan huvikseen myös Vanhat junat zip-paketteina -vaihtoehtoa. Tämä voi olla houkutteleva, mikäli jonkun bisnesongelma käsittelee pidemmän ajanjakson ongelmaa useampaan junaan liittyen. Keksitkö tavan automatisoida kaikkien näiden S3:lla olevien zip-tiedostojen lataamisen (esimerkiksi ./data/lake/staging/vr-zip/yyyy-mm-dd.zip) ja purkamisen (./data/lake/staging/vr-data/yyyy/mm/dd/???.zip).
  • Puske GitLabiin

HOX! Varsinaiset skriptit voit kehittää .ipynb-Notebookissa, jos kaipaat interaktiivisuutta kehittäessä, mutta luo niistä kuitenkin lopulta .py-skriptit, jotta niiden parametrisointi ja automatisointi on helppoa myöhemmin.

HOX! Get-metodin palauttaman objektin metodi .json() on kiva plärätessä, mutta jos haluat Bronzen todella olevan as-raw-as-possible, kirjoita palautuneet bytet sellaisina kuin ne ovat saapuneet ilman turhia enkoodauksia, joita ko. metodi väkisinkin ujuttaa mukaan.

HOX! Huomaa, että sinun tulee aina lukea käyttämäsi API:n käyttöehdot ja -ohjeet. Esimerkiksi Fintrafficilla on oma Tuki > Ohjeita ja lisätietoa rajapintojen käyttöön -ohje, jossa neuvotaan pakolliset headerit (eli 'Digitraffic-User: Junamies/FoobarApp 1.0') sekä kuvaukset rajoituksista, jotka rajoittavat kyselyiden tiehyttä (default: 60 kpl/min).

Part 3/3: Ingestion Tool

  • Koodaa dummy ingestion tool.
    • Jupyter Notebook (./notebooks/jaateloauto_ingestion.ipynb)
    • Hetken miete datan/skeeman tarkistukselle (e.g. Polars schema)
    • Hetken miete JSON koskemattomana vai esim. JSON Lines (ELT vs EtLT)
    • Hetken miete, saako tässä välissä tiputtaa tarpeettomia kenttiä pois
  • (Advanced:) Koodaa helpommin ajettava ingestion tool
    • Python (./src/ingestor/jaateloauto.py) ja argparse
    • Immutability!
  • Laita data/lake/staging/ gitignoreen. Data ei kuulu versionhallintaan.
  • Puske GitLabiin.

📅 Tähän pisteeseen teidän pitäisi päästä ensimmäisellä viikolla! Kirjoittakaa jäätelöautodatan kylkeen skriptit, jotka louhivat kyseisen dummydatan sijasta aitoa raideliikennedataa.

Luento 2: Staging to Bronze using dbt

Part 1/3: dbt ja DuckDB

  • Asenna dbt
    • Aloita asentamalla DuckDB CLI. (⚠️ Huomaa mahdollinen vaatimus C++ kirjastojen asentamiselle Windowsissa! ⚠️).
    • Tämän jälkeen asenna dbt-duckdb (uv add dbt-duckdb), jonka riippuvuuksina asentuvat myös dbt-core ja duckdb.
  • Luo dbt-projekti
    • Päätä: voit luoda joko jäätelöautolle ja junadatalle yhteisen tai kummallekin erikseen.
    • uv run dbt init
    • cd <projektin_nimi>
    • Luo lokaali profiles.yml (katso malli alta)
  • Tutustu seuraaviin tiedostoihin:
    • profiles.yml (configuration precedence: Project, User, System)
    • dbt_project.yml
    • models/**/*.yml
    • models/**/*.sql
      • Ja niissä {{ jotain }} -syntaksi

Luo lokaali profiles.yml:

dbt_warehouse:
  target: dev
  outputs:
    dev:
      type: duckdb
      path: '../data/warehouse/warehouse.duckdb'
      schema: main
  • Aja ensimmäinen malli
    • uv run dbt run
    • uv run dbt test <= 🚧 Noteeraa virhe 🚧
    • Tutki tiedostoja, etsi virhe, korjaa se.
  • Tutustu DuckDB UI:in
    • Aja duckdb -ui polku/sinun/warehouseen.duckdb. Selaimeen aukeaa etäisesti Jupyter Notebookin oloinen SQL-client.
    • Huomaa, että se luo Notebookit ja muun datan kotikansioosi ~/.duckdb/extension_data/ui/
  • Tutustu docsiin
    • uv run dbt docs generate
    • uv run dbt docs serve
  • Puske GitLabiin

Part 2/3: dbt ja Fake CSV

  • Luo tiedostot
    • data/lake/staging/csvtest/customers.csv (id, name, email)
    • .../orders.csv (id, customer_id, product, quantity)
  • Luo datamalli Bronzelle
    • Hakemisto <projektin_nimi>/models/bronze/csvtest
    • Tiedostot csvtest_customers.sql ja csvtest_orders.sql
    • Lisää dbt_project.yml tiedostoon model
      • materialized: table
      • schema: bronze (muokkaa profilesiin target: dev, jotta DuckDB schemaksi tulee dev_bronze)
    • Kirjoita myös schema.yml (tai whatever.yml)
      • (Optional:) Käytä docs.md tiedostoa ja tuo se näin: '{{ doc("customers_desc") }}'
  • Puske GitLabiin

Part 3/3: dbt Jaatelo Bronzelle

  • Tarkastele tiedostoja
    • data/lake/staging/jaatelo/<junanro>/<date>.json
    • Tiedosta, että REST API saa olla nyt alhaalla.
  • Luo datamalli Bronzelle
    • Hakemisto models/bronze/jaatelo
    • Tiedosto jaateloauto_api.sql
      • Suunnittele, älä hutki!
      • Luo primääriavain sk_jaateloauto (truckId + departureDate + operatorId)
      • ... käytä myös concat_ws, coalesce tai ifnull ja md5 hash
      • ... ja dbt:ssä käytä dbt utils (packages.yml ja uv run dbt deps)
    • Lisää dbt_project.yml tiedostoon modelseihin
    • Dokumentoi schema.yml
  • Puske GitLabiin

Luento 3: Silver and Gold modelling

Part 1/3: Silver Timetable

  • Luo datamalli Silverille

    • Suunnittele ennen kuin teet! Sinun pitää tietää, mikä yksi rivi yhdessä taulussa edustaa!

    • Tästä alkaen työ on mekaanista toistoa yllä olevasta, mutta kaikki tehdään models/silver ja gold kansioon

    • SQL kannattaa taas harjoitella interaktiivisessa DuckDB UI -tilassa ja myöhemmin liittää .sql-tiedostoon.

    • Luotavat 🥈 Silver taulut:

      • jaateloauto_timetable : one row per an event (arrive, departure) that has ever happened. In other words, this is the unnested JSON data from Bronze. Primary key needs to be a surrogate key.
        • timetable_row -> '$.station' as method_a (JSON Functions) (json_extract) sopii kaikille paitsi stringeille.
        • timetable_row --> '$.stopName' as method_b (json_extract_string) parsii pois hipsukat stringeistä.
        • Yhdistä castin kanssa: json_extract(ttr, '$stopId')::INT
        • Jos kenttä on yhä nestattu: json_extract(ttr, '$.latenessCauses')::JSON[]
        • PK/SK: sk_jaateloauto_timetable : (sk_jaateloauto + stop_id + stop_direction)
        • Lisää modeliksi ja dokumentoi taulu.
    • Puske GitLabiin

Part 2/3: Silver Fact and Dim

Luotavat 🥈 Silver taulut hakevat kumpikin tiedot yllä tehdystä aputaulusta.

  • f_stop : A fact table representing one row per stop - including arrival and departure. The ice cream trucks stops to sell ice cream. It will include columns for arrives lateness and depature lateness. In short, the data is nearly the same as in the one above, but granularity has been reduced to stop (instead of stop-event). The moment of a truck stopping to sell ice cream has been chosen as a business event that is tracked. Primary key needs to be a surrogate key, since we don't get a unifying UUID from source system of other suitable id.

    • PK/SK: sk_stop (sk_jaateloauto + stop_id) using
  • d_truck : One row per truck. In real life, we would most likely enrich this data with other information such as route length, amount of stops on the truck's route et cetera. Primary key is truck_id, which is essentially a natural key, since customer's would see this truck id in the time schedule.

    • PK: truck_id
    • Huomaa, että koska jäätelöauton perustiedot pysyvät päivästä toiseen samana, voimme noutaa kaiken tiedon kyseisen trukin uusimmasta rivistä, ja voimme myös pitää esimerkiksi pelkästään departuren.
  • Puske GitLabiin

Part 3/3: Gold

Luotavat 🥇 Gold taulut:

  • jaateloauto_weekly_lateness : one row per week + truck_id + operator_name combo. We will focus on departure times. Aggregated fields field be e.g. total_departure_lateness, p50_departure_lateness (alias median) and p90_departure_lateness.
  • Tarkista, että dbt docsissa lineage näkyy oikein.
  • Puske GitLabiin

Luento 4: Evidence

Info

Jos haluat, voit korvata Evidencen jollakin toisella BI-työkalulla. Vaihtoehtoja ovat esimerkiksi:

  • Marimo Notebook + Altair
  • Streamlit + Matplotlib/Plotly/Altair/etc.
  • Power BI tai Tableau
  • JavaScript + D3.js

Evidencen setup on aiheuttanut joillakin Windows-käyttäjillä päänvaivaa, joten sallin myös vaihtoehtoiset ratkaisut. BI-kerros ei ole tämän harjoituksen pääpointti, mutta se pitää kuitenkin olla olemassa, jotta Gold-tason tauluille on jokin merkitys olemassa.

Part 1/2

  • Tutustu Evidencen dokumentaatioon
  • Aja ensin init
    • docker compose -f docker-compose.init.yml up --build
    • docker compose -f docker-compose.init.yml down
    • ... jotta saat bi/workspace kansion.
  • Lisää GitLabiin

Tässä välissä, jos haluat, voit tutkia mitä uusi volume sisältää.

# Huomaat, että sinulle on uusi volume evidence_node_modules
docker volume ls

# Luo tilapäinen kontti, johon volume on mountattu
docker run --rm -it \
  -v evidence_node_modules:/mnt \
  ubuntu bash

# Kontin sisällä voit ajaa seuraavat komennot.
# Ctrl + D sulkee ja tuhoaa kontin (mutta ei volumea)
cd /mnt
ls
  • Aja sitten palvelu ylös
    • Tarpeen mukaan docker compose build
    • docker compose up --watch
    • Kokeile muokata index.md:tä. Sivun pitäisi päivittyä.
  • Puske GitLabiin

Tässä välissä vaihdan Windowsiin, jotta homma olisi hieman cross OS -testattua:

  • Aja ensin dbt-komennot, jotta warehouse.duckdb on ajan tasalla.
  • Testaa kummatkin tilanteet:
    • on ensin ajettu rm -r bi/workspace ja sitten ajetaan kummatkin docker-komennot. Kontin pitäisi päivittyä kun index.md:tä muokataan.
    • workspace on valmiiksi olemassa, mutta tämän koneen Docker-ympäristöstä puuttuu evidence_node_modules. Muista tuhota se ensin yllä olevan testin jäljiltä. Muutoin testi on sama kuin yllä.

Part 2/2

  • Kirjoita update_data.sh skripti, joka ajaa tarvittavat dbt-rimpsut, jotta warehouse on ajan tasalla, ja lopulta kopioi sen bi/workspaces/sources/warehouse/warehouse.duckdb tiedoston päälle.
  • Tutustu Evidence Docs: Build Your First App
  • Luo source:
    • HOX! Koska Compose Watch:n sync on yksisuuntainen host -> container, et voi luoda tietolähteitä Evidence GUI:ssa. Tee tämä ihan vain luomalla tiedostoja.
    • Luo connection.yaml ja jaateloauto_weekly_lateness.sql esimerkkinä toimivan needful_things kaltaisesti.
    • Testaa Evidence SQL Consolessa select * from warehouse.jaateloauto_weekly_lateness
  • Luo itse sivu:
    • Muokkaa index.md-tiedostoa.
    • Luo perus SQL code block, kuten ohjeissa neuvotaan, jotta saat datan aliakseen.
    • Kokeile ihan vain <DataTable data={code_block_nimi_ylta} />
    • Lisää <BarChart />, jonka pitäisi istua tähän käyttöön varsin hyvin.
      • data=
      • x=week_monday
      • y=p50_departure_lateness
      • y2=p90_depatrure_lateness
      • xFmt=shortdate
      • xAxisTitle=Week
      • yAxisTitle=Minutes
    • Ja toinen, jossa on lisäksi series=truck_id ja type=grouped tai =stacked
  • Puske GitLabiin

Videolla esitettävä

Tässä harjoituksessa videon tulee osoittaa alustasi ja datarakenteesi keskeisimmät oivallukset. Koska teit tässä useita vaiheita, tiivistä presentaatio näyttämään prosessin kulku. Videolla näkyy vähimmillään seuraavat vaiheet:

  1. Aloitus ja tavoite: Kerro valitsemasi junadatan bisnesongelma (esim. joidenkin tiettyjen junien myöhästymiset) ja näytä, mihin REST API -päätepisteeseen olet yhdistänyt.
  2. Raakadatan nouto: Näytä Python-skriptisi (ingestion tool), joka hakee datan, ja demonstroi, että raakadata tallentuu onnistuneesti .json tai .csv -muodossa Datalaken staging-kansioon.
  3. Bronze (dbt & DuckDB): Esittele, miten dbt tekee raakadatasta DuckDB-tietovaraston Bronze-tason taulun (näytä esim. SQL-kyselysi tai DuckDB UI -näkymä).
  4. Silver & Gold -mallinnus (dbt): Esittele tekemäsi dimensionalinen mallinnus (Silver-tason ratkaisut) sekä lopputuloksena syntyvä Gold-tason aggregaatiotaulu.
  5. dbt Docs & Lineage: Avaa dbt docs selaimessa ja näytä projektisi data lineage -graafi varmistaaksesi putken oikeellisuuden.
  6. BI: Esittele lopuksi dashboard, joka hyödyntää DuckDB:n Gold-tasoa ja vastaa alussa asettamaasi bisnesongelmaan. Voit tehdä kerroksen valitsemallasi työkaulla; ei ole pakko käyttää Evidenceä.