initial commit
[CPE_learningsite] / CPE / CPE.App / CPE.App.Web / Code / SessionEnd.cs
1 using CPE.App.Web.Models;
2 using System;
3 using System.Collections.Generic;
4 using System.Configuration;
5 using System.Linq;
6
7 namespace CPE.App.Web.Code
8 {
9     public enum participantResult
10     {
11         Pass,
12         Fail,
13         Ineligible,
14         Wonky //instead of throwing an error if something is null, we'll ask the client to contact cust service
15     }
16
17     public enum DispositionProcessingState
18     {
19         NotStarted = 0,
20         InProgress = 1,
21         Finished = 2
22     }
23
24     public class SessionEnd
25     {
26         public static participantResult ProcessEndOfMeetingSessionHeartbeatResultsForParticipant(CPEWebDataContext Database, MeetingParticipantSession meetingParticipantSession)
27         {
28             participantResult result = participantResult.Wonky;
29
30             int principalid = meetingParticipantSession.ParticipantKey;
31             int msk = meetingParticipantSession.MeetingSessionKey;
32
33             var meeting = meetingParticipantSession.MeetingSession;
34             int scoid = meeting.SCO_ID;
35
36             var participantEngagements = Database.ParticipantEngagements.Where(pe => pe.MeetingParticipantSessionKey == meetingParticipantSession.MeetingParticipantSessionKey);
37             int SessionHeartbeatCount = participantEngagements.Count();
38             int SessionEngagementCount = participantEngagements.Where(pe => pe.ResponseTime != null).Count();
39
40             ParticipantPurchase purchase = null;
41             var purchases = Database.ParticipantPurchases.Where(pp => pp.PrincipalID == principalid && pp.MeetingSco == scoid);
42             purchase = purchases.FirstOrDefault();
43
44             if (purchase == null)
45             {
46                 Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionHeartbeatResultsForParticipant]", String.Format("purchase is null: principalid = {0} scoid = {1}", principalid, scoid));
47                 result = participantResult.Ineligible;
48             }
49             else
50             {
51                 if (purchases.Count() > 1)
52                 {
53                     Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionHeartbeatResultsForParticipant]", String.Format("purchases count unexpectedly found > 1 : principalid = {0} scoid = {1} purchases.Count = {2}", principalid, scoid, purchases.Count()));
54                 }
55
56                 {
57                     Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionHeartbeatResultsForParticipant]", String.Format(
58                         "Before Percent Calc: principalid = {0} scoid = {1} SessionEngagementCount = {2} SessionHeartbeatCount = {3}",
59                         principalid, scoid, SessionEngagementCount, SessionHeartbeatCount));
60
61                     double sessionHeartbeatCountAsDouble = SessionHeartbeatCount;
62                     double sessionEngagementCountAsDouble = SessionEngagementCount;
63
64                     double percentComplete = (sessionHeartbeatCountAsDouble == 0 ? 1 : (sessionEngagementCountAsDouble / sessionHeartbeatCountAsDouble));
65
66                     if (percentComplete >= .75) //threshold for earning a certificate is 75% response rate
67                     {
68                         result = participantResult.Pass;
69                     }
70                     else
71                     {
72                         result = participantResult.Fail;
73                     }
74
75                     Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionHeartbeatResultsForParticipant]", String.Format(
76                         "Before SubmitChanges: principalid = {0} scoid = {1} result = {2} percentComplete = {3} SessionEngagementCount = {4} SessionHeartbeatCount = {5}",
77                         principalid, scoid, result, percentComplete, SessionEngagementCount, SessionHeartbeatCount));
78                 }
79             }
80
81             return result;
82         }
83
84         public static participantResult ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant(CPEWebDataContext Database, MeetingParticipantSession meetingParticipantSession, out string toEmail, out int meetingSco, out DateTime purchaseDate, out string purchaseTicket)
85         {
86             participantResult result = participantResult.Wonky;
87             toEmail = null;
88             meetingSco = -1;
89             purchaseDate = DateTime.MinValue;
90             purchaseTicket = null;
91
92             int principalid = meetingParticipantSession.ParticipantKey;
93             int msk = meetingParticipantSession.MeetingSessionKey;
94
95             var meeting = meetingParticipantSession.MeetingSession;
96             int scoid = meeting.SCO_ID;
97
98             int pollingCount = 0;
99             int maxPollingCount = 40;
100             var partTrackingsForMpsk = Database.GetParticipantTrackings().Where(pt => pt.MeetingParticipantSessionKey == meetingParticipantSession.MeetingParticipantSessionKey);
101             while ((partTrackingsForMpsk.Count() < 1) && (pollingCount < maxPollingCount)) // Try for 2 minutes
102             {
103                 System.Threading.Thread.Sleep(5000); // 5 seconds
104
105                 //adobe.GetAdobeTransactions(meeting, Database);
106                 //Database.SubmitChanges();
107
108                 // Try to force refresh data context
109                 try
110                 {
111                     Database.Refresh(System.Data.Linq.RefreshMode.KeepChanges, Database.AdobeTransactions);
112                 }
113                 catch (Exception ex)
114                 {
115                     // Unable to refresh the specified object.  The object no longer exists in the database.
116                     Extensions.LogServiceError("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant] 1 " + meetingParticipantSession.MeetingParticipantSessionKey, ex);
117                 }
118                 Database = new CPEWebDataContext();
119                 partTrackingsForMpsk = Database.GetParticipantTrackings().Where(pt => pt.MeetingParticipantSessionKey == meetingParticipantSession.MeetingParticipantSessionKey);
120                 pollingCount++;
121             }
122
123             if (pollingCount == maxPollingCount)
124             {
125                 Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format("Hit max poll attempts while searching for ParticipantTracking: principalid = {0} scoid = {1} pollingCount = {2}", principalid, scoid, pollingCount));
126             }
127             else
128             {
129                 Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format("ParticipantTracking record found: principalid = {0} scoid = {1} pollingCount = {2}", principalid, scoid, pollingCount));
130             }
131
132             // Try to force refresh data context
133             try
134             {
135                 Database.Refresh(System.Data.Linq.RefreshMode.KeepChanges, Database.AdobeTransactions);
136             }
137             catch (Exception ex)
138             {
139                 // Unable to refresh the specified object.  The object no longer exists in the database.
140                 Extensions.LogServiceError("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant] 2 " + meetingParticipantSession.MeetingParticipantSessionKey, ex);
141             }
142             Database = new CPEWebDataContext();
143
144             // Log the transactions that are used to make pass/fail and credit decisions
145             IQueryable<AdobeTransaction> adobeTransactionForUserInMeeting = Database.AdobeTransactions.Where(at => at.MeetingSessionKey == msk && at.Principal_ID == meetingParticipantSession.ParticipantKey);
146             foreach (AdobeTransaction at in adobeTransactionForUserInMeeting)
147             {
148                 Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format("principalid = {0} scoid = {1} AdobeTransactionKey = {2} StartDate = {3} EndDate = {4} MeetingSessionKey = {5}", principalid, scoid, at.AdobeTransactionKey, at.StartDate, at.EndDate, at.MeetingSessionKey));
149             }
150
151             ParticipantSessionsDataResult psdr =
152                 Database.ParticipantSessionsData(msk)
153                     .FirstOrDefault(
154                         psd =>
155                             psd.MeetingParticipantSessionKey ==
156                             meetingParticipantSession.MeetingParticipantSessionKey);
157             ParticipantPurchase purchase = null;
158             if (psdr != null)
159             {
160                 var purchases = Database.ParticipantPurchases.Where(pp => pp.PrincipalID == principalid && pp.MeetingSco == scoid);
161                 purchase = purchases.FirstOrDefault();
162
163                 if (purchase == null)
164                 {
165                     Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format("purchase is null: principalid = {0} scoid = {1}", principalid, scoid));
166                     result = participantResult.Ineligible;
167                 }
168                 else
169                 {
170                     if (purchases.Count() > 1)
171                     {
172                         Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format("purchases count unexpectedly found > 1 : principalid = {0} scoid = {1} purchases.Count = {2}", principalid, scoid, purchases.Count()));
173                     }
174
175                     purchase.DispositionProcessingState = (int)DispositionProcessingState.InProgress;
176                     Database.SubmitChanges();
177
178                     toEmail = purchase.Email;
179                     meetingSco = purchase.MeetingSco;
180                     purchaseDate = purchase.PurchaseDate;
181                     purchaseTicket = purchase.Ticket;
182
183                     int minutesInSessionHour = 50;
184
185                     MeetingSessionsDataResult meetingSession =
186                         Database.MeetingSessionsData().FirstOrDefault(m => m.MeetingSessionKey == msk);
187                     if (meetingSession != null)
188                     {
189                         double maxSessionTimePossible = -1;
190                         double maxSessionTimePossibleRoundedDown = -1;
191                         if (meetingSession.ActualSessionTime != null)
192                         {
193                             // one of these integers needs to be cast to a double to get a double as an answer, that's how .NET math works.
194                             maxSessionTimePossible = ((double)meetingSession.ActualSessionTime) / minutesInSessionHour;
195                             maxSessionTimePossibleRoundedDown = (Math.Floor(maxSessionTimePossible / .5)) * .5;
196                         }
197                         else
198                         {
199                             Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format("ActualSessionTime unexpectedly found == null : principalid = {0} scoid = {1} MeetingSessionKey = {2}", principalid, scoid, msk));
200                         }
201
202                         double dbSessionCredit = 0;
203                         double sessionCreditRoundedDown = 0;
204                         if (psdr.SessionCredit.HasValue)
205                         {
206                             dbSessionCredit = Convert.ToDouble(psdr.SessionCredit);
207
208                             sessionCreditRoundedDown = (Math.Floor(dbSessionCredit / .5))*.5;
209                         }
210
211                         List<double> validCourseCreditsValues =
212                             ConfigurationManager.AppSettings["MaxCourseCreditList"].Split(';')
213                                 .Select(s => double.Parse(s))
214                                 .ToList(); //max credits cut list
215
216                         int mx = validCourseCreditsValues.BinarySearch(maxSessionTimePossibleRoundedDown);
217                         //find appropriate valid value for this course
218                         //user awarded no more than max possible credits for the course
219                         int arrayPosition = mx >= 0 ? mx : ~mx - 1; //...by rounding down
220                         double maxValidCourseCredit = validCourseCreditsValues[arrayPosition];
221
222                         if (sessionCreditRoundedDown < 1)
223                         {
224                             purchase.Credits = 0;
225                         }
226                         else
227                         {
228                             if (sessionCreditRoundedDown > maxSessionTimePossibleRoundedDown)
229                             {
230                                 purchase.Credits = maxSessionTimePossibleRoundedDown;
231                             }
232                             else if (sessionCreditRoundedDown > maxValidCourseCredit)
233                             {
234                                 purchase.Credits = maxValidCourseCredit;
235                             }
236                             else
237                             {
238                                 purchase.Credits = sessionCreditRoundedDown;
239                             }
240                         }
241
242                         MeetingDetail meetingDetail =
243                             Database.MeetingDetails.FirstOrDefault(m => m.MeetingKey == purchase.MeetingSco);
244
245                         purchase.Presenter = meetingDetail == null ? "" : meetingDetail.Instructor;
246
247                         // This value gets set on first login, which may not be the date the cerficate was earned on (this value gets displayed on the certificate).  Ideally we would use the local user's time zone to figure the date.
248                         purchase.MeetingDate = DateTime.Now.Date;
249
250                         Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format(
251                             "Before Percent Calc: principalid = {0} scoid = {1} SessionEngagementCount = {2} SessionHeartbeatCount = {3}",
252                             principalid, scoid, psdr.SessionEngagementCount.GetValueOrDefault(), psdr.SessionHeartbeatCount.GetValueOrDefault()));
253
254                         double sessionHeartbeatCountAsDouble = psdr.SessionHeartbeatCount.GetValueOrDefault();
255                         double sessionEngagementCountAsDouble = psdr.SessionEngagementCount.GetValueOrDefault();
256
257                         double percentComplete = (sessionHeartbeatCountAsDouble == 0 ? 1 : (sessionEngagementCountAsDouble / sessionHeartbeatCountAsDouble));
258
259                         if ((purchase.Credits >= 1.0) && (percentComplete >= .75)) //threshold for earning a certificate is 75% response rate
260                         {
261                             purchase.EarnedCertificate = true;
262                             result = participantResult.Pass;
263                         }
264                         else
265                         {
266                             result = participantResult.Fail;
267                         }
268
269                         Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format(
270                             "Before SubmitChanges: principalid = {0} scoid = {1} result = {2} percentComplete = {3} dbSessionCredit = {4} sessionCreditRoundedDown = {5} purchase.Credits = {6} SessionEngagementCount = {7} SessionHeartbeatCount = {8}",
271                             principalid, scoid, result, percentComplete, dbSessionCredit, sessionCreditRoundedDown, purchase.Credits, psdr.SessionEngagementCount.GetValueOrDefault(), psdr.SessionHeartbeatCount.GetValueOrDefault()));
272
273                         purchase.DispositionProcessingState = (int)DispositionProcessingState.Finished;
274
275                         Database.SubmitChanges();
276
277                         // Comment this back out once things are working better
278                         //Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format("After SubmitChanges: principalid = {0} scoid = {1}", principalid, scoid));
279                     }
280                     else
281                     {
282                         Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format(
283                             "meetingSession is null: principalid = {0} scoid = {1} msk = {2}",
284                             principalid, scoid, msk));
285                     }
286                 }
287             }
288             else
289             {
290                 Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format(
291                     "psdr is null A: principalid = {0} scoid = {1} msk = {2} meetingParticipantSession.MeetingParticipantSessionKey = {3} meeting.StartDate = {4} meeting.EndDate = {5}",
292                     principalid, scoid, msk, meetingParticipantSession.MeetingParticipantSessionKey, meeting.StartDate, meeting.EndDate));
293             }
294             if (purchase == null)
295             {
296                 result = participantResult.Ineligible;
297             }
298
299             //Extensions.LogServiceCall("[SessionEnd][ProcessEndOfMeetingSessionAdobeConnectResultsForParticipant]", String.Format("After switch statement: principalid = {0} scoid= {1}", principalid, scoid));
300             return result;
301         }
302     }
303 }