@@ -25,6 +25,17 @@ export const usePredictionStore = defineStore('prediction', () => {
2525 currentInput . value = inputData
2626 currentPrediction . value = result
2727
28+ // Automatically fetch food recommendations based on the input data
29+ try {
30+ // fetchRecommendations will populate `recommendations` in this store
31+ await fetchRecommendations ( inputData , 6 )
32+ // Attach recommendations to the prediction result for easy access in the UI
33+ currentPrediction . value . recommendedFoods = recommendations . value || [ ]
34+ } catch ( recErr ) {
35+ // Non-fatal: log and continue
36+ console . warn ( 'Failed to fetch recommendations:' , recErr )
37+ }
38+
2839 // Add to history (limit to last 10)
2940 predictionHistory . value . unshift ( {
3041 input : inputData ,
@@ -88,14 +99,116 @@ export const usePredictionStore = defineStore('prediction', () => {
8899
89100 const vector = keys . map ( ( k ) => ( inputData && inputData [ k ] != null ? Number ( inputData [ k ] ) : 0 ) )
90101
91- const result = await apiService . getRecommendations ( { vector, top_k } )
92- if ( result && result . success ) {
93- recommendations . value = result . items || [ ]
102+ // Try ensemble-based recommendations first
103+ let recResult = null
104+ try {
105+ recResult = await apiService . getRecommendations ( { vector, top_k : Math . max ( top_k , 20 ) } )
106+ } catch ( e ) {
107+ // Continue to fallback logic below
108+ recResult = null
109+ }
110+
111+ // Fetch local foods to enrich metadata and enable region/price filtering
112+ let localFoods = [ ]
113+ try {
114+ const lf = await apiService . getLocalFoods ( )
115+ if ( Array . isArray ( lf ) ) localFoods = lf
116+ } catch ( e ) {
117+ // Non-fatal; we can proceed without local enrichment
118+ console . warn ( 'Could not fetch local foods for enrichment:' , e )
119+ }
120+
121+ const regionMap = [ 'central' , 'western' , 'eastern' , 'northern' ]
122+ const preferredRegion = ( inputData && typeof inputData . region_encoded === 'number' ) ? regionMap [ inputData . region_encoded ] : null
123+ const budget = inputData && ( inputData . estimated_cost_ugx || inputData . budget )
124+
125+ // Helper to match recommended item to a local food entry
126+ const buildLocalMap = ( arr ) => {
127+ const byName = { }
128+ const byId = { }
129+ arr . forEach ( ( f ) => {
130+ if ( ! f ) return
131+ const name = ( f . name || f . title || '' ) . toString ( ) . toLowerCase ( )
132+ if ( name ) byName [ name ] = f
133+ if ( f . id != null ) byId [ String ( f . id ) ] = f
134+ } )
135+ return { byName, byId }
136+ }
137+
138+ const { byName, byId } = buildLocalMap ( localFoods )
139+
140+ const enrichItem = ( item ) => {
141+ const meta = ( item && item . meta ) || { }
142+ // try match by meta name or id
143+ const nameKey = ( meta . name || meta . title || item . id || '' ) . toString ( ) . toLowerCase ( )
144+ const localMatch = byId [ String ( item . id ) ] || byName [ nameKey ]
145+ const mergedMeta = Object . assign ( { } , meta , ( localMatch || { } ) )
146+ return Object . assign ( { } , item , { meta : mergedMeta } )
147+ }
148+
149+ let items = [ ]
150+ if ( recResult && recResult . success && Array . isArray ( recResult . items ) && recResult . items . length ) {
151+ // Enrich recommendations
152+ items = recResult . items . map ( enrichItem )
153+
154+ // Filter out unavailable items if we have available flags
155+ const hasAvailability = items . some ( ( it ) => it . meta && typeof it . meta . available !== 'undefined' )
156+ if ( hasAvailability ) {
157+ const filtered = items . filter ( ( it ) => it . meta && it . meta . available !== false )
158+ if ( filtered . length ) items = filtered
159+ }
160+
161+ // Sort: prefer same region, then by score desc, then by price asc
162+ items . sort ( ( a , b ) => {
163+ const aRegion = ( a . meta && ( a . meta . region || a . meta . region_name || a . meta . region_str ) || '' ) . toString ( ) . toLowerCase ( )
164+ const bRegion = ( b . meta && ( b . meta . region || b . meta . region_name || b . meta . region_str ) || '' ) . toString ( ) . toLowerCase ( )
165+ const aSame = preferredRegion && aRegion === preferredRegion ? 1 : 0
166+ const bSame = preferredRegion && bRegion === preferredRegion ? 1 : 0
167+ if ( bSame !== aSame ) return bSame - aSame
168+ const scoreDiff = ( b . score || 0 ) - ( a . score || 0 )
169+ if ( scoreDiff !== 0 ) return scoreDiff
170+ const aPrice = ( a . meta && ( a . meta . pricePerKg || a . meta . price_per_kg || a . meta . price ) ) || Number . MAX_SAFE_INTEGER
171+ const bPrice = ( b . meta && ( b . meta . pricePerKg || b . meta . price_per_kg || b . meta . price ) ) || Number . MAX_SAFE_INTEGER
172+ return aPrice - bPrice
173+ } )
174+
175+ // If budget provided, prefer items with price <= budget (heuristic)
176+ if ( budget ) {
177+ const affordable = items . filter ( ( it ) => {
178+ const price = ( it . meta && ( it . meta . pricePerKg || it . meta . price_per_kg || it . meta . price ) ) || null
179+ if ( price == null ) return true
180+ // Treat budget as daily budget in UGX; fall back to allowing items cheaper than budget*2
181+ return Number ( price ) <= Number ( budget ) * 2
182+ } )
183+ if ( affordable . length ) items = affordable
184+ }
185+
186+ // Limit to requested top_k
187+ items = items . slice ( 0 , top_k )
188+ recommendations . value = items
94189 return recommendations . value
95- } else {
96- recError . value = ( result && result . error ) || 'Recommendation failed'
97- return [ ]
98190 }
191+
192+ // Fallback: choose local foods by region/availability/price when no embedding results
193+ if ( localFoods && localFoods . length ) {
194+ let picks = [ ...localFoods ]
195+ // filter by availability if present
196+ if ( picks . some ( ( f ) => typeof f . available !== 'undefined' ) ) {
197+ picks = picks . filter ( ( f ) => f . available !== false )
198+ }
199+ if ( preferredRegion ) {
200+ const inRegion = picks . filter ( ( f ) => ( f . region || '' ) . toString ( ) . toLowerCase ( ) === preferredRegion )
201+ if ( inRegion . length ) picks = inRegion
202+ }
203+ // sort by price asc if present
204+ picks . sort ( ( a , b ) => ( ( a . pricePerKg || Number . MAX_SAFE_INTEGER ) - ( b . pricePerKg || Number . MAX_SAFE_INTEGER ) ) )
205+ picks = picks . slice ( 0 , top_k ) . map ( ( f ) => ( { id : f . name || f . id , score : 0 , meta : f } ) )
206+ recommendations . value = picks
207+ return recommendations . value
208+ }
209+
210+ recError . value = 'No recommendations available'
211+ return [ ]
99212 } catch ( err ) {
100213 recError . value = err . message || String ( err )
101214 throw err
0 commit comments