# Analiza planu iteracji 2–7: System targetów / progów oceny metryk

**Data analizy:** 2026-04-17  
**Kontekst:** Iteracja 1 wdrożona. Analiza dotyczy iteracji 2–7.

---

## 1. Kolejność iteracji — ocena i rekomendacje

### Ocena ogólna

Proponowana kolejność 2→3→4→5→6→7 jest **zasadniczo logiczna**, ale zawiera kilka punktów spornych:

| Iteracja | Ocena kolejności | Uwaga |
|---|---|---|
| 2 — Nowe metryki | ✅ Właściwa | Naturalne rozszerzenie iter. 1 |
| 3 — Dwa targety jednocześnie | ✅ Właściwa | Zależy od iter. 2 |
| 4 — Fallback globalny | ⚠️ Dyskusyjne | Warto połączyć z iter. 2 |
| 5 — Kontekst czasowy | ✅ Właściwa | Logicznie po ustabilizowaniu danych |
| 6 — Historia zmian | ✅ Właściwa | Niezależna od iter. 5 |
| 7 — Widok porównawczy | ✅ Właściwa | Wymaga iter. 2 + 5 do pełnego działania |

### Propozycja: połączenie iteracji 2 i 4

Iteracja 4 (fallback globalny) jest **bardzo mała implementacyjnie** — dodanie pola `default_thresholds` do `MetricDefinitions.php` i pola `source` w odpowiedzi API. Wdrażanie jej osobno opóźnia wartość dla użytkownika panelu.  
**Rekomendacja: połączyć iter. 4 z iter. 2.**

### Propozycja: iteracja 6 przed lub równolegle z 5

Historia zmian (iter. 6) jest **w pełni niezależna** od kontekstu czasowego (iter. 5). Można wdrożyć je równolegle albo zamienić kolejnością — iter. 6 jest mniejsza i mniej ryzykowna.

---

## 2. Zależności techniczne między iteracjami

```
Iteracja 1 (wdrożona)
    │
    ├─► Iteracja 2 — nowe metryki (MetricDefinitions, formularze)
    │       │
    │       ├─► Iteracja 3 — dwa targety w UI (zależy od stabilnego API z iter. 2)
    │       │       └─► Iteracja 7 — widok porównawczy (badge dwóch targetów)
    │       │
    │       └─► Iteracja 4 — fallback (pole `source` w API — zmiana backward-compatible)
    │
    ├─► Iteracja 5 — kontekst czasowy (nowy endpoint API + logika "ostatni aktywny")
    │       └─► Iteracja 7 — widok porównawczy (wymaga "ostatni aktywny target")
    │
    └─► Iteracja 6 — historia zmian (niezależna od 3, 4, 5)
```

### Blokujące zależności

- **Iter. 2 blokuje wszystkie pozostałe** — baza metryk musi być rozszerzona
- **Iter. 3 blokuje pełne UX iter. 7** — widok porównawczy powinien pokazywać badging z logiką dwóch targetów
- **Iter. 5 blokuje zakresowe użycie iter. 7** dla widoków rocznych

### Iteracje niezależne / równoległe

- Iter. 6 (historia) jest **w pełni niezależna** — może być wdrożona równolegle z iter. 3 lub 5

---

## 3. Ryzyka implementacyjne per iteracja

### Iteracja 2 — Nowe metryki biznesowe

#### `b2b_clients_count` — ryzyko WYSOKIE 🔴

**Problem:** W `revenue_dashboard.twig` dostępne jest pole `b2b_unique_clients_count` (liczba unikalnych klientów B2B którzy złożyli transakcję w danym miesiącu). To **nie musi** być tożsame z "oczekiwaną liczbą klientów B2B" jako metryką targetową.

**Wymagana decyzja przed implementacją:** Czy `b2b_clients_count` = `b2b_unique_clients_count` z istniejących danych sprzedażowych, czy jest to inaczej definiowana metryka wymagająca osobnego źródła danych?

#### `net_margin_pct` — ryzyko ŚREDNIE 🟡

Pole `profit_margin` jest dostępne w danych dashboardu (kolumna `r.profit_margin`), jednak przy obsłudze widoków wielomiesięcznych (iter. 5) klasyfikacja dla zakresu > 1 miesiąc wymaga agregacji ważonej, a nie prostej średniej. Logika musi być ujednolicona między JS dashboardu a logiką API.

#### `revenue`, `transactions_count`, `avg_ticket` — ryzyko NISKIE 🟢

Wszystkie pola dostępne w `computeKPIs()` w `revenue_dashboard.twig`: `totalRevenue`, `totalTxn`, `avgTicket`. Wpięcie badgingu KPI jest proste i nieryzykowne.

#### Kompatybilność wsteczna API — ryzyko NISKIE 🟢

Zmiana `active: false → true` w `MetricDefinitions::active()` automatycznie rozszerza odpowiedź API. Istniejące klienty które nie obsługują nowych kluczy metryk nie zostaną uszkodzone (dodatkowe klucze JSON są ignorowane).

**Uwaga:** Formularze admina i konsultanta muszą być zaktualizowane **synchronicznie** z włączeniem metryk w API — użytkownik zobaczyłby puste pola zanim frontend zostanie zaktualizowany.

---

### Iteracja 3 — Dwa targety jednocześnie w UI

#### Ryzyko NISKIE–ŚREDNIE 🟡

API już zwraca oba zestawy (`admin` i `consultant`) z polem `active` w `getForPanel()`. Zmiany dotyczą wyłącznie warstwy prezentacji w `panel`.

**Potencjalny problem:** Tooltip z detalami obu targetów w `revenue_dashboard.twig` wymaga przekazania surowych progów obu zestawów do JS. Aktualnie `RD._thresholds` zawiera tylko aktywne progi (wybrany winner). Należy rozszerzyć obiekt JS o `RD._targets_raw` z pełnymi danymi obu autorów.

---

### Iteracja 4 — Fallback globalny

#### Ryzyko NISKIE 🟢 (jeśli połączona z iter. 2)

Implementacja to: dodanie `default_thresholds` per metrykę do `MetricDefinitions::all()` + zwracanie `source: "default"` gdy `active === null`.

**Wymagane decyzje biznesowe przed implementacją:**

1. **Kto definiuje wartości domyślne?**  
   Rekomendacja: hard-coded w `MetricDefinitions.php` przez programistę, z możliwością zmiany tylko przez wdrożenie nowej wersji kodu. Brak potrzeby przechowywania w bazie danych.

2. **Czy admin może wyłączyć fallback per metrykę?**  
   Jeśli tak — potrzebne dodatkowe pole konfiguracyjne (np. tabela `shop_metric_settings` lub globalny config). Jeśli nie — implementacja jest prosta.

---

### Iteracja 5 — Kontekst czasowy targetu

#### Ryzyko ŚREDNIE 🟡

**"Ostatni aktywny target" — wymagana precyzyjna definicja:**

- Co zrobić gdy rok nie ma żadnego targetu?  
  Opcja A: użyć fallbacku z iter. 4 (wartości domyślne)  
  Opcja B: brak oceny, brak kolorowania  
  **Brak tej decyzji blokuje implementację.**

- Czy "ostatni aktywny" to ostatni miesiąc w roku z targetem, czy absolutnie ostatni zapis w tabeli (potencjalnie z poprzedniego roku)?

**Wydajność endpointu rocznego:**  
Nowy wariant `GET /panel/shops/{id}/targets?year=Y` (bez `month`) pobierze `WHERE id_shop = X AND year = Y` — przy indeksie `(id_shop, year, month)` wydajność będzie dobra. Przy 8 metrykach × 2 autorów × 12 miesięcy = max 192 wiersze — akceptowalne.

---

### Iteracja 6 — Historia zmian (audit log)

#### Ryzyko ŚREDNIE 🟡

**Trigger vs logika serwisowa:**

| Podejście | Zalety | Wady |
|---|---|---|
| Trigger SQL | Działa zawsze, niezależnie od warstwy | Trudny w testowaniu, poza kontrolą aplikacji, trudniej dodać `change_reason` |
| Logika serwisowa | Testowalna, obsługuje `change_reason`, widoczna w kodzie | Może pominąć bezpośrednie zapytania do bazy |

**Rekomendacja: logika serwisowa** w `ShopMetricTargetService::saveTargets()` — przed wywołaniem `upsert()` sprawdzić czy rekord istnieje, odczytać poprzednie wartości i zapisać do tabeli historii.

**Wymagane decyzje:**

- Czy historia dotyczy tylko admina, czy też konsultanta? **Rekomendacja: oboje** — tabela historii powinna być niezależna od `author_type`.
- Czy pole `change_reason` jest obowiązkowe czy opcjonalne? Jeśli obowiązkowe — formularz musi blokować zapis bez powodu.

---

### Iteracja 7 — Widok porównawczy target vs realizacja

#### Ryzyko WYSOKIE 🔴

**Główne pytanie: skąd pobierać "wartość zrealizowaną"?**

Dane sprzedażowe są ładowane przez AJAX po stronie klienta w `revenue_dashboard.twig` (endpoint `POST /ajax/analysis/revenue/monthly-data`). API nie ma endpointu zwracającego zagregowane dane sprzedażowe z oceną metryk.

**Dwie opcje architektury:**

| Opcja | Opis | Ryzyko |
|---|---|---|
| **A — Nowy endpoint API** | `GET /panel/shops/{id}/targets/evaluation` łączy dane targetów z danymi sprzedażowymi po stronie `api` | Wymaga dostępu `api` do tabel sprzedażowych — konieczna weryfikacja czy bazy są współdzielone |
| **B — Logika po stronie panelu** | Panel pobiera targety z API + dane sprzedażowe lokalnie, klasyfikuje per metrykę w PHP | Duplikacja logiki klasyfikacji (jest w `MetricDefinitions::classify()`), ale szybsza implementacja |

**Rekomendacja Opcja A** — zgodna z zasadą architektoniczną "api jest źródłem logiki biznesowej". Wymaga jednak potwierdzenia dostępu `api` do bazy danych z danymi sprzedażowymi.

**Ryzyko architektoniczne:** Jeśli `api` nie ma dostępu do tabel sprzedażowych `panel`, Opcja A wymaga albo współdzielonej bazy, albo wewnętrznego wywołania HTTP między serwisami. **Konieczna analiza architektury baz danych przed projektowaniem endpointu.**

**Osobny widok vs istniejący revenue_dashboard:**  
Rekomendacja: **osobna zakładka/sekcja w revenue_dashboard** (nie nowy widok), żeby uniknąć duplikacji filtrów zakresu dat i selektora sklepu.

---

## 4. Brakujące decyzje biznesowe i techniczne

| # | Pytanie | Kto decyduje | Wpływ |
|---|---|---|---|
| 1 | Definicja `b2b_clients_count`: klienci którzy kupili w miesiącu czy inna definicja? | Biznes | 🔴 Blokuje iter. 2 |
| 2 | Wartości domyślne fallbacku: kto je ustala, jak są przechowywane? | Biznes + Dev | 🔴 Blokuje iter. 4 |
| 3 | Czy admin może wyłączyć fallback per metrykę? | Biznes | 🟡 Zmienia zakres iter. 4 |
| 4 | "Ostatni aktywny target" gdy rok nie ma żadnego: fallback czy brak oceny? | Biznes | 🔴 Blokuje iter. 5 |
| 5 | Historia zmian: dotyczy tylko admina czy też konsultanta? | Biznes | 🟡 Zmienia zakres iter. 6 |
| 6 | Pole `change_reason` w historii: obowiązkowe czy opcjonalne? | Biznes | 🟡 Zmienia UX iter. 6 |
| 7 | Widok porównawczy (iter. 7): osobny widok czy sekcja w revenue_dashboard? | UX/Biznes | 🟡 Zmienia zakres prac frontend |
| 8 | Dostęp `api` do danych sprzedażowych panelu: wspólna baza czy inne rozwiązanie? | Architektura | 🔴 Może zablokować iter. 7 Opcja A |

---

## 5. Kandydaci do rozbicia na mniejsze zadania

### Iteracja 2 — warto rozbić na:
- **2a:** Rozszerzenie `MetricDefinitions` (active=true) + walidacja API — tylko `api`
- **2b:** Aktualizacja formularzy w `admin` i `consultant`
- **2c:** Badge oceny KPI w `panel/revenue_dashboard` dla nowych metryk (revenue, avg_ticket, transactions_count, net_margin_pct)
- **2d (po decyzji):** Weryfikacja i integracja `b2b_clients_count` — osobny podzadanie

### Iteracja 5 — warto rozbić na:
- **5a:** Nowy endpoint API bez `month` → lista targetów per miesiąc
- **5b:** Logika "ostatni aktywny target" w serwisie
- **5c:** UI w panelu — badge z adnotacją "(wg Marzec 2026)"

### Iteracja 7 — obligatoryjnie rozbić na:
- **7a:** Decyzja architektoniczna o źródle danych — **musi poprzedzać implementację**
- **7b:** Endpoint `evaluation` w API (lub alternatywna architektura)
- **7c:** UI widoku porównawczego w panelu
- **7d (opcjonalne):** Eksport CSV/PDF — jako osobna iteracja

---

## 6. Rekomendacje łączenia iteracji

| Połączenie | Uzasadnienie |
|---|---|
| **Iter. 2 + Iter. 4** | Oba dotyczą `MetricDefinitions.php` i odpowiedzi API — jedna wspólna zmiana z polem `source` i `default_thresholds` |
| **Iter. 6** razem z refaktorem `saveTargets()` z iter. 2 | `saveTargets()` i tak będzie modyfikowane przy dodawaniu nowych metryk — dodanie zapisu historii wpasowuje się naturalnie |
| **Iter. 3 + fragment iter. 7c (UI badgingu)** | Oba dotyczą prezentacji badgingu w revenue_dashboard — wspólne CSS/JS/Tooltip — zmniejsza liczbę zmian w tym samym pliku |

---

## 7. Podsumowanie rekomendowanej kolejności

```
Iter. 2a (API) ──► Iter. 2b (formularze) ──► Iter. 2c+4 (panel + fallback) ──► Iter. 3 ──► Iter. 5 ──► Iter. 7
                                                                                      │
Iter. 2d (b2b) — po decyzji, asynchronicznie ─────────────────────────────────────────┘
Iter. 6 — niezależna, równolegle z iter. 3 lub 5
```

### Priorytety krytyczne przed startem prac

1. 🔴 Potwierdzenie definicji `b2b_clients_count` — bez tego iter. 2 jest niekompletna
2. 🔴 Decyzja o dostępie `api` do danych sprzedażowych — bez tego iter. 7 nie może być zaprojektowana
3. 🔴 Określenie wartości domyślnych fallbacku — bez tego iter. 4 nie może być wdrożona
4. 🔴 Zachowanie przy braku targetu w całym roku — bez tego iter. 5 nie może być zaimplementowana

