feat(frontend): map SmartAdvisor rules dynamically from backend API with medical rationale rendering
This commit is contained in:
parent
2ec77f0e1a
commit
b1027903d6
@ -89,7 +89,7 @@
|
|||||||
"architectural_layer": "presentation_ui",
|
"architectural_layer": "presentation_ui",
|
||||||
"assigned_role": "06_dev_frontend",
|
"assigned_role": "06_dev_frontend",
|
||||||
"priority": "HIGH",
|
"priority": "HIGH",
|
||||||
"status": "pending",
|
"status": "completed",
|
||||||
"max_files_allowed": 3,
|
"max_files_allowed": 3,
|
||||||
"estimated_minutes": 25,
|
"estimated_minutes": 25,
|
||||||
"dependency_task_ids": ["EPIC-02-TASK-01"],
|
"dependency_task_ids": ["EPIC-02-TASK-01"],
|
||||||
@ -98,8 +98,8 @@
|
|||||||
"محاسبه دوز دقیق دارویی بر اساس وزن بدن پت"
|
"محاسبه دوز دقیق دارویی بر اساس وزن بدن پت"
|
||||||
],
|
],
|
||||||
"sub_steps": [
|
"sub_steps": [
|
||||||
{ "index": 1, "name": "connect_smart_advisor_api", "description": "Fetch SmartAdvisorRules from backend", "status": "pending" },
|
{ "index": 1, "name": "connect_smart_advisor_api", "description": "Fetch SmartAdvisorRules from backend", "status": "done" },
|
||||||
{ "index": 2, "name": "render_medical_rationale", "description": "Render medical rationale and dosage calculation", "status": "pending" }
|
{ "index": 2, "name": "render_medical_rationale", "description": "Render medical rationale and dosage calculation", "status": "done" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -226,10 +226,10 @@
|
|||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"total": 11,
|
"total": 11,
|
||||||
"completed": 4,
|
"completed": 5,
|
||||||
"in_progress": 1,
|
"in_progress": 1,
|
||||||
"pending": 6,
|
"pending": 5,
|
||||||
"generated_at": "2026-07-26T19:40:00Z",
|
"generated_at": "2026-07-26T19:45:00Z",
|
||||||
"decomposition_pass": 4
|
"decomposition_pass": 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,12 +7,12 @@
|
|||||||
"status": "IN_PROGRESS",
|
"status": "IN_PROGRESS",
|
||||||
"checkpoint": {
|
"checkpoint": {
|
||||||
"active_agent": "06_dev_frontend",
|
"active_agent": "06_dev_frontend",
|
||||||
"current_ticket_id": "EPIC-02-TASK-02",
|
"current_ticket_id": "EPIC-02-TASK-03",
|
||||||
"sub_step": {
|
"sub_step": {
|
||||||
"index": 1,
|
"index": 1,
|
||||||
"total": 2,
|
"total": 2,
|
||||||
"name": "connect_smart_advisor_api",
|
"name": "wire_b2b_application_modal",
|
||||||
"description": "SmartAdvisor component dynamic rule mapping"
|
"description": "Wire B2BPortal.tsx wholesale registration modal to NestJS wholesale API"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"review_phase": {
|
"review_phase": {
|
||||||
@ -29,16 +29,15 @@
|
|||||||
"findings_dir": ".ai_agency/specs/reviews/"
|
"findings_dir": ".ai_agency/specs/reviews/"
|
||||||
},
|
},
|
||||||
"resume_context": {
|
"resume_context": {
|
||||||
"last_completed_action": "EPIC-02-TASK-01 completed: Hero.tsx and VetGallery.tsx connected to live NestJS APIs with dynamic testimonials & banners.",
|
"last_completed_action": "EPIC-02-TASK-02 completed: SmartAdvisor rules mapped dynamically from NestJS backend API with medical rationale rendering.",
|
||||||
"next_action": "Execute EPIC-02-TASK-02: SmartAdvisor dynamic rules and real product recommendation mapping.",
|
"next_action": "Execute EPIC-02-TASK-03: B2B Wholesale Portal registration and bulk order submission API integration.",
|
||||||
"files_modified_this_session": [
|
"files_modified_this_session": [
|
||||||
"frontend/application/components/VetGallery.tsx",
|
"frontend/application/components/SmartAdvisor.tsx",
|
||||||
"frontend/application/app/HomeClient.tsx",
|
|
||||||
".ai_agency/memory/backlog.json",
|
".ai_agency/memory/backlog.json",
|
||||||
".ai_agency/memory/state.json"
|
".ai_agency/memory/state.json"
|
||||||
],
|
],
|
||||||
"files_pending": [],
|
"files_pending": [],
|
||||||
"notes": "Verified Next.js production build cleanly. Moving to EPIC-02-TASK-02."
|
"notes": "Verified Next.js production build cleanly (4.7s). Moving to EPIC-02-TASK-03."
|
||||||
},
|
},
|
||||||
"tech_stack": {
|
"tech_stack": {
|
||||||
"language": "TypeScript",
|
"language": "TypeScript",
|
||||||
@ -57,11 +56,11 @@
|
|||||||
"max_retry_attempts": 3,
|
"max_retry_attempts": 3,
|
||||||
"blocking_reason": null,
|
"blocking_reason": null,
|
||||||
"agent_visit_counts": {
|
"agent_visit_counts": {
|
||||||
"06_dev_frontend": 6
|
"06_dev_frontend": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"agent_call_log": [
|
"agent_call_log": [
|
||||||
{ "agent": "06_dev_frontend", "result": "EPIC-02-TASK-01_completed", "timestamp": "2026-07-26T19:40:00Z" }
|
{ "agent": "06_dev_frontend", "result": "EPIC-02-TASK-02_completed", "timestamp": "2026-07-26T19:45:00Z" }
|
||||||
],
|
],
|
||||||
"last_updated": "2026-07-26T19:40:00Z"
|
"last_updated": "2026-07-26T19:45:00Z"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,27 +97,48 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
|
|||||||
const combinedConditions = Array.from(new Set([...currentSymptoms, ...medicalConditions]));
|
const combinedConditions = Array.from(new Set([...currentSymptoms, ...medicalConditions]));
|
||||||
|
|
||||||
let matchedProd: any = null;
|
let matchedProd: any = null;
|
||||||
try {
|
let matchedReason: string | null = null;
|
||||||
const liveRes = await productService.getProducts({ petType: type, limit: 50 });
|
|
||||||
if (liveRes.data && liveRes.data.length > 0) {
|
// First, check backend rules if provided
|
||||||
matchedProd = liveRes.data.find(p => {
|
if (rules && rules.length > 0) {
|
||||||
const matchPet = p.suitableFor === "هر دو" || p.suitableFor === type;
|
const foundRule = rules.find((r: any) => {
|
||||||
const matchSymptom = p.symptoms?.some((s: string) => combinedConditions.some(c => s.includes(c) || c.includes(s)));
|
const petMatch = !r.targetPetType || r.targetPetType === "هر دو" || r.targetPetType === type;
|
||||||
return matchPet && matchSymptom;
|
const conditionMatch = combinedConditions.some(c => r.condition?.includes(c) || c.includes(r.condition));
|
||||||
});
|
return petMatch && conditionMatch;
|
||||||
if (!matchedProd) {
|
});
|
||||||
matchedProd = liveRes.data[0];
|
if (foundRule && foundRule.product) {
|
||||||
}
|
matchedProd = foundRule.product;
|
||||||
|
matchedReason = foundRule.reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second, query dynamic products API
|
||||||
|
if (!matchedProd) {
|
||||||
|
try {
|
||||||
|
const liveRes = await productService.getProducts({ petType: type, limit: 50 });
|
||||||
|
if (liveRes.data && liveRes.data.length > 0) {
|
||||||
|
matchedProd = liveRes.data.find(p => {
|
||||||
|
const matchPet = p.suitableFor === "هر دو" || p.suitableFor === type;
|
||||||
|
const matchSymptom = p.symptoms?.some((s: string) => combinedConditions.some(c => s.includes(c) || c.includes(s)));
|
||||||
|
return matchPet && matchSymptom;
|
||||||
|
});
|
||||||
|
if (!matchedProd) {
|
||||||
|
matchedProd = liveRes.data[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[SmartAdvisor] Dynamic product fetch failed, using fallback", e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error("[SmartAdvisor] Dynamic product fetch failed, using fallback", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matchedProd) {
|
if (!matchedProd) {
|
||||||
matchedProd = PRODUCTS.find(p => p.suitableFor === "هر دو" || p.suitableFor === type) || PRODUCTS[0];
|
matchedProd = PRODUCTS.find(p => p.suitableFor === "هر دو" || p.suitableFor === type) || PRODUCTS[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
setRecommendedProduct(matchedProd);
|
setRecommendedProduct({
|
||||||
|
...matchedProd,
|
||||||
|
medicalReason: matchedReason || matchedProd.scientificTagline || matchedProd.shortDescription
|
||||||
|
});
|
||||||
|
|
||||||
// Calculate dosage logic if method exists
|
// Calculate dosage logic if method exists
|
||||||
if (matchedProd && matchedProd.calculateDosage) {
|
if (matchedProd && matchedProd.calculateDosage) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user