varshakolanu commited on
Commit
d68584a
·
verified ·
1 Parent(s): 0284b07

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +249 -0
app.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ import os
3
+ import json
4
+ import requests # For making HTTP requests to Salesforce
5
+ from model import process_vendor_logs # Import your scoring logic from model.py
6
+
7
+ app = Flask(__name__)
8
+
9
+ # --- Configuration ---
10
+ # These should be set as environment variables in your Hugging Face Space for security
11
+ SALESFORCE_CONSUMER_KEY = os.environ.get('SALESFORCE_CONSUMER_KEY')
12
+ SALESFORCE_CONSUMER_SECRET = os.environ.get('SALESFORCE_CONSUMER_SECRET')
13
+ SALESFORCE_CALLBACK_URL = os.environ.get('SALESFORCE_CALLBACK_URL') # Needs to match your Connected App
14
+ SALESFORCE_TOKEN_URL = "https://login.salesforce.com/services/oauth2/token" # Or your sandbox URL
15
+ SALESFORCE_AUTH_URL = "https://login.salesforce.com/services/oauth2/authorize" # Or your sandbox URL
16
+
17
+ # Placeholder for your Salesforce instance URL. This will be obtained dynamically
18
+ SALESFORCE_INSTANCE_URL = None
19
+
20
+ # Hugging Face Space doesn't persist data, so use a very basic in-memory storage for demonstration.
21
+ # Replace with a database (e.g., PostgreSQL) for a production environment.
22
+ auth_code = None
23
+ access_token = None
24
+ refresh_token = None
25
+
26
+ # --- Helper Functions ---
27
+
28
+ def get_salesforce_access_token(code):
29
+ """
30
+ Exchanges an authorization code for an access token and refresh token from Salesforce.
31
+ Args:
32
+ code: The authorization code received from Salesforce.
33
+ Returns:
34
+ A tuple containing the access token and refresh token, or (None, None) on error.
35
+ """
36
+ token_data = {
37
+ 'grant_type': 'authorization_code',
38
+ 'code': code,
39
+ 'client_id': SALESFORCE_CONSUMER_KEY,
40
+ 'client_secret': SALESFORCE_CONSUMER_SECRET,
41
+ 'redirect_uri': SALESFORCE_CALLBACK_URL
42
+ }
43
+ response = requests.post(SALESFORCE_TOKEN_URL, data=token_data)
44
+ if response.status_code == 200:
45
+ token_response = response.json()
46
+ return token_response.get('access_token'), token_response.get('refresh_token'), token_response.get('instance_url')
47
+ else:
48
+ print(f"Error getting access token: {response.text}") # Log the error
49
+ return None, None, None
50
+
51
+ def refresh_salesforce_access_token(refresh_token):
52
+ """
53
+ Refreshes the access token using the refresh token.
54
+ Args:
55
+ refresh_token: The refresh token from Salesforce.
56
+ Returns:
57
+ A tuple containing the new access token and refresh token, or (None, None) on error.
58
+ """
59
+ token_data = {
60
+ 'grant_type': 'refresh_token',
61
+ 'refresh_token': refresh_token,
62
+ 'client_id': SALESFORCE_CONSUMER_KEY,
63
+ 'client_secret': SALESFORCE_CONSUMER_SECRET,
64
+ 'redirect_uri': SALESFORCE_CALLBACK_URL #important for refresh
65
+ }
66
+ response = requests.post(SALESFORCE_TOKEN_URL, data=token_data)
67
+ if response.status_code == 200:
68
+ token_response = response.json()
69
+ return token_response.get('access_token'), token_response.get('refresh_token'), token_response.get('instance_url')
70
+ else:
71
+ print(f"Error refreshing access token: {response.text}") # Log the error
72
+ return None, None, None
73
+
74
+ def query_salesforce(soql_query, access_token, instance_url):
75
+ """
76
+ Executes a SOQL query against Salesforce.
77
+ Args:
78
+ soql_query: The SOQL query string.
79
+ access_token: The Salesforce access token.
80
+ instance_url: The Salesforce instance URL.
81
+ Returns:
82
+ A list of records from Salesforce, or None on error.
83
+ """
84
+ headers = {'Authorization': f'Bearer {access_token}'}
85
+ url = f"{instance_url}/services/data/v59.0/query/?q={soql_query}" # Use API version v59.0
86
+ response = requests.get(url, headers=headers)
87
+ if response.status_code == 200:
88
+ return response.json().get('records')
89
+ else:
90
+ print(f"Error querying Salesforce: {response.text}") # Log the error
91
+ return None
92
+
93
+ def send_data_to_salesforce(sobject_name, data, access_token, instance_url):
94
+ """
95
+ Sends data to Salesforce to create a new record.
96
+ Args:
97
+ sobject_name: The Salesforce object name (e.g., 'Subcontractor_Performance_Score__c').
98
+ data: A dictionary containing the data to send.
99
+ access_token: The Salesforce access token.
100
+ instance_url: The Salesforce instance URL.
101
+ Returns:
102
+ The ID of the created record, or None on error.
103
+ """
104
+ headers = {
105
+ 'Authorization': f'Bearer {access_token}',
106
+ 'Content-Type': 'application/json'
107
+ }
108
+ url = f"{instance_url}/services/data/v59.0/sobjects/{sobject_name}/" # Use API version v59.0
109
+ response = requests.post(url, headers=headers, json=data)
110
+ if 200 <= response.status_code < 300: # Successful creation (201 Created)
111
+ return response.json().get('id')
112
+ else:
113
+ print(f"Error sending data to Salesforce ({sobject_name}): {response.text}") # Log error
114
+ return None
115
+
116
+
117
+
118
+ # --- Flask Routes ---
119
+
120
+ @app.route('/')
121
+ def index():
122
+ """
123
+ Displays a welcome message and instructions.
124
+ """
125
+ return ("Welcome to the Subcontractor Performance Scorer API!\n"
126
+ "1. To authorize with Salesforce, visit /authorize_salesforce.\n"
127
+ "2. After authorization, vendor logs will be processed automatically (in a real implementation).\n"
128
+ "3. Scores will be sent to Salesforce (in a real implementation).\n")
129
+
130
+ @app.route('/authorize_salesforce')
131
+ def authorize_salesforce():
132
+ """
133
+ Redirects the user to the Salesforce authorization URL to obtain an authorization code.
134
+ """
135
+ authorization_url = (
136
+ f"{SALESFORCE_AUTH_URL}?"
137
+ "response_type=code&"
138
+ f"client_id={SALESFORCE_CONSUMER_KEY}&"
139
+ f"redirect_uri={SALESFORCE_CALLBACK_URL}&"
140
+ "scope=api" # Add other scopes as needed (e.g., 'refresh_token')
141
+ )
142
+ return jsonify({"authorization_url": authorization_url}) # Return as JSON for easier handling
143
+
144
+
145
+ @app.route('/callback')
146
+ def callback():
147
+ """
148
+ Handles the Salesforce callback after authorization. Exchanges the authorization code
149
+ for an access token and refresh token.
150
+ """
151
+ global auth_code, access_token, refresh_token, SALESFORCE_INSTANCE_URL
152
+ code = request.args.get('code')
153
+ if not code:
154
+ return jsonify({'error': 'Authorization code not provided.'}), 400
155
+ auth_code = code # Store the auth code
156
+ access_token, refresh_token, SALESFORCE_INSTANCE_URL = get_salesforce_access_token(code)
157
+ if access_token:
158
+ return jsonify({'message': 'Successfully authorized with Salesforce! Access token and refresh token obtained.'}), 200
159
+ else:
160
+ return jsonify({'error': 'Failed to obtain access token.'}), 500
161
+
162
+
163
+
164
+ @app.route('/process_logs') # You might trigger this from a Salesforce outbound message or scheduled job.
165
+ def process_logs():
166
+ """
167
+ Retrieves vendor logs from Salesforce, processes them using the scoring model,
168
+ and sends the results to Salesforce.
169
+ """
170
+ global access_token, refresh_token, SALESFORCE_INSTANCE_URL
171
+ if not access_token:
172
+ if auth_code:
173
+ access_token, refresh_token, SALESFORCE_INSTANCE_URL = get_salesforce_access_token(auth_code)
174
+ else:
175
+ return jsonify({'error': 'Not authorized with Salesforce. Please visit /authorize_salesforce first.'}), 401
176
+
177
+ if not access_token:
178
+ return jsonify({'error': 'Not authorized with Salesforce. Please visit /authorize_salesforce first.'}), 401
179
+
180
+ if not SALESFORCE_INSTANCE_URL:
181
+ return jsonify({'error': 'Salesforce instance URL is missing.'}), 500
182
+
183
+ try:
184
+ # 1. Fetch vendor logs from Salesforce (replace with your actual SOQL query)
185
+ soql_query = "SELECT Id, Vendor__c, Work_Completion_Details__c, Delay_Reports__c, Incident_Logs__c, Log_Date__c FROM Vendor_Log__c"
186
+ vendor_logs = query_salesforce(soql_query, access_token, SALESFORCE_INSTANCE_URL)
187
+ if not vendor_logs:
188
+ return jsonify({'error': 'Failed to retrieve vendor logs from Salesforce or no logs found.'}), 500
189
+
190
+ # 2. Process the vendor logs using the model
191
+ scores_for_salesforce = process_vendor_logs(vendor_logs) # Call your model.py function
192
+
193
+ if not scores_for_salesforce:
194
+ return jsonify({'warning': 'No scores to send to Salesforce.'}), 200 # No error, but no data sent
195
+
196
+ # 3. Send the scores to Salesforce
197
+ all_updates_successful = True
198
+ for score_data in scores_for_salesforce:
199
+ # Map the score_data to the fields in your Subcontractor_Performance_Score__c object
200
+ salesforce_data = {
201
+ 'Vendor__c': score_data['vendor_id'], # You might need to look up the Salesforce ID
202
+ 'Month_c': score_data['month'],
203
+ 'Quality_Score__c': score_data['quality'],
204
+ 'Timeliness_Score__c': score_data['timeliness'],
205
+ 'Safety_Score__c': score_data['safety'],
206
+ 'Communication_Score__c': score_data['communication'],
207
+ 'Final_Score_c': score_data['final_score'],
208
+ 'Alert_Flag_c': score_data['alert_flag'],
209
+ 'Certification_URL_c': score_data['certificate_url'], #add this
210
+ 'Trend_Deviation__c': score_data['trend_deviation']
211
+ }
212
+ record_id = send_data_to_salesforce('Subcontractor_Performance_Score__c', salesforce_data, access_token, SALESFORCE_INSTANCE_URL)
213
+ if not record_id:
214
+ all_updates_successful = False
215
+ print(f"Failed to send score for vendor {score_data['vendor_id']} to Salesforce.")
216
+
217
+ if all_updates_successful:
218
+ return jsonify({'message': 'Successfully processed vendor logs and sent scores to Salesforce.'}), 200
219
+ else:
220
+ return jsonify({'error': 'Failed to send some scores to Salesforce. Check logs for details.'}), 500
221
+
222
+ except Exception as e:
223
+ return jsonify({'error': f'An error occurred: {e}'}), 500
224
+
225
+
226
+
227
+ @app.route('/refresh_token')
228
+ def refresh_token_route():
229
+ """
230
+ Endpoint to refresh the Salesforce access token. This would be called periodically
231
+ (e.g., by a scheduled task) in a real application.
232
+ """
233
+ global access_token, refresh_token, SALESFORCE_INSTANCE_URL
234
+ if not refresh_token:
235
+ return jsonify({'error': 'No refresh token available.'}), 400
236
+
237
+ new_access_token, new_refresh_token, SALESFORCE_INSTANCE_URL = refresh_salesforce_access_token(refresh_token)
238
+ if new_access_token:
239
+ access_token = new_access_token
240
+ refresh_token = new_refresh_token # Update the refresh token, as it might change.
241
+ return jsonify({'message': 'Access token refreshed successfully.'}), 200
242
+ else:
243
+ return jsonify({'error': 'Failed to refresh access token.'}), 500
244
+
245
+
246
+ if __name__ == '__main__':
247
+ # The port is automatically configured by Hugging Face Spaces
248
+ port = int(os.environ.get("PORT", 8080))
249
+ app.run(host='0.0.0.0', port=port, debug=True)