| /** | |
| * featureCategorizer.js | |
| * ===================== | |
| * Categorizes dynamically fetched fields into logical groups (Vitals, | |
| * Lifestyle, Demographics, Medical History, etc.) for visual hierarchy. | |
| * | |
| * Uses keyword matching on field names and descriptions. Categories | |
| * are returned as an ordered Map so they render in a consistent layout. | |
| * | |
| * Category definitions: | |
| * βββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββ | |
| * β Category β Matched Keywords β | |
| * βββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββ€ | |
| * β Vitals & Signs β Age, BP, HR, BMI, Cholesterol, Chest, ST, β | |
| * β β Oldpeak, MaxHR, Pulse, Temp, Resp, O2 β | |
| * β Lifestyle β Smoke, PhysActivity, Fruits, Veggies, Alc, β | |
| * β β Exercise, Diet, Activity β | |
| * β Demographics β Sex, Age, Education, Income, Race, Region β | |
| * β Medical History β HighBP, HighChol, Stroke, Heart, Diabetes, β | |
| * β β RestingECG, FastingBS, CholCheck, DiffWalk β | |
| * β Healthcare β AnyHealthcare, NoDocbcCost, Insurance β | |
| * β Mental Health β MentHlth, PhysHlth, GenHlth, DiffWalk β | |
| * β General β (fallback for uncategorized fields) β | |
| * βββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββ | |
| */ | |
| /** | |
| * @typedef {import('./schemaFieldParser').FieldMetadata} FieldMetadata | |
| */ | |
| // ββ Category definitions ββββββββββββββββββββββββββββββββββββββββββββββ | |
| const CATEGORIES = { | |
| 'Vitals & Signs': { | |
| icon: 'Activity', | |
| keywords: [ | |
| 'age', 'restingbp', 'maxhr', 'cholesterol', 'oldpeak', | |
| 'chestpain', 'st_slope', 'fastingbs', 'bmi', 'heartrate', | |
| 'pulse', 'temperature', 'respiratory', 'o2', 'bp', 'blood pressure', | |
| ], | |
| }, | |
| Lifestyle: { | |
| icon: 'Heart', | |
| keywords: [ | |
| 'smoker', 'physactivity', 'fruits', 'veggies', | |
| 'hvyalcoholconsump', 'exercise', 'diet', 'activity', | |
| 'alcohol', 'smoking', | |
| ], | |
| }, | |
| Demographics: { | |
| icon: 'User', | |
| keywords: [ | |
| 'sex', 'education', 'income', 'race', 'region', | |
| 'geography', 'marital', | |
| ], | |
| }, | |
| 'Medical History': { | |
| icon: 'Stethoscope', | |
| keywords: [ | |
| 'highbp', 'highchol', 'stroke', 'heartdiseaseorattack', | |
| 'restingecg', 'cholcheck', 'diffwalk', 'diabetes', | |
| 'anyhealthcare', 'nodocbccost', 'kidney', 'cancer', | |
| 'condition', 'diagnosis', | |
| ], | |
| }, | |
| 'Healthcare Access': { | |
| icon: 'Pill', | |
| keywords: [ | |
| 'anyhealthcare', 'nodocbccost', 'insurance', 'doctor', | |
| 'checkup', 'medication', | |
| ], | |
| }, | |
| 'Mental Health': { | |
| icon: 'Wind', | |
| keywords: [ | |
| 'menthlth', 'physhlth', 'genhlth', 'depression', | |
| 'anxiety', 'mental', 'wellbeing', | |
| ], | |
| }, | |
| }; | |
| const CATEGORY_ORDER = [ | |
| 'Vitals & Signs', | |
| 'Medical History', | |
| 'Lifestyle', | |
| 'Demographics', | |
| 'Mental Health', | |
| 'Healthcare Access', | |
| 'General', | |
| ]; | |
| // ββ Categorization logic ββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Determine the category for a single field based on its name and description. | |
| */ | |
| function categorizeField(field) { | |
| const searchText = `${field.name} ${field.description}`.toLowerCase(); | |
| for (const [category, def] of Object.entries(CATEGORIES)) { | |
| for (const keyword of def.keywords) { | |
| // Match whole field names (case-insensitive) or partial in description | |
| if ( | |
| field.name.toLowerCase() === keyword || | |
| searchText.includes(keyword) | |
| ) { | |
| return category; | |
| } | |
| } | |
| } | |
| return 'General'; | |
| } | |
| /** | |
| * Categorize an array of FieldMetadata into an ordered Map. | |
| * | |
| * @param {FieldMetadata[]} fields - Parsed field metadata | |
| * @returns {Map<string, FieldMetadata[]>} Ordered map: category β fields[] | |
| */ | |
| export function categorizeFields(fields) { | |
| const grouped = new Map(); | |
| // Initialize all categories in order | |
| for (const cat of CATEGORY_ORDER) { | |
| grouped.set(cat, []); | |
| } | |
| // Assign each field to a category | |
| for (const field of fields) { | |
| const category = categorizeField(field); | |
| field.category = category; | |
| if (!grouped.has(category)) { | |
| grouped.set(category, []); | |
| } | |
| grouped.get(category).push(field); | |
| } | |
| // Remove empty categories (preserving order) | |
| for (const [cat, fieldsInCat] of grouped) { | |
| if (fieldsInCat.length === 0) { | |
| grouped.delete(cat); | |
| } | |
| } | |
| return grouped; | |
| } | |
| /** | |
| * Get the icon name for a category. | |
| */ | |
| export function getCategoryIcon(category) { | |
| return CATEGORIES[category]?.icon || 'ClipboardList'; | |
| } | |