Markishime commited on
Commit
9d3b8a9
·
1 Parent(s): 9bca8e4

Add CropDrive integration: upload limits enforcement and analysis completion messaging

Browse files

- Update cropdrive_integration.py to handle CONFIG messages with upload limits
- Add can_start_analysis() function to check upload limits before analysis
- Add get_upload_limit_info() helper function
- Update send_analysis_complete() to include ISO timestamp
- Update upload.py to enforce upload limits before allowing analysis
- Display upload usage information on upload page
- Update results.py to ensure ANALYSIS_COMPLETE message includes timestamp
- Handle unlimited plans (infinity/-1) properly

modules/results.py CHANGED
@@ -625,15 +625,25 @@ def show_results_page():
625
  analysis_type = 'leaf'
626
 
627
  # Send completion message
 
 
 
 
 
 
 
 
 
 
628
  send_analysis_complete(
629
- title=f'Agricultural Analysis - {datetime.now().strftime("%Y-%m-%d %H:%M")}',
630
  analysis_type=analysis_type,
631
  summary=summary or "Comprehensive soil and leaf analysis completed successfully.",
632
  recommendations_count=recommendations_count,
633
  file_url=None, # Could add URL to PDF if generated
634
  analysis_data={
635
  'resultId': results_data.get('id'),
636
- 'timestamp': results_data.get('timestamp').isoformat() if hasattr(results_data.get('timestamp'), 'isoformat') else str(results_data.get('timestamp')),
637
  'status': 'completed'
638
  }
639
  )
 
625
  analysis_type = 'leaf'
626
 
627
  # Send completion message
628
+ # Get timestamp in ISO format
629
+ timestamp = results_data.get('timestamp')
630
+ if timestamp:
631
+ if hasattr(timestamp, 'isoformat'):
632
+ timestamp_str = timestamp.isoformat()
633
+ else:
634
+ timestamp_str = str(timestamp)
635
+ else:
636
+ timestamp_str = datetime.now().isoformat()
637
+
638
  send_analysis_complete(
639
+ title=f'Analysis Report - {datetime.now().strftime("%Y-%m-%d")}',
640
  analysis_type=analysis_type,
641
  summary=summary or "Comprehensive soil and leaf analysis completed successfully.",
642
  recommendations_count=recommendations_count,
643
  file_url=None, # Could add URL to PDF if generated
644
  analysis_data={
645
  'resultId': results_data.get('id'),
646
+ 'timestamp': timestamp_str,
647
  'status': 'completed'
648
  }
649
  )
modules/upload.py CHANGED
@@ -1060,10 +1060,52 @@ def upload_section():
1060
 
1061
  land_yield_provided = land_size > 0 and current_yield > 0
1062
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1063
  if soil_uploaded and leaf_uploaded and land_yield_provided:
1064
- if st.button(f"🚀 {t('upload_start_analysis')}", type="primary", use_container_width=True, key="start_analysis"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1065
  # Ensure files are valid before passing to analysis
1066
  try:
 
 
 
 
 
 
 
 
 
 
 
1067
  soil_file = st.session_state.soil_file
1068
  leaf_file = st.session_state.leaf_file
1069
 
 
1060
 
1061
  land_yield_provided = land_size > 0 and current_yield > 0
1062
 
1063
+ # Check upload limits before allowing analysis
1064
+ can_start = True
1065
+ limit_info = None
1066
+ try:
1067
+ from utils.cropdrive_integration import can_start_analysis, get_upload_limit_info
1068
+ can_start = can_start_analysis()
1069
+ limit_info = get_upload_limit_info()
1070
+ except ImportError:
1071
+ # CropDrive integration not available, allow analysis
1072
+ pass
1073
+ except Exception:
1074
+ # Error checking limits, allow analysis to proceed
1075
+ pass
1076
+
1077
  if soil_uploaded and leaf_uploaded and land_yield_provided:
1078
+ # Show upload limit info if available
1079
+ if limit_info:
1080
+ uploads_used = limit_info.get('uploads_used', 0)
1081
+ uploads_limit = limit_info.get('uploads_limit', 0)
1082
+ uploads_remaining = limit_info.get('uploads_remaining', 0)
1083
+
1084
+ if uploads_limit > 0:
1085
+ if uploads_remaining == float('inf') or uploads_remaining == -1:
1086
+ st.info(f"📊 **Analyses:** Unlimited")
1087
+ else:
1088
+ st.info(f"📊 **Analyses:** {uploads_used}/{uploads_limit} used ({uploads_remaining} remaining)")
1089
+
1090
+ # Check if limit exceeded
1091
+ if not can_start:
1092
+ st.error("❌ **Analysis Limit Reached**")
1093
+ st.info("💡 You have reached your analysis limit. Please upgrade your plan to continue analyzing lab reports.")
1094
+ st.button(f"🚀 {t('upload_start_analysis')}", disabled=True, use_container_width=True, key="start_analysis_disabled_limit")
1095
+ elif st.button(f"🚀 {t('upload_start_analysis')}", type="primary", use_container_width=True, key="start_analysis"):
1096
  # Ensure files are valid before passing to analysis
1097
  try:
1098
+ # Double-check limit before starting (in case it changed)
1099
+ try:
1100
+ from utils.cropdrive_integration import can_start_analysis
1101
+ if not can_start_analysis():
1102
+ st.error("❌ **Analysis Limit Reached**")
1103
+ st.info("💡 You have reached your analysis limit. Please upgrade your plan to continue analyzing lab reports.")
1104
+ st.stop()
1105
+ except Exception:
1106
+ # If check fails, allow to proceed
1107
+ pass
1108
+
1109
  soil_file = st.session_state.soil_file
1110
  leaf_file = st.session_state.leaf_file
1111
 
utils/cropdrive_integration.py CHANGED
@@ -43,7 +43,11 @@ def inject_parent_communication():
43
  userId: data.userId,
44
  plan: data.plan || 'none',
45
  userEmail: data.userEmail || '',
46
- userName: data.userName || ''
 
 
 
 
47
  };
48
  currentLanguage = userConfig.language;
49
  if (currentLanguage !== 'en' && currentLanguage !== 'ms') {
@@ -56,6 +60,10 @@ def inject_parent_communication():
56
  localStorage.setItem('cropdrive_userEmail', userConfig.userEmail || '');
57
  localStorage.setItem('cropdrive_userName', userConfig.userName || '');
58
  localStorage.setItem('cropdrive_plan', userConfig.plan || 'none');
 
 
 
 
59
 
60
  // Update URL parameters
61
  const url = new URL(window.location.href);
@@ -67,8 +75,18 @@ def inject_parent_communication():
67
  url.searchParams.set('userEmail', userConfig.userEmail || '');
68
  url.searchParams.set('userName', userConfig.userName || '');
69
  url.searchParams.set('plan', userConfig.plan || 'none');
 
 
 
 
70
  window.history.replaceState({}, '', url);
71
 
 
 
 
 
 
 
72
  // Notify parent that language was set
73
  window.parent.postMessage({
74
  type: 'STREAMLIT_LANGUAGE_UPDATE',
@@ -115,6 +133,11 @@ def inject_parent_communication():
115
  const userIdParam = urlParams.get('userId');
116
  const userEmailParam = urlParams.get('userEmail');
117
  const userNameParam = urlParams.get('userName');
 
 
 
 
 
118
  if (userIdParam) {
119
  localStorage.setItem('cropdrive_userId', userIdParam);
120
  }
@@ -124,6 +147,18 @@ def inject_parent_communication():
124
  if (userNameParam) {
125
  localStorage.setItem('cropdrive_userName', userNameParam);
126
  }
 
 
 
 
 
 
 
 
 
 
 
 
127
  });
128
  })();
129
  </script>
@@ -158,6 +193,37 @@ def initialize_integration():
158
  features_str = query_params.get('features', '')
159
  features = features_str.split(',') if features_str else []
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  # Store user config in session state
162
  if 'user_config' not in st.session_state:
163
  st.session_state.user_config = {
@@ -178,6 +244,12 @@ def initialize_integration():
178
  st.session_state.user_config['userEmail'] = user_email
179
  st.session_state.user_config['userName'] = user_name
180
 
 
 
 
 
 
 
181
  return current_lang, user_plan, features
182
 
183
  # ============================================================================
@@ -203,20 +275,30 @@ def send_analysis_complete(
203
  file_url: URL to analysis file/image (optional)
204
  analysis_data: Additional analysis data dict (optional)
205
  """
 
 
206
  # Include user ID in the analysis complete message
207
  user_id = get_user_id()
208
  user_email = get_user_email()
209
 
 
 
 
 
 
 
 
 
210
  message = {
211
  'type': 'ANALYSIS_COMPLETE',
 
212
  'title': title,
213
  'analysisType': analysis_type,
214
  'summary': summary or '',
215
  'recommendationsCount': recommendations_count,
216
  'fileUrl': file_url,
217
  'analysisData': analysis_data or {},
218
- 'userId': user_id,
219
- 'userEmail': user_email
220
  }
221
 
222
  # Send to all allowed origins
@@ -320,6 +402,51 @@ def get_user_name():
320
  user_name = st.session_state.get('user_config', {}).get('userName', '')
321
  return user_name
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  def send_language_change(new_language: str):
324
  """Send language change notification to parent window when language changes within Streamlit"""
325
  # Validate language
 
43
  userId: data.userId,
44
  plan: data.plan || 'none',
45
  userEmail: data.userEmail || '',
46
+ userName: data.userName || '',
47
+ uploadsUsed: data.uploadsUsed || 0,
48
+ uploadsLimit: data.uploadsLimit || 0,
49
+ uploadLimitExceeded: data.uploadLimitExceeded || false,
50
+ uploadsRemaining: data.uploadsRemaining !== undefined ? data.uploadsRemaining : (data.uploadsLimit || 0) - (data.uploadsUsed || 0)
51
  };
52
  currentLanguage = userConfig.language;
53
  if (currentLanguage !== 'en' && currentLanguage !== 'ms') {
 
60
  localStorage.setItem('cropdrive_userEmail', userConfig.userEmail || '');
61
  localStorage.setItem('cropdrive_userName', userConfig.userName || '');
62
  localStorage.setItem('cropdrive_plan', userConfig.plan || 'none');
63
+ localStorage.setItem('cropdrive_uploadsUsed', String(userConfig.uploadsUsed));
64
+ localStorage.setItem('cropdrive_uploadsLimit', String(userConfig.uploadsLimit));
65
+ localStorage.setItem('cropdrive_uploadLimitExceeded', String(userConfig.uploadLimitExceeded));
66
+ localStorage.setItem('cropdrive_uploadsRemaining', String(userConfig.uploadsRemaining));
67
 
68
  // Update URL parameters
69
  const url = new URL(window.location.href);
 
75
  url.searchParams.set('userEmail', userConfig.userEmail || '');
76
  url.searchParams.set('userName', userConfig.userName || '');
77
  url.searchParams.set('plan', userConfig.plan || 'none');
78
+ url.searchParams.set('uploadsUsed', String(userConfig.uploadsUsed));
79
+ url.searchParams.set('uploadsLimit', String(userConfig.uploadsLimit));
80
+ url.searchParams.set('uploadLimitExceeded', String(userConfig.uploadLimitExceeded));
81
+ url.searchParams.set('uploadsRemaining', String(userConfig.uploadsRemaining));
82
  window.history.replaceState({}, '', url);
83
 
84
+ // Notify parent that config was received
85
+ window.parent.postMessage({
86
+ type: 'CONFIG_RECEIVED',
87
+ userId: userConfig.userId
88
+ }, '*');
89
+
90
  // Notify parent that language was set
91
  window.parent.postMessage({
92
  type: 'STREAMLIT_LANGUAGE_UPDATE',
 
133
  const userIdParam = urlParams.get('userId');
134
  const userEmailParam = urlParams.get('userEmail');
135
  const userNameParam = urlParams.get('userName');
136
+ const uploadsUsedParam = urlParams.get('uploadsUsed');
137
+ const uploadsLimitParam = urlParams.get('uploadsLimit');
138
+ const uploadLimitExceededParam = urlParams.get('uploadLimitExceeded');
139
+ const uploadsRemainingParam = urlParams.get('uploadsRemaining');
140
+
141
  if (userIdParam) {
142
  localStorage.setItem('cropdrive_userId', userIdParam);
143
  }
 
147
  if (userNameParam) {
148
  localStorage.setItem('cropdrive_userName', userNameParam);
149
  }
150
+ if (uploadsUsedParam !== null) {
151
+ localStorage.setItem('cropdrive_uploadsUsed', uploadsUsedParam);
152
+ }
153
+ if (uploadsLimitParam !== null) {
154
+ localStorage.setItem('cropdrive_uploadsLimit', uploadsLimitParam);
155
+ }
156
+ if (uploadLimitExceededParam !== null) {
157
+ localStorage.setItem('cropdrive_uploadLimitExceeded', uploadLimitExceededParam);
158
+ }
159
+ if (uploadsRemainingParam !== null) {
160
+ localStorage.setItem('cropdrive_uploadsRemaining', uploadsRemainingParam);
161
+ }
162
  });
163
  })();
164
  </script>
 
193
  features_str = query_params.get('features', '')
194
  features = features_str.split(',') if features_str else []
195
 
196
+ # Get upload limits from URL parameters
197
+ uploads_used = query_params.get('uploadsUsed', '0')
198
+ uploads_limit = query_params.get('uploadsLimit', '0')
199
+ upload_limit_exceeded = query_params.get('uploadLimitExceeded', 'false')
200
+ uploads_remaining = query_params.get('uploadsRemaining', '0')
201
+
202
+ # Convert to appropriate types
203
+ try:
204
+ uploads_used = int(uploads_used) if uploads_used else 0
205
+ except (ValueError, TypeError):
206
+ uploads_used = 0
207
+
208
+ try:
209
+ uploads_limit = int(uploads_limit) if uploads_limit else 0
210
+ except (ValueError, TypeError):
211
+ uploads_limit = 0
212
+
213
+ try:
214
+ upload_limit_exceeded = upload_limit_exceeded.lower() == 'true' if upload_limit_exceeded else False
215
+ except (AttributeError, ValueError):
216
+ upload_limit_exceeded = False
217
+
218
+ try:
219
+ uploads_remaining_str = str(uploads_remaining)
220
+ if uploads_remaining_str.lower() == 'infinity' or uploads_remaining_str == '-1':
221
+ uploads_remaining = float('inf')
222
+ else:
223
+ uploads_remaining = int(uploads_remaining) if uploads_remaining else 0
224
+ except (ValueError, TypeError):
225
+ uploads_remaining = 0
226
+
227
  # Store user config in session state
228
  if 'user_config' not in st.session_state:
229
  st.session_state.user_config = {
 
244
  st.session_state.user_config['userEmail'] = user_email
245
  st.session_state.user_config['userName'] = user_name
246
 
247
+ # Store upload limits in session state
248
+ st.session_state.uploads_used = uploads_used
249
+ st.session_state.uploads_limit = uploads_limit
250
+ st.session_state.upload_limit_exceeded = upload_limit_exceeded
251
+ st.session_state.uploads_remaining = uploads_remaining
252
+
253
  return current_lang, user_plan, features
254
 
255
  # ============================================================================
 
275
  file_url: URL to analysis file/image (optional)
276
  analysis_data: Additional analysis data dict (optional)
277
  """
278
+ from datetime import datetime
279
+
280
  # Include user ID in the analysis complete message
281
  user_id = get_user_id()
282
  user_email = get_user_email()
283
 
284
+ # Ensure analysis_data includes timestamp if not already present
285
+ if analysis_data is None:
286
+ analysis_data = {}
287
+
288
+ # Add timestamp if not already in analysis_data
289
+ if 'timestamp' not in analysis_data:
290
+ analysis_data['timestamp'] = datetime.now().isoformat()
291
+
292
  message = {
293
  'type': 'ANALYSIS_COMPLETE',
294
+ 'userId': user_id,
295
  'title': title,
296
  'analysisType': analysis_type,
297
  'summary': summary or '',
298
  'recommendationsCount': recommendations_count,
299
  'fileUrl': file_url,
300
  'analysisData': analysis_data or {},
301
+ 'timestamp': datetime.now().isoformat()
 
302
  }
303
 
304
  # Send to all allowed origins
 
402
  user_name = st.session_state.get('user_config', {}).get('userName', '')
403
  return user_name
404
 
405
+ def can_start_analysis():
406
+ """
407
+ Check if user can start a new analysis based on upload limits
408
+
409
+ Returns:
410
+ True if analysis can start, False otherwise
411
+ """
412
+ # Check if upload limit is exceeded
413
+ upload_limit_exceeded = st.session_state.get('upload_limit_exceeded', False)
414
+ if upload_limit_exceeded:
415
+ return False
416
+
417
+ # Check remaining uploads
418
+ uploads_remaining = st.session_state.get('uploads_remaining', 0)
419
+
420
+ # If uploads_remaining is infinity or -1, allow unlimited analyses
421
+ if uploads_remaining == float('inf') or uploads_remaining == -1:
422
+ return True
423
+
424
+ # If uploads_remaining is 0 or less, block analysis
425
+ if uploads_remaining <= 0:
426
+ return False
427
+
428
+ # Otherwise, allow analysis
429
+ return True
430
+
431
+ def get_upload_limit_info():
432
+ """
433
+ Get upload limit information for display
434
+
435
+ Returns:
436
+ Dictionary with upload limit information
437
+ """
438
+ uploads_used = st.session_state.get('uploads_used', 0)
439
+ uploads_limit = st.session_state.get('uploads_limit', 0)
440
+ upload_limit_exceeded = st.session_state.get('upload_limit_exceeded', False)
441
+ uploads_remaining = st.session_state.get('uploads_remaining', 0)
442
+
443
+ return {
444
+ 'uploads_used': uploads_used,
445
+ 'uploads_limit': uploads_limit,
446
+ 'upload_limit_exceeded': upload_limit_exceeded,
447
+ 'uploads_remaining': uploads_remaining
448
+ }
449
+
450
  def send_language_change(new_language: str):
451
  """Send language change notification to parent window when language changes within Streamlit"""
452
  # Validate language