Commit 20098dc6 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

feat: add di_hoc occasion mapping and reason text sanitization for kid products

parent 0e3f8b6a
...@@ -459,6 +459,7 @@ ...@@ -459,6 +459,7 @@
} }
.occ-badge-hang_ngay { background: #eef1f8; color: #3b5998; } .occ-badge-hang_ngay { background: #eef1f8; color: #3b5998; }
.occ-badge-di_lam { background: #ecfdf5; color: #16a34a; } .occ-badge-di_lam { background: #ecfdf5; color: #16a34a; }
.occ-badge-di_hoc { background: #e0e7ff; color: #4f46e5; }
.occ-badge-di_choi { background: #fef3c7; color: #d97706; } .occ-badge-di_choi { background: #fef3c7; color: #d97706; }
.occ-badge-the_thao { background: #fce7f3; color: #db2777; } .occ-badge-the_thao { background: #fce7f3; color: #db2777; }
.occ-badge-mac_nha { background: #f3e8ff; color: #7c3aed; } .occ-badge-mac_nha { background: #f3e8ff; color: #7c3aed; }
...@@ -540,6 +541,14 @@ ...@@ -540,6 +541,14 @@
let currentSimilarItems = []; let currentSimilarItems = [];
let currentOutfits = []; let currentOutfits = [];
function isKidProduct() {
if (!currentProduct) return false;
const age = (currentProduct.age_by_product || "").toLowerCase();
const gender = (currentProduct.gender_by_product || "").toLowerCase();
const name = (currentProduct.product_name || "").toLowerCase();
return age === 'kid' || age === 'baby' || gender === 'be_trai' || gender === 'be_gai' || name.includes("trẻ em") || name.includes("bé trai") || name.includes("bé gái") || name.includes("kid");
}
// ═══ IMAGE URL FORMATTER ═══ // ═══ IMAGE URL FORMATTER ═══
function formatImageUrl(url) { function formatImageUrl(url) {
if (!url) return ''; if (!url) return '';
...@@ -1101,7 +1110,12 @@ ...@@ -1101,7 +1110,12 @@
currentProduct = data.product; currentProduct = data.product;
window.currentProduct = currentProduct; window.currentProduct = currentProduct;
currentSimilarItems = currentProduct.parsed_similar_items || []; currentSimilarItems = currentProduct.parsed_similar_items || [];
currentOutfits = currentProduct.parsed_outfit_recommendations || []; currentOutfits = (currentProduct.parsed_outfit_recommendations || []).map(o => {
if (o.occasion === 'di_lam' && isKidProduct()) {
o.occasion = 'di_hoc';
}
return o;
});
// Update sidebar state badges to match current database state // Update sidebar state badges to match current database state
const hasSim = currentSimilarItems.length > 0; const hasSim = currentSimilarItems.length > 0;
...@@ -1158,7 +1172,12 @@ ...@@ -1158,7 +1172,12 @@
currentProduct = data.product; currentProduct = data.product;
window.currentProduct = currentProduct; window.currentProduct = currentProduct;
currentSimilarItems = currentProduct.parsed_similar_items || []; currentSimilarItems = currentProduct.parsed_similar_items || [];
currentOutfits = currentProduct.parsed_outfit_recommendations || []; currentOutfits = (currentProduct.parsed_outfit_recommendations || []).map(o => {
if (o.occasion === 'di_lam' && isKidProduct()) {
o.occasion = 'di_hoc';
}
return o;
});
// Cập nhật trạng thái sidebar dựa trên dữ liệu mới của biến thể màu // Cập nhật trạng thái sidebar dựa trên dữ liệu mới của biến thể màu
const baseCode = currentProduct.magento_ref_code || currentProduct.internal_ref_code; const baseCode = currentProduct.magento_ref_code || currentProduct.internal_ref_code;
...@@ -1493,6 +1512,7 @@ ...@@ -1493,6 +1512,7 @@
const OCC_LABELS = { const OCC_LABELS = {
'hang_ngay': 'Hàng ngày', 'hang_ngay': 'Hàng ngày',
'di_lam': 'Đi làm', 'di_lam': 'Đi làm',
'di_hoc': 'Đi học',
'di_choi': 'Đi chơi', 'di_choi': 'Đi chơi',
'di_tiec': 'Đi tiệc', 'di_tiec': 'Đi tiệc',
'the_thao': 'Thể thao', 'the_thao': 'Thể thao',
...@@ -1624,6 +1644,7 @@ ...@@ -1624,6 +1644,7 @@
color_name: p.product_color_name || '', color_name: p.product_color_name || '',
match_product_name: p.product_name, match_product_name: p.product_name,
role: "bottom", role: "bottom",
occasion: activeOccFilter || (isKidProduct() ? "di_hoc" : "hang_ngay"),
reason: `Phối cùng ${p.product_name} giúp tôn lên phong cách trẻ trung và thoải mái khi diện hàng ngày.`, reason: `Phối cùng ${p.product_name} giúp tôn lên phong cách trẻ trung và thoải mái khi diện hàng ngày.`,
product_image_url_thumbnail: p.product_image_url_thumbnail product_image_url_thumbnail: p.product_image_url_thumbnail
}); });
...@@ -1738,6 +1759,7 @@ ...@@ -1738,6 +1759,7 @@
color_name: item.color_name || '', color_name: item.color_name || '',
match_product_name: item.match_product_name, match_product_name: item.match_product_name,
role: item.role || 'bottom', role: item.role || 'bottom',
occasion: item.occasion || (isKidProduct() ? 'di_hoc' : 'hang_ngay'),
reason: item.reason || '' reason: item.reason || ''
})); }));
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
"occasions": [ "occasions": [
"hang_ngay", "hang_ngay",
"di_lam", "di_lam",
"di_hoc",
"di_choi", "di_choi",
"di_tiec", "di_tiec",
"the_thao", "the_thao",
...@@ -11,6 +12,7 @@ ...@@ -11,6 +12,7 @@
"occasion_labels": { "occasion_labels": {
"hang_ngay": "Hàng ngày", "hang_ngay": "Hàng ngày",
"di_lam": "Đi làm công sở", "di_lam": "Đi làm công sở",
"di_hoc": "Đi học",
"di_choi": "Đi chơi / dạo phố", "di_choi": "Đi chơi / dạo phố",
"di_tiec": "Đi tiệc", "di_tiec": "Đi tiệc",
"the_thao": "Thể thao", "the_thao": "Thể thao",
...@@ -31,6 +33,15 @@ ...@@ -31,6 +33,15 @@
"work", "work",
"office" "office"
], ],
"di_hoc": [
"đi học",
"trường học",
"lớp học",
"school",
"class",
"học tập",
"vui học"
],
"di_choi": [ "di_choi": [
"đi chơi", "đi chơi",
"dạo phố", "dạo phố",
......
...@@ -120,11 +120,19 @@ class StylistEngine: ...@@ -120,11 +120,19 @@ class StylistEngine:
anchor_cat = source.get("product_line", "") anchor_cat = source.get("product_line", "")
gender = source.get("gender", "") gender = source.get("gender", "")
is_kid = (
source.get("age_group", "").lower() == "kid"
or source.get("gender", "").lower() in ("boy", "girl", "be_trai", "be_gai")
or any(k in source.get("name", "").lower() for k in ["trẻ em", "bé trai", "bé gái", "cho bé", "unisex trẻ em"])
)
# Fetch allowed mappings from DB # Fetch allowed mappings from DB
db_rules = self._fetch_db_rules(anchor_cat, gender) db_rules = self._fetch_db_rules(anchor_cat, gender)
allowed_by_occ = {} allowed_by_occ = {}
for r in db_rules: for r in db_rules:
occ = r["occ"] occ = r["occ"]
if is_kid and occ == "di_lam":
occ = "di_hoc"
tgt = r["target_cat"].lower() tgt = r["target_cat"].lower()
if occ not in allowed_by_occ: if occ not in allowed_by_occ:
allowed_by_occ[occ] = set() allowed_by_occ[occ] = set()
...@@ -159,6 +167,16 @@ class StylistEngine: ...@@ -159,6 +167,16 @@ class StylistEngine:
if score < rules.get("min_score", 35): if score < rules.get("min_score", 35):
continue continue
reason = self._build_reason(source, target, occ, score) reason = self._build_reason(source, target, occ, score)
if is_kid:
reason = (reason
.replace("đi làm công sở", "đi học")
.replace("đi làm", "đi học")
.replace("công sở", "học tập và vui chơi")
.replace("văn phòng", "trường học")
.replace("làm việc", "học tập")
.replace("lịch sự dịp đi làm", "lịch sự dịp đi học")
.replace("phù hợp dịp đi học công sở", "phù hợp dịp đi học")
)
buckets[occ][role].append({ buckets[occ][role].append({
"code": target["code"], "code": target["code"],
"name": target["name"], "name": target["name"],
...@@ -202,6 +220,12 @@ class StylistEngine: ...@@ -202,6 +220,12 @@ class StylistEngine:
anchor_cat = source.get("product_line", "") anchor_cat = source.get("product_line", "")
gender = source.get("gender", "") gender = source.get("gender", "")
is_kid = (
source.get("age_group", "").lower() == "kid"
or source.get("gender", "").lower() in ("boy", "girl", "be_trai", "be_gai")
or any(k in source.get("name", "").lower() for k in ["trẻ em", "bé trai", "bé gái", "cho bé", "unisex trẻ em"])
)
# 1. Fetch Rules from DB: list of {occ, role, target_cat, ai_reason} # 1. Fetch Rules from DB: list of {occ, role, target_cat, ai_reason}
db_rules = self._fetch_db_rules(anchor_cat, gender) db_rules = self._fetch_db_rules(anchor_cat, gender)
if not db_rules: if not db_rules:
...@@ -211,9 +235,12 @@ class StylistEngine: ...@@ -211,9 +235,12 @@ class StylistEngine:
# Group by occasion for easier lookup # Group by occasion for easier lookup
rules_by_occ: dict[str, list[dict]] = {} rules_by_occ: dict[str, list[dict]] = {}
for r in db_rules: for r in db_rules:
if r["occ"] not in rules_by_occ: occ = r["occ"]
rules_by_occ[r["occ"]] = [] if is_kid and occ == "di_lam":
rules_by_occ[r["occ"]].append(r) occ = "di_hoc"
if occ not in rules_by_occ:
rules_by_occ[occ] = []
rules_by_occ[occ].append(r)
max_items = self.rules.get("role_max_items", {}) max_items = self.rules.get("role_max_items", {})
buckets: dict[str, dict[str, list[dict]]] = {} buckets: dict[str, dict[str, list[dict]]] = {}
...@@ -245,6 +272,18 @@ class StylistEngine: ...@@ -245,6 +272,18 @@ class StylistEngine:
db_reason = r["ai_reason"] or f"Theo định hướng ({occ})" db_reason = r["ai_reason"] or f"Theo định hướng ({occ})"
combined_reason = f"{algo_reason} | {db_reason}" combined_reason = f"{algo_reason} | {db_reason}"
if is_kid:
# Sanitize reason text to replace work/office terms with school terms
combined_reason = (combined_reason
.replace("đi làm công sở", "đi học")
.replace("đi làm", "đi học")
.replace("công sở", "học tập và vui chơi")
.replace("văn phòng", "trường học")
.replace("làm việc", "học tập")
.replace("lịch sự dịp đi làm", "lịch sự dịp đi học")
.replace("phù hợp dịp đi học công sở", "phù hợp dịp đi học")
)
breakdown = self._score_breakdown(source, target, occ) breakdown = self._score_breakdown(source, target, occ)
if occ not in buckets: if occ not in buckets:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment