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).

Files changed (3) hide show
  1. prompts.py +1 -1
  2. test_stock_regression.py +5 -4
  3. 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 sayma: Luna VE Terra ikisi de 19 dedi, gercek 25 (11 Agu olcumu)
102
- ("caddebostanda kac tane marlin var", True, "ADET HÜKMÜ", "sayma: kod sayar, LLM sayamaz"),
103
- ("kac tane marlin var", True, "ADET HÜKMÜ", "sayma, magazasiz"),
104
- ("alsancakta kac adet madone var", True, "ADET HÜKMÜ", "sayma, farkli magaza+model"),
 
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, qty in sku_map.get(sku, []):
709
- if magaza and wh.upper() != magaza:
710
- continue
711
- if beden and not _variant_matches_size(v, beden):
712
- continue
713
- if renk and renk not in normalize(_format_variant_label(v)):
714
- continue
715
- toplam += qty
716
- dagilim[wh] = dagilim.get(wh, 0) + qty
717
- satirlar.append(f"• {m['name']} {_format_variant_label(v)} — {wh}: {qty} adet")
718
- if toplam:
719
- nerede = f"{magaza} mağazasında" if magaza else "tüm mağazalarda"
720
- dag = ", ".join(f"{k}: {v}" for k, v in sorted(dagilim.items(), key=lambda x: -x[1]))
721
- out = [f"[ADET HÜKMÜ KESİN VERİ] {nerede} TOPLAM {toplam} ADET var. "
722
- f"Dağılım: {dag}. Bu sayı sistem hesabı KENDİN SAYMA:"]
 
723
  out.extend(satirlar[:30])
724
  return _seal(out)
725
- return _seal([f"[ADET HÜKMÜ — KESİN VERİ] {'bu kriterlerle' if not magaza else magaza + ' mağazasında'} "
726
- f"stok YOK (0 adet). Sayı UYDURMA."])
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 SAYMA: 'caddebostanda kac tane marlin var'. KOD sayar 11 Agu
1059
- # olcumunde Luna ve Terra ikisi de 19 dedi, gercek 24'tu.
 
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, qty in sku_map.get(sku, []):
1072
- if count_store and wh.upper() != count_store:
1073
- continue
1074
- if size and not _variant_matches_size(v, size):
1075
- continue
1076
- if color and color not in normalize(_format_variant_label(v)):
1077
- continue
1078
- toplam += qty
1079
- magaza_dagilimi[wh] = magaza_dagilimi.get(wh, 0) + qty
1080
- satirlar.append(f"• {m['name']} {_format_variant_label(v)} — {wh}: {qty} adet")
1081
- if toplam:
1082
- nerede = f"{count_store} mağazasında" if count_store else "tüm mağazalarda"
1083
- dagilim = ", ".join(f"{k}: {v}" for k, v in sorted(
1084
- magaza_dagilimi.items(), key=lambda x: -x[1]))
1085
- out = [f"[ADET HÜKMÜ KESİN VERİ] {nerede} TOPLAM {toplam} ADET var. "
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"[ADET HÜKMÜ — KESİN VERİ] {count_store} mağazasında bu üründen "
1094
- f"stok YOK (0 adet). Sayı UYDURMA."])
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)