Trek Turkey commited on
Commit ·
57c12f8
1
Parent(s): 021bdc2
feat(gizlilik): stok ADEDI musteriyle paylasilmaz — isletme karari
Browse files'Kac tane marlin var' tarzi sorulara artik SAYI verilmez:
- Iki lookup yolunda da (metin + yapilandirilmis) adet sorusu 'STOKTA
MEVCUT + hangi magazalarda' seklinde cevaplanir, hukumde 'ADET BILGISI
VERME' talimati; kesin adet icin magazaya yonlendirilir.
- prompts 0a: LLM hicbir cevapta adet soyleyemez ('3 adet var', 'son 1
tane' YASAK) — eski/yan yollardan sizinti olursa da son savunma.
Gerekce: adet bilgisi ticari istihbarat — rakip botu periyodik sorgulayip
stok hareketini izleyebilir; musterinin karari icin 'var/nerede/bedenim'
yeterli. Sizinti testi: iki yolda da regex 'N adet|TOPLAM N' = YOK.
Regresyon: 65/65 (3 senaryo yeni beklentiye guncellendi).
- prompts.py +1 -1
- test_stock_regression.py +5 -4
- warehouse_lookup.py +44 -44
prompts.py
CHANGED
|
@@ -109,7 +109,7 @@ SYSTEM_PROMPTS = [
|
|
| 109 |
{
|
| 110 |
"role": "system",
|
| 111 |
"category": "recommendation_focus",
|
| 112 |
-
"content": "ÖNERİ NETLİĞİ KURALI (ÇOK ÖNEMLİ — HER STOK CEVABINDA UYGULA):\n0) '[STOK HÜKMÜ — KESİN VERİ]' satırı sistemin depodan aldığı KESİN gerçektir. 'STOKTA VAR ✅' diyorsa ASLA 'stok görünmüyor/doğrulanmış kayıt yok' yazma; 'STOKTA YOK ❌' diyorsa stok varmış gibi yazma. Hükümle çelişen HER cümle yanlış bilgidir. Modelin yanındaki parantezli etiket ((ELEKTRİKLİ), (ŞEHİR/HİBRİT), (DAĞ), (YOL)) sistemin kesin sınıflandırmasıdır — 'bu elektrikli mi/şehir bisikleti mi' diye şüphe etme.\n0b) FİYAT VE MODEL ADI SADECE listeden alınır. Listede fiyatı olmayan ürünün fiyatını hafızandan YAZMA, listede olmayan modeli ANMA. Fiyat sorulur da listede yoksa 'mağazadan teyit alalım' de.\n0c) Müşteri itiraz ederse ('yok mu', 'değil mi', 'emin misin') listedeki veriyi TEKRAR kontrol et; veri seni doğruluyorsa nazikçe verinin doğru olduğunu söyle. Sırf itiraz edildi diye fikir değiştirip UYDURMA — bu en kötü hatadır.\n1) UYAN ürünlerin HEPSİNİ, TÜM renk/varyant ve mağazalarıyla listele. Stok listesinde görünen hiçbir uyan varyantı ATLAMA.\n2) UYMAYAN modellerin ADINI BİLE ANMA. 'X modelinde bu beden yok', 'Y uygun değil' gibi cümleler YASAK — müşteri o modeli sormadı.\n3) Mesaja yalnızca önerdiğin ürünlerin linkini koy (en uygun 1-2 tane). İlk link WhatsApp kartı olur; anlatmadığın ürünün linki yanlış kart çıkarır.\n\nSATIR DÜZENİ (birden çok model listelerken):\n• HER MODEL AYRI SATIRDA başlar, aralarına BOŞ SATIR koy. Tek paragrafa sıkıştırma — okunmaz oluyor.\n• Format örneği:\n'Evet, S beden stokta! 🚴\n\n• Madone SL 6 Gen 8 — kırmızı, Caddebostan — 385.000 TL\n\n• Madone SL 7 Gen 8 — mat lila (Alsancak), kristal beyaz-gri (Bahçeköy, Caddebostan) — 490.000 TL\n\nLink: https://...'\n\nÖRNEK — 'XS Marlin var mı?' (stokta: Marlin 4 XS siyah + su yeşili; Marlin 5/6/7'de XS yok):\n✅ DOĞRU: 'Evet! Marlin 4'ün XS bedeni siyah ve su yeşili renklerde Alsancak ve Bahçeköy mağazalarımızda stokta. Fiyatı 53.000 TL.'\n❌ YANLIŞ: 'Marlin 4 XS siyah stokta. Marlin 6'da XS görünmüyor.' (su yeşilini atladı + sorulmayan Marlin 6'yı andı)"
|
| 113 |
},
|
| 114 |
|
| 115 |
# 14. KAMPANYA VE SERVİS HİZMETLERİ
|
|
|
|
| 109 |
{
|
| 110 |
"role": "system",
|
| 111 |
"category": "recommendation_focus",
|
| 112 |
+
"content": "ÖNERİ NETLİĞİ KURALI (ÇOK ÖNEMLİ — HER STOK CEVABINDA UYGULA):\n0) '[STOK HÜKMÜ — KESİN VERİ]' satırı sistemin depodan aldığı KESİN gerçektir. 'STOKTA VAR ✅' diyorsa ASLA 'stok görünmüyor/doğrulanmış kayıt yok' yazma; 'STOKTA YOK ❌' diyorsa stok varmış gibi yazma. Hükümle çelişen HER cümle yanlış bilgidir. Modelin yanındaki parantezli etiket ((ELEKTRİKLİ), (ŞEHİR/HİBRİT), (DAĞ), (YOL)) sistemin kesin sınıflandırmasıdır — 'bu elektrikli mi/şehir bisikleti mi' diye şüphe etme.\n0a) STOK ADEDİ GİZLİDİR: müşteriye ASLA kaç adet ürün olduğunu söyleme ('3 adet var', 'son 1 tane' YASAK). 'Kaç tane var' sorulursa cevap: 'stokta mevcut' + hangi mağazalarda olduğu + 'kesin adet için mağazamızı arayabilirsiniz'. Bu ticari bilgidir, rakipler botu sorgulayabilir.\n0b) FİYAT VE MODEL ADI SADECE listeden alınır. Listede fiyatı olmayan ürünün fiyatını hafızandan YAZMA, listede olmayan modeli ANMA. Fiyat sorulur da listede yoksa 'mağazadan teyit alalım' de.\n0c) Müşteri itiraz ederse ('yok mu', 'değil mi', 'emin misin') listedeki veriyi TEKRAR kontrol et; veri seni doğruluyorsa nazikçe verinin doğru olduğunu söyle. Sırf itiraz edildi diye fikir değiştirip UYDURMA — bu en kötü hatadır.\n1) UYAN ürünlerin HEPSİNİ, TÜM renk/varyant ve mağazalarıyla listele. Stok listesinde görünen hiçbir uyan varyantı ATLAMA.\n2) UYMAYAN modellerin ADINI BİLE ANMA. 'X modelinde bu beden yok', 'Y uygun değil' gibi cümleler YASAK — müşteri o modeli sormadı.\n3) Mesaja yalnızca önerdiğin ürünlerin linkini koy (en uygun 1-2 tane). İlk link WhatsApp kartı olur; anlatmadığın ürünün linki yanlış kart çıkarır.\n\nSATIR DÜZENİ (birden çok model listelerken):\n• HER MODEL AYRI SATIRDA başlar, aralarına BOŞ SATIR koy. Tek paragrafa sıkıştırma — okunmaz oluyor.\n• Format örneği:\n'Evet, S beden stokta! 🚴\n\n• Madone SL 6 Gen 8 — kırmızı, Caddebostan — 385.000 TL\n\n• Madone SL 7 Gen 8 — mat lila (Alsancak), kristal beyaz-gri (Bahçeköy, Caddebostan) — 490.000 TL\n\nLink: https://...'\n\nÖRNEK — 'XS Marlin var mı?' (stokta: Marlin 4 XS siyah + su yeşili; Marlin 5/6/7'de XS yok):\n✅ DOĞRU: 'Evet! Marlin 4'ün XS bedeni siyah ve su yeşili renklerde Alsancak ve Bahçeköy mağazalarımızda stokta. Fiyatı 53.000 TL.'\n❌ YANLIŞ: 'Marlin 4 XS siyah stokta. Marlin 6'da XS görünmüyor.' (su yeşilini atladı + sorulmayan Marlin 6'yı andı)"
|
| 113 |
},
|
| 114 |
|
| 115 |
# 14. KAMPANYA VE SERVİS HİZMETLERİ
|
test_stock_regression.py
CHANGED
|
@@ -98,10 +98,11 @@ CASES = [
|
|
| 98 |
("hangi modeller karbon", False, None, "malzeme -> LLM'e birakilir"),
|
| 99 |
("su yesili xs marlin hangi magazada var", True, "XS beden", "COK KELIMELI renk (A/B tur 5 bug)"),
|
| 100 |
("su yesili marlin", True, None, "cok kelimeli renk, bedensiz"),
|
| 101 |
-
# Adet
|
| 102 |
-
|
| 103 |
-
("kac tane marlin var",
|
| 104 |
-
("
|
|
|
|
| 105 |
("specialized tarmac var mi", False, None, "rakip marka -> MISS"),
|
| 106 |
("giant tcr var mi", False, None, "rakip marka (Giant)"),
|
| 107 |
("canyon aeroad fiyati", False, None, "rakip marka (Canyon)"),
|
|
|
|
| 98 |
("hangi modeller karbon", False, None, "malzeme -> LLM'e birakilir"),
|
| 99 |
("su yesili xs marlin hangi magazada var", True, "XS beden", "COK KELIMELI renk (A/B tur 5 bug)"),
|
| 100 |
("su yesili marlin", True, None, "cok kelimeli renk, bedensiz"),
|
| 101 |
+
# Adet sorusu — GIZLILIK (11 Agu isletme karari): sayi PAYLASILMAZ,
|
| 102 |
+
# 'stokta mevcut + magazalar' denir. Hukumde 'ADET BILGISI VERME' olmali.
|
| 103 |
+
("caddebostanda kac tane marlin var", True, "ADET BİLGİSİ VERME", "adet gizli: magazali"),
|
| 104 |
+
("kac tane marlin var", True, "ADET BİLGİSİ VERME", "adet gizli: magazasiz"),
|
| 105 |
+
("alsancakta kac adet madone var", True, "ADET BİLGİSİ VERME", "adet gizli: farkli magaza"),
|
| 106 |
("specialized tarmac var mi", False, None, "rakip marka -> MISS"),
|
| 107 |
("giant tcr var mi", False, None, "rakip marka (Giant)"),
|
| 108 |
("canyon aeroad fiyati", False, None, "rakip marka (Canyon)"),
|
warehouse_lookup.py
CHANGED
|
@@ -695,35 +695,39 @@ def lookup_stock_structured(p: dict) -> list[str] | None:
|
|
| 695 |
cat_suffix = f" ({label})" if label else ""
|
| 696 |
|
| 697 |
# --- ADET SAYMA ---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 698 |
if adet_sorusu:
|
| 699 |
-
toplam = 0
|
| 700 |
-
dagilim: dict[str, int] = {}
|
| 701 |
satirlar: list[str] = []
|
|
|
|
| 702 |
for m in family:
|
| 703 |
link = m.get("link") or ""
|
| 704 |
for v in (idx.variants_of(link) if link else [m]):
|
| 705 |
sku = (v.get("sku") or "").strip()
|
| 706 |
if not sku:
|
| 707 |
continue
|
| 708 |
-
for wh,
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
if
|
| 719 |
-
nerede = f"{magaza} mağazasında" if magaza else "
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
f"
|
|
|
|
| 723 |
out.extend(satirlar[:30])
|
| 724 |
return _seal(out)
|
| 725 |
-
return _seal([f"[
|
| 726 |
-
f"
|
| 727 |
|
| 728 |
# --- FIYAT FILTRESI ---
|
| 729 |
if fiyat_min is not None or fiyat_max is not None:
|
|
@@ -1055,43 +1059,39 @@ def lookup_stock_for_query(query: str) -> list[str] | None:
|
|
| 1055 |
logger.info(f"[wh_lookup] malzeme sorgusu -> LLM'e birakiliyor: {query!r}")
|
| 1056 |
return None
|
| 1057 |
|
| 1058 |
-
# ADET
|
| 1059 |
-
#
|
|
|
|
| 1060 |
is_count, count_store = _detect_count_query(query)
|
| 1061 |
if is_count and family:
|
| 1062 |
-
toplam = 0
|
| 1063 |
-
magaza_dagilimi: dict[str, int] = {}
|
| 1064 |
satirlar: list[str] = []
|
|
|
|
| 1065 |
for m in family:
|
| 1066 |
link = m.get("link") or ""
|
| 1067 |
for v in (idx.variants_of(link) if link else [m]):
|
| 1068 |
sku = (v.get("sku") or "").strip()
|
| 1069 |
if not sku:
|
| 1070 |
continue
|
| 1071 |
-
for wh,
|
| 1072 |
-
|
| 1073 |
-
|
| 1074 |
-
|
| 1075 |
-
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
| 1080 |
-
|
| 1081 |
-
if
|
| 1082 |
-
nerede = f"{count_store} mağazasında" if count_store else "
|
| 1083 |
-
|
| 1084 |
-
|
| 1085 |
-
|
| 1086 |
-
f"Mağaza dağılımı: {dagilim}. Bu sayı sistem tarafından hesaplandı — "
|
| 1087 |
-
f"KENDİN SAYMA, bu rakamı aynen kullan:"]
|
| 1088 |
out.extend(satirlar[:30])
|
| 1089 |
-
if len(satirlar) > 30:
|
| 1090 |
-
out.append(f"... (+{len(satirlar)-30} satır daha, toplam yukarıda)")
|
| 1091 |
return _seal(out)
|
| 1092 |
if count_store:
|
| 1093 |
-
return _seal([f"[
|
| 1094 |
-
f"stok YOK
|
| 1095 |
|
| 1096 |
# EN UCUZ / EN PAHALI: siralamali sorgular ('en ucuz bisiklet hangisi')
|
| 1097 |
superlative = _detect_superlative(query)
|
|
|
|
| 695 |
cat_suffix = f" ({label})" if label else ""
|
| 696 |
|
| 697 |
# --- ADET SAYMA ---
|
| 698 |
+
# ADET SORUSU — GIZLILIK KURALI (11 Agu, isletme karari): stok ADEDI
|
| 699 |
+
# musteriyle PAYLASILMAZ. Rakip botu sorgulayip stok hareketini
|
| 700 |
+
# izleyebilir; musterinin gercek ihtiyaci 'var mi, nerede, benim
|
| 701 |
+
# bedenimde mi' — adetsiz cevaplanir.
|
| 702 |
if adet_sorusu:
|
|
|
|
|
|
|
| 703 |
satirlar: list[str] = []
|
| 704 |
+
var_mi = False
|
| 705 |
for m in family:
|
| 706 |
link = m.get("link") or ""
|
| 707 |
for v in (idx.variants_of(link) if link else [m]):
|
| 708 |
sku = (v.get("sku") or "").strip()
|
| 709 |
if not sku:
|
| 710 |
continue
|
| 711 |
+
whs = [wh for wh, _q in sku_map.get(sku, [])
|
| 712 |
+
if not (magaza and wh.upper() != magaza)]
|
| 713 |
+
if not whs:
|
| 714 |
+
continue
|
| 715 |
+
if beden and not _variant_matches_size(v, beden):
|
| 716 |
+
continue
|
| 717 |
+
if renk and renk not in normalize(_format_variant_label(v)):
|
| 718 |
+
continue
|
| 719 |
+
var_mi = True
|
| 720 |
+
satirlar.append(f"• {m['name']} {_format_variant_label(v)}: {', '.join(whs)}")
|
| 721 |
+
if var_mi:
|
| 722 |
+
nerede = f"{magaza} mağazasında" if magaza else "mağazalarımızda"
|
| 723 |
+
out = [f"[STOK HÜKMÜ — KESİN VERİ] Ürün {nerede} STOKTA MEVCUT ✅. "
|
| 724 |
+
f"⚠️ ADET BİLGİSİ VERME: müşteri sayı sorsa bile toplam/adet SÖYLEME, "
|
| 725 |
+
f"'stokta mevcut' de ve hangi mağazalarda olduğunu belirt. "
|
| 726 |
+
f"Kesin adet için mağazaya yönlendir:"]
|
| 727 |
out.extend(satirlar[:30])
|
| 728 |
return _seal(out)
|
| 729 |
+
return _seal([f"[STOK HÜKMÜ — KESİN VERİ] Bu kriterlerle stok YOK ❌. "
|
| 730 |
+
f"Sayı/adet UYDURMA."])
|
| 731 |
|
| 732 |
# --- FIYAT FILTRESI ---
|
| 733 |
if fiyat_min is not None or fiyat_max is not None:
|
|
|
|
| 1059 |
logger.info(f"[wh_lookup] malzeme sorgusu -> LLM'e birakiliyor: {query!r}")
|
| 1060 |
return None
|
| 1061 |
|
| 1062 |
+
# ADET SORUSU — GIZLILIK KURALI (11 Agu, isletme karari): stok ADEDI
|
| 1063 |
+
# musteriyle PAYLASILMAZ (rakip istihbarati). 'Var/yok + hangi magaza'
|
| 1064 |
+
# ile cevaplanir, kesin adet icin magazaya yonlendirilir.
|
| 1065 |
is_count, count_store = _detect_count_query(query)
|
| 1066 |
if is_count and family:
|
|
|
|
|
|
|
| 1067 |
satirlar: list[str] = []
|
| 1068 |
+
var_mi = False
|
| 1069 |
for m in family:
|
| 1070 |
link = m.get("link") or ""
|
| 1071 |
for v in (idx.variants_of(link) if link else [m]):
|
| 1072 |
sku = (v.get("sku") or "").strip()
|
| 1073 |
if not sku:
|
| 1074 |
continue
|
| 1075 |
+
whs = [wh for wh, _q in sku_map.get(sku, [])
|
| 1076 |
+
if not (count_store and wh.upper() != count_store)]
|
| 1077 |
+
if not whs:
|
| 1078 |
+
continue
|
| 1079 |
+
if size and not _variant_matches_size(v, size):
|
| 1080 |
+
continue
|
| 1081 |
+
if color and color not in normalize(_format_variant_label(v)):
|
| 1082 |
+
continue
|
| 1083 |
+
var_mi = True
|
| 1084 |
+
satirlar.append(f"• {m['name']} {_format_variant_label(v)}: {', '.join(whs)}")
|
| 1085 |
+
if var_mi:
|
| 1086 |
+
nerede = f"{count_store} mağazasında" if count_store else "mağazalarımızda"
|
| 1087 |
+
out = [f"[STOK HÜKMÜ — KESİN VERİ] Ürün {nerede} STOKTA MEVCUT ✅. "
|
| 1088 |
+
f"⚠️ ADET BİLGİSİ VERME: müşteri sayı sorsa bile toplam/adet SÖYLEME, "
|
| 1089 |
+
f"'stokta mevcut' de ve mağazaları belirt. Kesin adet için mağazaya yönlendir:"]
|
|
|
|
|
|
|
| 1090 |
out.extend(satirlar[:30])
|
|
|
|
|
|
|
| 1091 |
return _seal(out)
|
| 1092 |
if count_store:
|
| 1093 |
+
return _seal([f"[STOK HÜKMÜ — KESİN VERİ] {count_store} mağazasında bu üründen "
|
| 1094 |
+
f"stok YOK ❌. Sayı/adet UYDURMA."])
|
| 1095 |
|
| 1096 |
# EN UCUZ / EN PAHALI: siralamali sorgular ('en ucuz bisiklet hangisi')
|
| 1097 |
superlative = _detect_superlative(query)
|