update
This commit is contained in:
@@ -388,6 +388,141 @@ Jesli etykieta nie jest ustawiona:
|
||||
}
|
||||
```
|
||||
|
||||
### 4.6c Ustawienie unit pricing
|
||||
|
||||
- `action=product_unit_pricing_set`
|
||||
- Cel: zapisuje `products.unit_pricing_measure`, `products.unit_pricing_base_measure` oraz `products.unit_pricing_changed_at`
|
||||
|
||||
Parametry:
|
||||
- `api_key` (string, wymagany)
|
||||
- `offer_id` (string, wymagany)
|
||||
- `client_id` (int, wymagany)
|
||||
- `unit_pricing_measure` (string, opcjonalny) - format: `<liczba><spacja><jednostka>`, np. `30 ml`
|
||||
- `unit_pricing_base_measure` (string, opcjonalny) - format jw., np. `100 ml`
|
||||
|
||||
Uwagi:
|
||||
- oba pola musza byc podane razem albo oba puste (czyszczenie)
|
||||
- wymagane sa te same jednostki w `measure` i `base_measure`
|
||||
- API akceptuje jednostki: `ml`, `l`, `mg`, `g`, `kg`, `cl`, `m`, `cm`, `sqm`, `cbm`, `ct`, `szt`
|
||||
- dla jednostek `ml` i `g` baza musi miec wartosc `100`
|
||||
- dla jednostek `l`, `kg`, `szt`, `ct` baza musi miec wartosc `1`
|
||||
- zmiana zapisuje komentarz techniczny do `products_comments`
|
||||
|
||||
Przyklad:
|
||||
|
||||
```bash
|
||||
curl -X POST "https://example.com/api.php" \
|
||||
-d "action=product_unit_pricing_set" \
|
||||
-d "api_key=YOUR_API_KEY" \
|
||||
-d "client_id=12" \
|
||||
-d "offer_id=SKU-123" \
|
||||
-d "unit_pricing_measure=30 ml" \
|
||||
-d "unit_pricing_base_measure=100 ml"
|
||||
```
|
||||
|
||||
Przyklad odpowiedzi:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": "ok",
|
||||
"product_id": 987,
|
||||
"offer_id": "SKU-123",
|
||||
"unit_pricing_measure": "30 ml",
|
||||
"unit_pricing_base_measure": "100 ml"
|
||||
}
|
||||
```
|
||||
|
||||
Przyklad bledu walidacji:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": "error",
|
||||
"message": "Invalid unit_pricing_measure format"
|
||||
}
|
||||
```
|
||||
|
||||
HTTP status: `422`
|
||||
|
||||
### 4.6d Odczyt unit pricing
|
||||
|
||||
- `action=product_unit_pricing_get`
|
||||
- Cel: odczytuje `products.unit_pricing_measure` i `products.unit_pricing_base_measure`
|
||||
|
||||
Parametry:
|
||||
- `api_key` (string, wymagany)
|
||||
- `offer_id` (string, wymagany)
|
||||
- `client_id` (int, wymagany)
|
||||
|
||||
Przyklad:
|
||||
|
||||
```bash
|
||||
curl -G "https://example.com/api.php" \
|
||||
--data-urlencode "action=product_unit_pricing_get" \
|
||||
--data-urlencode "api_key=YOUR_API_KEY" \
|
||||
--data-urlencode "client_id=12" \
|
||||
--data-urlencode "offer_id=SKU-123"
|
||||
```
|
||||
|
||||
Przyklad odpowiedzi:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": "ok",
|
||||
"product_id": 987,
|
||||
"offer_id": "SKU-123",
|
||||
"unit_pricing_measure": "30 ml",
|
||||
"unit_pricing_base_measure": "100 ml"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.6e Lista produktow bez unit pricing
|
||||
|
||||
- `action=products_get_missing_unit_pricing`
|
||||
- Cel: zwraca produkty bez kompletu `unit_pricing_*`, z priorytetem wg klikniec
|
||||
|
||||
Parametry:
|
||||
- `api_key` (string, wymagany)
|
||||
- `client_id` (int, wymagany)
|
||||
- `top` (int, opcjonalny, domyslnie 50, max 200)
|
||||
- `category_filter` (string, opcjonalny) - gdy podany, filtruje po `google_product_category LIKE %...%`
|
||||
|
||||
Uwagi:
|
||||
- bez `category_filter` endpoint ogranicza liste do kategorii beauty/cosmetics (heurystyka LIKE)
|
||||
- sortowanie: `clicks_all_time DESC`, potem `clicks_30 DESC`
|
||||
|
||||
Przyklad:
|
||||
|
||||
```bash
|
||||
curl -G "https://example.com/api.php" \
|
||||
--data-urlencode "action=products_get_missing_unit_pricing" \
|
||||
--data-urlencode "api_key=YOUR_API_KEY" \
|
||||
--data-urlencode "client_id=12" \
|
||||
--data-urlencode "top=20"
|
||||
```
|
||||
|
||||
Przyklad odpowiedzi:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": "ok",
|
||||
"client_id": 12,
|
||||
"count": 2,
|
||||
"products": [
|
||||
{
|
||||
"product_id": 987,
|
||||
"offer_id": "SKU-123",
|
||||
"default_name": "Serum C 30 ml",
|
||||
"custom_title": "Serum C 30 ml",
|
||||
"google_product_category": "Health & Beauty > Skin Care",
|
||||
"unit_pricing_measure": "",
|
||||
"unit_pricing_base_measure": "",
|
||||
"clicks_30": 45,
|
||||
"clicks_all_time": 312
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.7 Odczyt minimalnego ROAS produktu
|
||||
|
||||
- `action=product_min_roas_get`
|
||||
|
||||
@@ -91,6 +91,9 @@ CREATE TABLE IF NOT EXISTS `products` (
|
||||
`name` varchar(255) NOT NULL DEFAULT '0',
|
||||
`min_roas` int(11) DEFAULT NULL,
|
||||
`custom_label_4` varchar(255) DEFAULT NULL,
|
||||
`unit_pricing_measure` varchar(64) DEFAULT NULL,
|
||||
`unit_pricing_base_measure` varchar(64) DEFAULT NULL,
|
||||
`unit_pricing_changed_at` datetime DEFAULT NULL,
|
||||
`custom_label_3` varchar(255) DEFAULT NULL,
|
||||
`title` varchar(255) DEFAULT NULL,
|
||||
`description` text DEFAULT NULL,
|
||||
|
||||
177
docs/unit-price.md
Normal file
177
docs/unit-price.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user