feat(frontend): map SmartAdvisor rules dynamically from backend API with medical rationale rendering

This commit is contained in:
parsa aghaei 2026-07-26 19:24:50 +03:30
parent 2ec77f0e1a
commit b1027903d6
3 changed files with 51 additions and 31 deletions

View File

@ -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
} }
} }

View File

@ -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"
} }

View File

@ -97,6 +97,23 @@ 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;
let matchedReason: string | null = null;
// First, check backend rules if provided
if (rules && rules.length > 0) {
const foundRule = rules.find((r: any) => {
const petMatch = !r.targetPetType || r.targetPetType === "هر دو" || r.targetPetType === type;
const conditionMatch = combinedConditions.some(c => r.condition?.includes(c) || c.includes(r.condition));
return petMatch && conditionMatch;
});
if (foundRule && foundRule.product) {
matchedProd = foundRule.product;
matchedReason = foundRule.reason;
}
}
// Second, query dynamic products API
if (!matchedProd) {
try { try {
const liveRes = await productService.getProducts({ petType: type, limit: 50 }); const liveRes = await productService.getProducts({ petType: type, limit: 50 });
if (liveRes.data && liveRes.data.length > 0) { if (liveRes.data && liveRes.data.length > 0) {
@ -112,12 +129,16 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
} catch (e) { } catch (e) {
console.error("[SmartAdvisor] Dynamic product fetch failed, using fallback", 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) {