1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.plugins.stock.modules.recommendation.service;
35
36 import fr.paris.lutece.plugins.stock.modules.recommendation.business.AvailableProductsDAO;
37 import fr.paris.lutece.plugins.stock.modules.recommendation.business.Recommendation;
38 import fr.paris.lutece.plugins.stock.modules.recommendation.business.RecommendationDAO;
39 import fr.paris.lutece.plugins.stock.modules.recommendation.business.RecommendedProduct;
40 import fr.paris.lutece.plugins.stock.modules.recommendation.business.StockPurchaseDAO;
41 import fr.paris.lutece.plugins.stock.modules.recommendation.business.UserItem;
42 import fr.paris.lutece.portal.service.plugin.Plugin;
43 import fr.paris.lutece.portal.service.plugin.PluginService;
44 import fr.paris.lutece.portal.service.util.AppLogService;
45 import fr.paris.lutece.portal.service.util.AppPathService;
46 import fr.paris.lutece.portal.service.util.AppPropertiesService;
47 import java.io.File;
48 import java.io.FileNotFoundException;
49 import java.io.IOException;
50 import java.sql.Timestamp;
51 import java.util.ArrayList;
52 import java.util.Date;
53 import java.util.List;
54 import org.apache.mahout.cf.taste.common.NoSuchUserException;
55 import org.apache.mahout.cf.taste.common.TasteException;
56 import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
57 import org.apache.mahout.cf.taste.impl.model.file.FileIDMigrator;
58 import org.apache.mahout.cf.taste.impl.neighborhood.ThresholdUserNeighborhood;
59 import org.apache.mahout.cf.taste.impl.recommender.GenericBooleanPrefUserBasedRecommender;
60 import org.apache.mahout.cf.taste.impl.similarity.LogLikelihoodSimilarity;
61 import org.apache.mahout.cf.taste.model.DataModel;
62 import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
63 import org.apache.mahout.cf.taste.recommender.RecommendedItem;
64 import org.apache.mahout.cf.taste.recommender.UserBasedRecommender;
65 import org.apache.mahout.cf.taste.similarity.UserSimilarity;
66
67
68
69
70 public final class StockRecommendationService
71 {
72 private static final String PLUGIN_NAME = "stock-recommendation";
73 private static final String PROPERTY_ID_MIGRATOR_FILE_PATH = "stock-recommendation.idMigratorFilePath";
74 private static final String PROPERTY_DATA_FILE_PATH = "stock-recommendation.dataFilePath";
75 private static final String PROPERTY_THRESHOLD = "stock-recommendation.recommender.threshold";
76 private static final String PROPERTY_COUNT = "stock-recommendation.recommender.count";
77 private static final String DEFAULT_THRESHOLD = "0.1";
78 private static final int DEFAULT_COUNT = 6;
79
80 private static Plugin _plugin = PluginService.getPlugin( PLUGIN_NAME );
81 private static StockPurchaseDAO _daoPurchase = new StockPurchaseDAO( );
82 private static RecommendationDAO _daoRecommendation = new RecommendationDAO( );
83 private static AvailableProductsDAO _daoProducts = new AvailableProductsDAO( );
84 private static FileIDMigrator _migrator;
85 private static StockRecommendationService _singleton;
86 private static PurchaseDataWriter _writer;
87 private static UserBasedRecommender _recommender;
88 private static int _nCount = AppPropertiesService.getPropertyInt( PROPERTY_COUNT, DEFAULT_COUNT );
89 private static List<Integer> _listAvailableProducts;
90
91
92 private StockRecommendationService( )
93 {
94 }
95
96
97
98
99
100
101 public static StockRecommendationService instance( )
102 {
103 String strDataFilePath = AppPropertiesService.getProperty( PROPERTY_DATA_FILE_PATH );
104 File dataFile = new File( AppPathService.getAbsolutePathFromRelativePath( strDataFilePath ) );
105 if ( _singleton == null || dataFile.length() <= 0L )
106 {
107 synchronized( StockRecommendationService.class )
108 {
109 _singleton = new StockRecommendationService( );
110 String strIdMigratorFilePath = AppPropertiesService.getProperty( PROPERTY_ID_MIGRATOR_FILE_PATH );
111 File idMigratorFile = new File( AppPathService.getAbsolutePathFromRelativePath( strIdMigratorFilePath ) );
112
113 try
114 {
115 _migrator = new FileIDMigrator( idMigratorFile );
116
117 _listAvailableProducts = buildAvailableProductsList( );
118 AppLogService.info( "stock-recommendation : " + _listAvailableProducts.size( ) + " products found that can be ordered." );
119 AppLogService.info( "stock-recommendation : creating data file with current purchases." );
120 _writer = new FilePurchaseDataWriter( dataFile );
121 extractPurchases( );
122 AppLogService.info( "stock-recommendation : initialize the recommender with data." );
123 if( dataFile.length() > 0L ) {
124 _recommender = createRecommender(dataFile);
125 }
126 }
127 catch( FileNotFoundException ex )
128 {
129 AppLogService.error( "stock-recommendation : Error creating file " + strIdMigratorFilePath + " " + ex.getMessage( ), ex );
130 }
131 catch( IOException ex )
132 {
133 AppLogService.error( "stock-recommendation : Error creating file " + strIdMigratorFilePath + " " + ex.getMessage( ), ex );
134 }
135 }
136 }
137 return _singleton;
138 }
139
140
141
142
143 public static void extractPurchases( )
144 {
145 _writer.reset( );
146 List<UserItem> list = _daoPurchase.selectUserItemsList( _plugin );
147 for ( UserItem ui : list )
148 {
149 long lUserID = _migrator.toLongID( ui.getUserName( ) );
150 _writer.write( lUserID, ui.getItemId( ) );
151
152 }
153 AppLogService.info( "stock-recommendation : retieved purchases count : " + list.size( ) );
154 _writer.close( );
155 }
156
157
158
159
160
161
162
163
164
165
166
167
168 public List<RecommendedItem> getRecommendedItems( String strUserName ) throws NoSuchUserException, TasteException
169 {
170 long lUserId = _migrator.toLongID( strUserName );
171 List<RecommendedItem> recommendedItems = new ArrayList<>();
172 if(_recommender != null ) {
173 recommendedItems = _recommender.recommend( lUserId, _nCount );
174 }
175 return recommendedItems;
176 }
177
178
179
180
181
182
183
184
185
186
187
188
189 public List<RecommendedProduct> getRecommendedProducts( String strUserName ) throws NoSuchUserException, TasteException
190 {
191
192 List<RecommendedProduct> list = loadFromDatabase( strUserName );
193
194 if ( list != null )
195 {
196 return list;
197 }
198
199
200 list = getRecommendedProductsList( strUserName );
201
202
203 writeUserRecommendations( strUserName, list );
204
205 return list;
206
207 }
208
209
210
211
212
213
214
215
216
217
218
219
220 private List<RecommendedProduct> getRecommendedProductsList( String strUserName ) throws NoSuchUserException, TasteException
221 {
222 List<RecommendedProduct> list = new ArrayList<>( );
223
224 for ( RecommendedItem item : getRecommendedItems( strUserName ) )
225 {
226 int nItemId = (int) item.getItemID( );
227 if ( _listAvailableProducts.contains( nItemId ) )
228 {
229 RecommendedProduct product = new RecommendedProduct( );
230 product.setProductId( nItemId );
231 product.setScore( item.getValue( ) );
232 _daoProducts.getProductInfos( product, _plugin );
233 list.add( product );
234 AppLogService.info( "Product recommended for user " + strUserName + " : " + product );
235 }
236 }
237 return list;
238 }
239
240
241
242
243
244
245
246
247 private List<RecommendedProduct> loadFromDatabase( String strUsername )
248 {
249 List<Recommendation> listRecommandations = _daoRecommendation.selectRecommendationsByUser( strUsername, _plugin );
250
251 if ( listRecommandations == null || listRecommandations.isEmpty( ) )
252 {
253 return null;
254 }
255
256 List<RecommendedProduct> list = new ArrayList<>( );
257 for ( Recommendation recommendation : listRecommandations )
258 {
259 RecommendedProduct product = new RecommendedProduct( );
260 product.setProductId( recommendation.getIdProduct( ) );
261 product.setScore( recommendation.getScore( ) );
262 _daoProducts.getProductInfos( product, _plugin );
263 list.add( product );
264 }
265 return list;
266
267 }
268
269
270
271
272
273
274
275
276
277
278 private static UserBasedRecommender createRecommender( File fileData ) throws IOException
279 {
280 DataModel model = new FileDataModel( fileData );
281 UserSimilarity similarity = new LogLikelihoodSimilarity( model );
282
283 String strThreshold = AppPropertiesService.getProperty( PROPERTY_THRESHOLD, DEFAULT_THRESHOLD );
284 double threshold = Double.valueOf( strThreshold );
285 UserNeighborhood neighborhood = new ThresholdUserNeighborhood( threshold, similarity, model );
286 return new GenericBooleanPrefUserBasedRecommender( model, neighborhood, similarity );
287
288 }
289
290
291
292
293
294
295 private static List<Integer> buildAvailableProductsList( )
296 {
297 Timestamp time = new Timestamp( ( new Date( ) ).getTime( ) );
298 List<Integer> listAvailableProducts = _daoProducts.selectAvailableProductsIdList( time, _plugin );
299 return listAvailableProducts;
300 }
301
302
303
304
305
306
307
308 public void buildRecommendations( StringBuilder sbLogs )
309 {
310 List<String> listUsers = _daoPurchase.selectUsersList( _plugin );
311 int nUsersCount = listUsers.size( );
312 int nRecommendationCount = 0;
313 AppLogService.info( "Starting building recommendations for " + nUsersCount + " users ..." );
314 for ( String strUsername : listUsers )
315 {
316 List<RecommendedProduct> listProduct;
317 try
318 {
319 listProduct = getRecommendedProductsList( strUsername );
320 nRecommendationCount += writeUserRecommendations( strUsername, listProduct );
321 }
322 catch( TasteException ex )
323 {
324 AppLogService.error( "stock-recommendation : Error building recommendations : " + ex.getMessage( ), ex );
325 }
326 }
327
328 double ratio = (double) nRecommendationCount / (double) nUsersCount;
329 sbLogs.append( "Recommendation builder\n " ).append( "number of users : " ).append( nUsersCount ).append( '\n' )
330 .append( "number of recommendations : " ).append( nRecommendationCount ).append( '\n' ).append( "ratio per user : " ).append( ratio )
331 .append( '\n' );
332 }
333
334
335
336
337
338
339
340
341
342
343 private int writeUserRecommendations( String strUsername, List<RecommendedProduct> listProduct )
344 {
345 int nCount = 0;
346 _daoRecommendation.deleteByUser( strUsername, _plugin );
347 for ( RecommendedProduct product : listProduct )
348 {
349 Recommendation recommendation = new Recommendation( );
350 recommendation.setUsername( strUsername );
351 recommendation.setIdProduct( product.getProductId( ) );
352 recommendation.setScore( product.getScore( ) );
353 _daoRecommendation.insert( recommendation, _plugin );
354 nCount++;
355 }
356
357 return nCount;
358
359 }
360 }