1.2 Adatéletciklus és a predikció pillanata

1.2.1 Cél és konkrét helyzet

A Northstar prediktív projektben a modellnek minden rendelés leadásakor, az order_ts időpontban kell 0–1 közötti kockázatot adnia arra, hogy a kézbesítés a 7 napos ígéretnél később történik. A bemenet csak a rendelés pillanatában ismert jellemzőkből állhat; a kimenet a target_late becslése. Ebben az alfejezetben nem modellt építünk, hanem az adatéletciklus időrendjét rögzítjük: minden mezőhöz hozzárendeljük, mikor válik ismertté, és melyik fázis használhatja. A konkrét input a kezdőlabor lab/orders.csv és lab/order_items.csv fájlja; az elvárt eredmény egy rendelkezésreállási tábla, valamint egy ellenőrző kód, amely kimutatja a jövőbeli mezők tiltott prediktorhasználatát.

Mielőtt tovább olvas, adjon előzetes becslést: az orders.csv 15 mezője közül hány ismert már a rendelés pillanatában, és hány csak később? Az order_items.csv 8 mezője közül hány későbbi?

1.2.2 Fogalmi magyarázat: adatforrás, as-of idő, label és provenance

Adatforrás (data source) az a rendszer vagy folyamat, ahol az érték keletkezik. A rendelési rendszer a kosár adatait, a szállítási rendszer a tényleges kézbesítést, az ügyféltörzs az ügyfél törzsadatait szolgáltatja. A Northstar szintetikus esetben a generate_data.py szimulálja ezeket a forrásokat; nincs valós ügyfél vagy üzleti tranzakció.

Nyers és feldolgozott adat (raw/processed data) nem ugyanaz. A lab/orders_raw.csv szándékosan hibás nyers másolat: öt duplikált sor, két hiányzó distance_km, egy negatív basket_minor és egy nem létező customer_id szerepel benne. A tiszta lab/orders.csv a tisztítás után keletkezett kontroll; a döntéseket auditnaplónak kell kísérnie, nem szabad csendben nullát vagy nullapontot feltételezni. A nyers és a tiszta adat tehát külön életszakasz, és csak a tiszta verzióhoz tartozik rögzített minőségi szerződés.

Label (címke) a felügyelt tanulás célértéke, amely a tanításkor már ismert, a predikció pillanatában viszont még nem. A Northstar késési feladatban a label a target_late: értéke 1, ha delivery_days > promised_days, egyébként 0. A target_late maga származtatott mező; a delivery_days ennek az alapanyaga, de szintén csak a kézbesítés után ismert.

As-of idő (as-of time) az a pillanat, amikor egy tábla állapotát nézzük. Ma lekérdezve a múltbeli rendelések már tartalmazzák a delivery_days értékét, de egy 2025. áprilisi predikciós pillanatban még nem létezett. Minden mezőhöz hozzárendelhetünk egy ismertté válási időpontot: a mező legkorábban attól kezdve használható, amikor az érték ténylegesen a rendelkezésünkre áll. A kezdőprojekt konzervatív szerződése szerint a címke a teljes kézbesítéskor válik ismertté: label_available_at = actual_delivery_at, akkor is, ha a késés tényét elvileg hamarabb is fel lehetne ismerni.

Provenance (származás, eredetigazolás) azt rögzíti, honnan jött az adat, milyen átalakítási láncon ment át, és melyik verzió volt az input. A kezdőlabor lab/manifest.json fájlja a seedet, a sorszámokat, a hashértékeket és a kontrollösszegeket tartalmazza; a quickcheck.py ezeket ellenőrzi. Ha egy kolléga manifest nélkül ad át egy CSV-t, nem tudjuk reprodukálni az állapotát, és nem tudjuk eldönteni, hogy a tisztítási döntések hol történtek.

1.2.3 Időpont, intervallum és feltételes információ

Jelölje t0,it_{0,i} az ii rendelés order_ts időpontját, τj\tau_j pedig a jj mező ismertté válási időpontját. Egy xjx_j mező akkor szerepelhet a t0,it_{0,i} pillanatban futó modell bemenetében, ha

τj≤t0,i. \tau_j \leq t_{0,i}.

A ≤\leq jel itt időpontok UTC-sorrendjét jelenti; az egyenlőség azt fejezi ki, hogy a rendelés leadásával egyidejűleg ismertté váló mező még éppen használható. A delivery_days esetében

τdelivery_days=actual_delivery_ati>t0,i, \tau_{\text{delivery\_days}} = \text{actual\_delivery\_at}_i > t_{0,i},

tehát a mező nem lehet prediktor. Az ℐ(t)\mathcal{I}(t) információs halmaz legyen azoknak az értékeknek a halmaza, amelyek tt-ben már ismertek. A fenti szabály így írható:

xi⊆ℐ(t0,i),yi∉xi, x_i \subseteq \mathcal{I}(t_{0,i}), \qquad y_i \notin x_i,

ahol xix_i az ii rendelés predikciós bemenete, yiy_i pedig a target_late label. A második feltétel azt mondja ki, hogy a label soha nem lehet a predikció inputja, még akkor sem, ha a tanító CSV-ben fizikailag ott van.

A label definíciója nap mértékegységben:

yi=𝟏{delivery_daysi>promised_daysi}, y_i = \mathbf{1}\{\text{delivery\_days}_i > \text{promised\_days}_i\},

ahol 𝟏{⋅}\mathbf{1}\{\cdot\} az indikátorfüggvény: értéke 1, ha a kapcsos zárójelben lévő feltétel igaz, különben 0. A promised_days a kezdőpéldában 7 nap, a delivery_days pozitív egész nap.

A tanítási cutoff $c = $ 2025-05-01T00:00:00+00:00. Egy rendelés akkor használható tanításra, ha a rendelés és a címke is a cutoff előtti:

traini(c)=𝟏{order_tsi<c}⋅𝟏{label_available_ati<c}. \text{train}_i(c) = \mathbf{1}\{\text{order\_ts}_i < c\} \cdot \mathbf{1}\{\text{label\_available\_at}_i < c\}.

A szorzás itt logikai ÉS: a sor csak akkor kap 1-et, ha mindkét feltétel igaz. A teszt feltétele

testi(c)=𝟏{order_tsi≥c}, \text{test}_i(c) = \mathbf{1}\{\text{order\_ts}_i \ge c\},

a kihagyott határsáv pedig

purgatoryi(c)=𝟏{order_tsi<c}⋅𝟏{label_available_ati≥c}. \text{purgatory}_i(c) = \mathbf{1}\{\text{order\_ts}_i < c\} \cdot \mathbf{1}\{\text{label\_available\_at}_i \ge c\}.

A három halmaz diszjunkt, és együtt lefedi az összes rendelést. Kézi kontroll: ha order_ts = 2025-04-28T09:00:00+00:00, label_available_at = 2025-05-04T18:00:00+00:00, akkor a rendelés a kihagyott határsávba esik: a rendelés ugyan a cutoff előtti, de a címke a cutoffkor még nem érkezett meg. A tanításhoz a címkének szigorúan a cutoff előtt kell rendelkezésre állnia.

1.2.4 Rendelkezésreállási tábla és minimális kód

A következő táblázat az orders.csv mezőit a lab/ADATOK.md szerződéséből sorolja fel. A t0t_0 oszlop a rendelés pillanatát jelenti; a „prediktor” oszlop a rendelkezésre állás és a kezdő feature-lista együttes ítélete.

Mező Keletkezés / ismertté válás Ismert t0t_0-kor? Szerep a starterben
order_id rendelés rögzítésekor, t0t_0 igen azonosító, nem prediktor
customer_id ügyféltörzs, t0t_0 előtt igen azonosító, nem prediktor
order_ts rendelés rögzítésekor igen a predikció időpontja
promised_days szolgáltatási szabály, t0t_0 igen konstans 7, a label definíciójához kell
quantity_sum kosár, t0t_0 igen feature
item_rows kosár, t0t_0 igen elérhető, a starter listán nem szerepel
basket_minor kosár, t0t_0 igen feature
shipping_fee_minor díjszabás, t0t_0 igen elérhető, a starter listán nem szerepel
currency díjszabás, t0t_0 igen konstans EUR
distance_km logisztikai rendszer, t0t_0 igen feature
priority_flag szolgáltatási szabály, t0t_0 igen feature
delivery_days tényleges kézbesítéskor nem tiltott prediktor; a label alapanyaga
actual_delivery_at tényleges kézbesítéskor nem tiltott; a label időbélyege
label_available_at származtatott: = actual_delivery_at nem tiltott; a taníthatóság kapuja
target_late származtatott: delivery_days > 7 nem a tanulási label, nem input

Az order_items.csv mezői:

Mező Keletkezés Ismert t0t_0-kor? Szerep
order_id rendelés rögzítésekor igen join kulcs
line_id rendelés rögzítésekor igen tételsor azonosító
product_id katalógus igen nem starter feature
quantity kosár, t0t_0 igen nem starter feature
unit_price_minor árazás, t0t_0 igen nem starter feature
discount_minor árazás, t0t_0 igen nem starter feature
line_total_minor származtatott, t0t_0 igen nem starter feature
currency árazás, t0t_0 igen konstans EUR

Az order_items minden mezője a rendelés leadásakor ismert: a kosár tételei akkor rögzülnek. A kezdő modell mégsem közvetlenül ezeket a sorokat használja, hanem az orders táblában lévő kosárösszegzőket. A későbbi SQL-fejezetben látjuk majd, hogy a tételszintű join miért duplázhatja a szállítási díjat, mert az shipping_fee_minor rendelésenként egyszer szerepel.

A kezdő feature-listában szereplő segment, region, acquisition_channel a customers.csv-ből, az account_age_days pedig a signup_date és az order_ts különbségeként származik. Mivel a regisztráció dátuma minden rendelés előtt van, ezek a mezők a rendelés pillanatában szintén ismertek.

A következő kód a projektgyökérből futtatva a két CSV fejlécét ellenőrzi a várt halmazokkal, és megnézi, hogy a jövőbeli mezők nem kerültek-e a starter feature-listába. A kód csak a Python standard libraryjét használja. Az itt látható kimenet a szerződésből levezetett várt kimenet, nem tényleges futási jegyzék; a laborban való futtatáskor a lab/manifest.json alapján ellenőrizhető.

from pathlib import Path
import csv

PREDICTION_TIME_FIELDS = {
    "order_id", "customer_id", "order_ts", "promised_days",
    "quantity_sum", "item_rows", "basket_minor", "shipping_fee_minor",
    "currency", "distance_km", "priority_flag",
}
LATER_FIELDS = {
    "delivery_days", "actual_delivery_at", "label_available_at", "target_late",
}
FORBIDDEN_AT_PREDICTION_TIME = LATER_FIELDS

STARTER_FEATURES = {
    "distance_km", "priority_flag", "basket_minor", "quantity_sum",
    "account_age_days", "segment", "region", "acquisition_channel",
}
ORDER_ITEMS_EXPECTED = {
    "order_id", "line_id", "product_id", "quantity",
    "unit_price_minor", "discount_minor", "line_total_minor", "currency",
}

def header(path):
    with Path(path).open(newline="", encoding="utf-8") as f:
        return set(csv.DictReader(f).fieldnames or [])

orders_fields = header("lab/orders.csv")
items_fields = header("lab/order_items.csv")

print("orders mezők száma:", len(orders_fields))
print("hiányzó orders mezők:", sorted((PREDICTION_TIME_FIELDS | LATER_FIELDS) - orders_fields))
print("váratlan orders mezők:", sorted(orders_fields - (PREDICTION_TIME_FIELDS | LATER_FIELDS)))
print("order_items hiányzó mezők:", sorted(ORDER_ITEMS_EXPECTED - items_fields))
print("order_items váratlan mezők:", sorted(items_fields - ORDER_ITEMS_EXPECTED))

assert FORBIDDEN_AT_PREDICTION_TIME.isdisjoint(STARTER_FEATURES)
print("szivárgás-ellenőrzés: OK")

Futtatás platformjelölt munkakönyvtárral:

# Linux/macOS terminál, a projektgyökérből:
python s002_availability_check.py
# Windows PowerShell, a projektgyökérből:
py s002_availability_check.py

A csv.DictReader a fejlécet olvassa be; a fieldnames az oszlopnevek listája, a set() pedig halmazként kezeli őket, így a sorrend nem számít. A halmazkivonás (-) a hiányzó és váratlan mezőket adja; a sorted() csak a kimenet megjelenítését teszi determinisztikussá.

1.2.5 Eredmény értelmezése

A szerződésből levezetett várt kimenet:

orders mezők száma: 15
hiányzó orders mezők: []
váratlan orders mezők: []
order_items hiányzó mezők: []
order_items váratlan mezők: []
szivárgás-ellenőrzés: OK

A 15 orders-mezőből 11 a PREDICTION_TIME_FIELDS, 4 a LATER_FIELDS halmazban van; a két halmaz diszjunkt, és az order_items 8 mezője mind a rendeléskor ismert. Az assert FORBIDDEN_AT_PREDICTION_TIME.isdisjoint(STARTER_FEATURES) az invariáns: ha valaki véletlenül beteszi a delivery_days-t a starter feature-listába, a delivery_days, actual_delivery_at, label_available_at vagy target_late bármelyike azonnal AssertionError-t ad. Az üres halmazok tehát nem formálisak: pontosan azt ellenőrzik, hogy a CSV séma nem tért el a meghirdetett szerződéstől, és a prediktorhalmaz nem lát a jövőbe.

Két időbélyeg, két eltérő szabály. A forrásesemény időpontja (event_time) és a rendszerben való tényleges elérhetőség (available_at/ingested_at) különbözhet. Egy feature csak akkor megengedett a tt predikciós pillanatban, ha az előállításához szükséges minden esemény és minden adat hozzáférhető volt legkésőbb tt-kor. Az utólag javított, visszadátumozott mező nem tekinthető történetileg ismertnek.

Ellenpróba: készíts olyan ügyfelet, akinek hétfőn keletkezett, de szerdán beérkezett eseménye van. A keddi as-of tábla nem használhatja ezt az eseményt, még akkor sem, ha az esemény időbélyege hétfő.

1.2.6 Feladatok

  1. Értelmezés. Egy rendelés order_ts = 2025-04-28T09:00:00+00:00, label_available_at = 2025-05-04T18:00:00+00:00. A tanítási cutoff 2025-05-01T00:00:00+00:00. Melyik split-halmazba tartozik a sor? Fogalmazza meg mindkét kritériumot, és mondja ki, miért nem kerülhet sem a tanító, sem a teszt halmazba.

  2. Megvalósítás. Írjon split_label(order_ts, label_available_at, cutoff) függvényt, amely a "train", "test" vagy "purgatory" sztringet adja vissza. Tesztelje a három esetet: (a) rendelés 2025-04-10, label 2025-04-20; (b) rendelés 2025-05-02, label 2025-05-10; (c) rendelés 2025-04-28, label 2025-05-04.

  3. Hibakeresés. Egy kolléga 0,99 teszt AUC-t mutat a késési modellre, mert a feature-listába betette a delivery_days oszlopot. Mi a hiba lényege? Hogyan mutatja ki a rendelkezésreállási tábla? Mi a javítás, és mit mondana a 0,99-es AUC-ról?

  4. Megváltozott feltétel. A logisztikai rendszer új planned_dispatch_date mezőt ad, amely a rendelés leadásakor ismert tervezett feladási napot tartalmazza. A rendszer azonban ezt a dátumot utólag is frissíti. Milyen feltétellel lehet prediktorként használni, és mit kell rögzíteni a provenance-ban?

1.2.7 Megoldások és indoklás

  1. A sor a kihagyott határsávba tartozik. A tanítási feltétel első fele igaz: order_ts < cutoff, mert 2025-04-28 megelőzi 2025-05-01-et. A második fele hamis: label_available_at = 2025-05-04 18:00, ami nem szigorúan korábbi a cutoffnál. A teszt feltétele is hamis, mert a teszt definíciója order_ts >= cutoff. A rendelés tehát sehol sem tanítható a cutoff ismeretében, és a modell buildjekor nem játszhatja el, hogy a címkéje már rendelkezésre áll.

  2. Lehetséges megoldás Python 3.12 datetime moduljával:

from datetime import datetime

def split_label(order_ts, label_available_at, cutoff="2025-05-01T00:00:00+00:00"):
    t0 = datetime.fromisoformat(order_ts)
    tl = datetime.fromisoformat(label_available_at)
    c = datetime.fromisoformat(cutoff)
    if t0 >= c:
        return "test"
    if tl < c:
        return "train"
    return "purgatory"

assert split_label("2025-04-10T09:00:00+00:00", "2025-04-20T18:00:00+00:00") == "train"
assert split_label("2025-05-02T09:00:00+00:00", "2025-05-10T18:00:00+00:00") == "test"
assert split_label("2025-04-28T09:00:00+00:00", "2025-05-04T18:00:00+00:00") == "purgatory"

A datetime.fromisoformat a +00:00 offsetet is értelmezi, így mindhárom érték időzóna-tudatos. Ha valamelyik bemenet nem tartalmazna offsetet, a naiv és az offset-tudatos időpont összehasonlítása TypeError-t adna; a szerződés éppen ezért követeli meg az explicit +00:00 offsetet. A várt ellenőrzési invariáns: adott cutoff mellett a három halmaz diszjunkt, és minden sor pontosan egy címkét kap.

  1. A hiba a jövőbeli adat szivárgása. A rendelkezésreállási tábla szerint a delivery_days ismertté válási ideje actual_delivery_at, ami mindig későbbi az order_ts predikciós pillanatnál. A teszt kiértékelésekor a modell olyan inputot kapott, amely a termelési predikció pillanatában nem létezik; ezért a 0,99 AUC nem valós képességet mér, hanem azt, hogy a modell megtanulta a kézbesítési eredményt. A javítás: törölni a delivery_days-t a prediktorlistából, a pipeline-t változatlan beállításokkal újra futtatni, és az új, ténylegesen mért teszt AUC-t értékelni. A 0,99 így nem bizonyíték, hanem a folyamat hibájának tünete.

  2. A mező a rendelkezésreállási táblában akkor kaphat „ismert t0t_0-kor” bejegyzést, ha a planned_dispatch_date a rendelés leadásakor ténylegesen kiszámítható, és a modell inputja a rendeléskori érték. Ha a rendszer utólag frissíti, a CSV-ben szereplő jelenlegi érték már nem feltétlenül az a dátum, amelyet a rendelés pillanatában ismertek; ebben az esetben a frissített mező jövőbeli információt tartalmaz. Csak as-of pillanatfelvételből szabad dolgozni: a táblának azt az értéket kell tárolnia, amely az order_ts időpontban érvényes volt. A provenance-ban rögzíteni kell a forrást, az érték időbélyegét, a generáló folyamat verzióját és azt a szabályt, hogy a logisztikai rendszer utólagos frissítései nem írhatják felül a predikciós inputot.

1.2.8 Gyakori tévedés ellenpéldával

A leggyakoribb félreértés: a cél kiszámításához szükséges jövőbeli adat nem automatikusan engedélyezett feature. A delivery_days a tanító CSV-ben fizikailag jelen van, és a modell kiválóan illeszkedik rá; a teszt AUC csábítóan magas lehet. De a predikció pillanatában a kézbesítés még nem történt meg, így a mező nem létezik. Ugyanez igaz a label_available_at-re: ez nem a label értéke, hanem a taníthatóság időbélyege, és prediktorként használva szintén a jövőbe lát. Az s001-ben rögzítettük, hogy a modell a rendelés pillanatában fut; most az as-of idő ezt a pillanatot formális korláttá teszi. Önellenőrző kérdés minden új feature-höz: „Ismerném-e ezt az értéket akkor, amikor a rendelés beérkezik?” Ha a válasz nem, a mező tiltott, akármilyen jó AUC-t ígér.

1.2.9 Rövid összefoglaló

Minden mezőhöz rendeljünk ismertté válási időt: egy prediktor csak akkor használható, ha az érték a predikció pillanatában már létezik. A Northstar orders táblájában 11 mező ismert a rendeléskor, 4 pedig csak kézbesítés után; a delivery_days a label alapanyaga, nem prediktor. A tanításhoz a rendelésnek és a címkének is a cutoff előtt kell lennie; a kihagyott határsáv a címke késése miatt nem tanítható sorokat gyűjti. A nyers és tiszta adat, a manifest és az auditnapló együtt adja a provenance-t. A következő alfejezetben ezt az időbeli fegyelmet a reprodukálható munkanaplóba építjük be.