@@ -194,6 +194,65 @@ export class DashboardService {
194194 return this . memberDashboardProvider . getMemberCheckIns ( userId , limit ) ;
195195 }
196196
197+ async getChurnRisk ( page : number , limit : number ) {
198+ const now = new Date ( ) ;
199+ const thirtyDaysAgo = new Date ( now . getTime ( ) - 30 * 24 * 60 * 60 * 1000 ) ;
200+ const ninetyDaysAgo = new Date ( now . getTime ( ) - 90 * 24 * 60 * 60 * 1000 ) ;
201+
202+ const activeUsers = await this . userRepository
203+ . createQueryBuilder ( 'u' )
204+ . where ( 'u.isDeleted = :d' , { d : false } )
205+ . andWhere ( 'u.isVerified = :v' , { v : true } )
206+ . getMany ( ) ;
207+
208+ const results : any [ ] = [ ] ;
209+ for ( const user of activeUsers ) {
210+ const recentBooking = await this . bookingRepository
211+ . createQueryBuilder ( 'b' )
212+ . where ( 'b.userId = :uid' , { uid : user . id } )
213+ . andWhere ( 'b.createdAt >= :thirtyDaysAgo' , { thirtyDaysAgo } )
214+ . getCount ( ) ;
215+
216+ const olderBooking = await this . bookingRepository
217+ . createQueryBuilder ( 'b' )
218+ . where ( 'b.userId = :uid' , { uid : user . id } )
219+ . andWhere ( 'b.createdAt >= :ninetyDaysAgo' , { ninetyDaysAgo } )
220+ . andWhere ( 'b.createdAt < :thirtyDaysAgo' , { thirtyDaysAgo } )
221+ . getCount ( ) ;
222+
223+ const isAtRisk = ( recentBooking === 0 && olderBooking > 0 ) ||
224+ ( user as any ) . membershipStatus === 'inactive' ;
225+
226+ if ( isAtRisk ) {
227+ const lastBooking = await this . bookingRepository
228+ . createQueryBuilder ( 'b' )
229+ . where ( 'b.userId = :uid' , { uid : user . id } )
230+ . orderBy ( 'b.createdAt' , 'DESC' )
231+ . getOne ( ) ;
232+
233+ const totalBookings = await this . bookingRepository . count ( { where : { userId : user . id } as any } ) ;
234+ const daysSince = lastBooking
235+ ? Math . floor ( ( now . getTime ( ) - lastBooking . createdAt . getTime ( ) ) / ( 24 * 60 * 60 * 1000 ) )
236+ : 90 ;
237+ const riskScore = Math . min ( 100 , Math . round ( 100 - ( daysSince / 90 ) * 100 ) ) ;
238+
239+ results . push ( {
240+ userId : user . id ,
241+ fullName : `${ user . firstname } ${ user . lastname } ` ,
242+ email : user . email ,
243+ lastBookingDate : lastBooking ?. createdAt ?? null ,
244+ totalBookingsAllTime : totalBookings ,
245+ riskScore,
246+ } ) ;
247+ }
248+ }
249+
250+ results . sort ( ( a , b ) => b . riskScore - a . riskScore ) ;
251+ const total = results . length ;
252+ const items = results . slice ( ( page - 1 ) * limit , page * limit ) ;
253+ return { items, total, page, limit, totalPages : Math . ceil ( total / limit ) } ;
254+ }
255+
197256 private async getMonthlyRegistrations ( months : number ) {
198257 const result : { month : string ; count : number } [ ] = [ ] ;
199258 const now = new Date ( ) ;
0 commit comments