Files
adsPRO/docs/unit-price.md
2026-05-05 21:46:26 +02:00

178 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Unit Pricing — specyfikacja rozszerzenia adsPRO
Cel: dodać do adsPRO obsługę pól `unit_pricing_measure` i `unit_pricing_base_measure` (Google Merchant Center) tak, żeby moduł 14 audytu Google Ads (`analiza-produkty`) mógł je zapisywać z poziomu agenta AI, analogicznie do `title` / `google_product_category` / `custom_label_*`.
Kontekst: Google Merchant Center wymaga `unit_pricing_measure` dla kategorii kosmetyki/Health & Beauty. Brak tego pola = 894/3455 SKU u klienta `aruba.rzeszow.pl` z warning `missing_potentially_required_attribute` (servability=unaffected, ale ranking ↓ i brak wyświetlania ceny per ml/g).
---
## 1. Model danych (DB)
### 1.1 Tabela `products` — nowe kolumny
```sql
ALTER TABLE products
ADD COLUMN unit_pricing_measure VARCHAR(64) NULL, -- np. "30 ml", "100 g", "1 szt"
ADD COLUMN unit_pricing_base_measure VARCHAR(64) NULL, -- np. "100 ml", "1 kg", "1 szt"
ADD COLUMN unit_pricing_changed_at DATETIME NULL;
```
**Walidacja wartości** (po stronie API i UI):
- Format: `<liczba><spacja><jednostka>` — regex `^\d+(\.\d+)?\s+(ml|l|g|kg|szt|cm|m)$`
- Dozwolone jednostki (Google specyfikacja): `ml`, `l`, `mg`, `g`, `kg`, `cl`, `m`, `cm`, `sqm`, `cbm`, `ct` (count = sztuki — Google używa `ct` lub osobnego pola; w PL przyjąć `szt`).
- `unit_pricing_base_measure` musi być w **tej samej jednostce** co `measure` (np. measure=`30 ml` → base=`100 ml`, NIE `1 l`).
- Standardowe wartości `base`: dla ml/g → `100`, dla l/kg → `1`, dla szt → `1`.
### 1.2 Migracja
`migrations/YYYY-MM-DD_add_unit_pricing.sql` — patrz folder `migrations/` projektu.
---
## 2. API publiczne (rozszerzenie `api.php`)
### 2.1 `action=product_unit_pricing_set` — zapis
Wzór: kopia `product_custom_label_4_set` z linii ~336 `api.php`.
Parametry:
- `api_key` (string, wymagany)
- `client_id` (int, wymagany)
- `offer_id` (string, wymagany)
- `unit_pricing_measure` (string, opcjonalny — pusta wartość czyści pole)
- `unit_pricing_base_measure` (string, opcjonalny — j.w.)
Logika:
- Walidacja regex (jw.) — jeśli zła forma → 422 `Invalid unit_pricing_measure format`.
- Walidacja zgodności jednostek (measure unit == base unit) → 422 jeśli niezgodne.
- Update `products.unit_pricing_measure` + `unit_pricing_base_measure` + `unit_pricing_changed_at = NOW()`.
- Wpis w `products_comments`: `'Zmiana unit_pricing na <m> / <b> (API)'`.
- HTTP 200 + JSON `{result: ok, product_id, offer_id, unit_pricing_measure, unit_pricing_base_measure}`.
Przykład:
```bash
curl -X POST https://adspro.projectpro.pl/api.php \
-d action=product_unit_pricing_set \
-d api_key=YOUR_KEY \
-d client_id=3 \
-d offer_id=7792 \
-d unit_pricing_measure="30 ml" \
-d unit_pricing_base_measure="100 ml"
```
### 2.2 `action=product_unit_pricing_get` — odczyt
Wzór: kopia `product_custom_label_4_get`.
Parametry: `api_key`, `client_id`, `offer_id`.
Odpowiedź:
```json
{
"result": "ok",
"product_id": 987,
"offer_id": "7792",
"unit_pricing_measure": "30 ml",
"unit_pricing_base_measure": "100 ml"
}
```
### 2.3 `action=products_get_missing_unit_pricing` — listing kandydatów (opcjonalne)
Cel: agent może pobrać top N produktów które wymagają unit_pricing (filtr per kategoria) — zamiast skanować per-product.
Parametry: `api_key`, `client_id`, opcjonalnie `top` (default 50), `category_filter` (np. `Skin Care`).
Logika: SELECT z `products` WHERE `unit_pricing_measure IS NULL` AND (kategoria w listy beauty/cosmetics) ORDER BY `clicks_all_time DESC` LIMIT N.
Odpowiedź: `{result: ok, count: N, products: [{offer_id, default_name, custom_title, google_product_category, clicks_30, ...}]}`.
---
## 3. Custom feed (zapis do GMC)
Lokalizacja: `feeds/` w projekcie. Generator feedu produktowego musi dodać:
```xml
<g:unit_pricing_measure>30 ml</g:unit_pricing_measure>
<g:unit_pricing_base_measure>100 ml</g:unit_pricing_base_measure>
```
**tylko** gdy oba pola niepuste w bazie. Pominąć tag jeśli null/empty (Google nie akceptuje pustych wartości).
---
## 4. Heurystyka AI (po stronie agenta — NIE w adsPRO)
Agent (np. `set_product_unit_pricing.py` w projekcie `D:\google ads\scripts\actions\`) wyciąga `measure` z tytułu produktu:
| Wzorzec w tytule | measure | base |
|---|---|---|
| `... 30ml` lub `... 30 ml` | `30 ml` | `100 ml` |
| `... 1000ml` lub `... 1l` | `1000 ml` | `100 ml` |
| `... 100g` | `100 g` | `100 g` |
| `... 1kg` | `1 kg` | `1 kg` |
| `... 10szt` | `10 szt` | `1 szt` |
Edge cases (do flagowania jako "wątpliwości" — agent pyta usera):
- Multi-component (zestaw 3 płynów × 200ml) — brak jednoznacznej miary
- Sprzęt (fotel, taboret) — N/A jednostka
- Nieznana pojemność (np. tylko `nr 3`)
---
## 5. Acceptance criteria
- [ ] Migracja DB dodaje 3 kolumny + indeks na `unit_pricing_measure IS NULL`
- [ ] `api.php` ma 3 nowe endpointy (`_set`, `_get`, `_get_missing` — ostatni opcjonalny)
- [ ] Walidacja formatu regex + jednostek (testy unit)
- [ ] Generator feedu zapisuje `<g:unit_pricing_measure>` + `<g:unit_pricing_base_measure>` gdy oba pola wypełnione
- [ ] `product_unit_pricing_set` zapisuje wpis do `products_comments`
- [ ] `product_unit_pricing_set` aktualizuje `unit_pricing_changed_at`
- [ ] Endpoint `_get_missing` filtruje po kategorii (beauty/cosmetics) i sortuje po klikach
- [ ] Skrypt `set_product_unit_pricing.py` po stronie agenta (`D:\google ads\scripts\actions\`) — wzorzec z `set_product_custom_label.py`
- [ ] Skrypt `apply_unit_pricing_from_previews.py` — batch zapis z tabel preview w raportach M14 (`klienci/<dom>/.analysis/reports/analiza-produkty-*.md` — etap 1.5)
---
## 6. Pilotaż
Klient pilotażowy: `aruba.rzeszow.pl` (`client_id=3`).
**Pierwsza partia (5 SKU z preview M14 z 2026-05-05):**
| product_id | unit_pricing_measure | unit_pricing_base_measure |
|---|---|---|
| 7792 | 30 ml | 100 ml |
| 305 | 10 ml | 100 ml |
| 35 | 60 ml | 100 ml |
| 281 | 100 ml | 100 ml |
| 2288 | 1000 ml | 100 ml |
Sukces pilotażu = 5/5 SKU widocznych w GMC z polami unit_pricing po 24h od pierwszego push feedu.
---
## 7. Dokumentacja publiczna
Po wdrożeniu: dopisać sekcję 4.X do `docs/api-public-product-management.md` (analogicznie do sekcji 4.7 `product_custom_label_4_set`):
- 4.X.1 `product_unit_pricing_set`
- 4.X.2 `product_unit_pricing_get`
- 4.X.3 (opc.) `products_get_missing_unit_pricing`
---
## 8. TODO procesowe (po wdrożeniu)
1. Backfill agentem AI wszystkich istniejących produktów z brakiem unit_pricing — heurystyka z sekcji 4 + flag wątpliwości na liście do user review.
2. Skrypt `apply_unit_pricing_from_previews.py` w projekcie agenta — batch zapis tabel preview z M14 raportów (etap 1.5) dla wszystkich klientów.
3. Monitoring w `analiza-feed` (M2): sprawdzanie liczby SKU bez unit_pricing — cel <5% per klient w branżach beauty/cosmetics.
---
## 9. Powiązane
- Kontekst dla agenta: `D:\google ads\.claude\commands\analiza-produkty.md` — sekcja "Etap 1.5: Unit pricing (preview)".
- Pierwsza preview tabela: `D:\google ads\klienci\aruba.rzeszow.pl\.analysis\reports\analiza-produkty-2026-05-05.md`.