Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- README.md +3 -9
- app.py +350 -0
- requirements.txt +3 -0
README.md
CHANGED
@@ -1,12 +1,6 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji: 📈
|
4 |
-
colorFrom: green
|
5 |
-
colorTo: purple
|
6 |
-
sdk: gradio
|
7 |
-
sdk_version: 5.42.0
|
8 |
app_file: app.py
|
9 |
-
|
|
|
10 |
---
|
11 |
-
|
12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: huggingface-deployment
|
|
|
|
|
|
|
|
|
|
|
3 |
app_file: app.py
|
4 |
+
sdk: gradio
|
5 |
+
sdk_version: 5.6.0
|
6 |
---
|
|
|
|
app.py
ADDED
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import requests
|
3 |
+
import pandas as pd
|
4 |
+
from typing import Dict, List, Optional, Tuple
|
5 |
+
import json
|
6 |
+
|
7 |
+
# Base URL for the API
|
8 |
+
BASE_URL = "https://gljx3devrf.execute-api.us-west-1.amazonaws.com/prod/"
|
9 |
+
|
10 |
+
class ContractPaymentViewer:
|
11 |
+
def __init__(self):
|
12 |
+
self.current_contract_data = {}
|
13 |
+
self.current_monthly_data = {}
|
14 |
+
|
15 |
+
def fetch_payment_history(self, contract_number: str) -> Tuple[gr.Dropdown, str, pd.DataFrame]:
|
16 |
+
"""
|
17 |
+
Fetch payment history for a given contract number
|
18 |
+
"""
|
19 |
+
if not contract_number:
|
20 |
+
return gr.Dropdown(choices=[], value=None), "Please enter a contract number", pd.DataFrame()
|
21 |
+
|
22 |
+
try:
|
23 |
+
# Clean the contract number
|
24 |
+
contract_number = contract_number.strip()
|
25 |
+
|
26 |
+
# Call the API
|
27 |
+
url = f"{BASE_URL}/contracts/{contract_number}/payments"
|
28 |
+
response = requests.get(url, timeout=10)
|
29 |
+
response.raise_for_status()
|
30 |
+
|
31 |
+
data = response.json()
|
32 |
+
self.current_contract_data = data
|
33 |
+
|
34 |
+
# Extract monthly breakdown for dropdown
|
35 |
+
monthly_breakdown = data.get('monthly_breakdown', {})
|
36 |
+
|
37 |
+
if not monthly_breakdown:
|
38 |
+
return gr.Dropdown(choices=[], value=None), "No payment history found", pd.DataFrame()
|
39 |
+
|
40 |
+
# Create choices for dropdown (sorted by date, newest first)
|
41 |
+
choices = sorted(list(monthly_breakdown.keys()), reverse=True)
|
42 |
+
|
43 |
+
# Create summary info
|
44 |
+
contract_details = data.get('contract_details', {})
|
45 |
+
summary = data.get('summary', {})
|
46 |
+
|
47 |
+
# Clean job description by removing carriage returns
|
48 |
+
job_description = contract_details.get('job_description', 'N/A').replace('\r', ' ')
|
49 |
+
|
50 |
+
summary_info = f"""
|
51 |
+
### Contract Summary
|
52 |
+
- **Contract Number:** {contract_details.get('contract_number', contract_number)}
|
53 |
+
- **Contractor:** {contract_details.get('contractor_name', 'N/A')}
|
54 |
+
- **Job Description:** {job_description}
|
55 |
+
- **Total Payments:** {summary.get('total_payments', 0)}
|
56 |
+
- **Total Disbursed:** ${summary.get('total_disbursed', 0):,.2f}
|
57 |
+
- **Total Held:** ${summary.get('total_held', 0):,.2f}
|
58 |
+
- **Estimated Completion:** {contract_details.get('estimated_completion', 'N/A')}
|
59 |
+
- **Last Updated:** {contract_details.get('last_updated', 'N/A')}
|
60 |
+
"""
|
61 |
+
|
62 |
+
# Create payment history DataFrame
|
63 |
+
payment_history = data.get('payment_history', [])
|
64 |
+
if payment_history:
|
65 |
+
history_df = pd.DataFrame(payment_history)
|
66 |
+
history_df = history_df[['estimate_number', 'type', 'payment_type', 'release_date', 'disbursed_amount', 'held_amount']]
|
67 |
+
history_df.columns = ['Estimate #', 'Type', 'Payment Type', 'Release Date', 'Disbursed Amount', 'Held Amount']
|
68 |
+
history_df['Disbursed Amount'] = history_df['Disbursed Amount'].apply(lambda x: f"${x:,.2f}")
|
69 |
+
history_df['Held Amount'] = history_df['Held Amount'].apply(lambda x: f"${x:,.2f}")
|
70 |
+
else:
|
71 |
+
history_df = pd.DataFrame()
|
72 |
+
|
73 |
+
return (
|
74 |
+
gr.Dropdown(choices=choices, value=choices[0] if choices else None, visible=True, interactive=True),
|
75 |
+
summary_info,
|
76 |
+
history_df
|
77 |
+
)
|
78 |
+
|
79 |
+
except requests.exceptions.RequestException as e:
|
80 |
+
return gr.Dropdown(choices=[], value=None), f"Error fetching data: {str(e)}", pd.DataFrame()
|
81 |
+
except Exception as e:
|
82 |
+
return gr.Dropdown(choices=[], value=None), f"Unexpected error: {str(e)}", pd.DataFrame()
|
83 |
+
|
84 |
+
def fetch_monthly_details(self, contract_number: str, selected_month: str) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame, str]:
|
85 |
+
"""
|
86 |
+
Fetch detailed monthly payment information
|
87 |
+
"""
|
88 |
+
if not contract_number or not selected_month:
|
89 |
+
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), "Please select a month"
|
90 |
+
|
91 |
+
try:
|
92 |
+
# Clean inputs
|
93 |
+
contract_number = contract_number.strip()
|
94 |
+
selected_month = selected_month.strip()
|
95 |
+
|
96 |
+
# Call the API for monthly details
|
97 |
+
url = f"{BASE_URL}/contracts/{contract_number}/payments/monthly/{selected_month}"
|
98 |
+
response = requests.get(url, timeout=10)
|
99 |
+
response.raise_for_status()
|
100 |
+
|
101 |
+
monthly_data = response.json()
|
102 |
+
self.current_monthly_data = monthly_data
|
103 |
+
|
104 |
+
# Process the payment data
|
105 |
+
payment_info = monthly_data.get('payments', [{}])[0] if monthly_data.get('payments') else {}
|
106 |
+
|
107 |
+
# Create monthly summary DataFrame
|
108 |
+
summary_data = {
|
109 |
+
'Metric': [
|
110 |
+
'Month',
|
111 |
+
'Payment Count',
|
112 |
+
'Total Disbursed',
|
113 |
+
'Total Held',
|
114 |
+
'Estimate Number',
|
115 |
+
'Payment Type',
|
116 |
+
'Release Date'
|
117 |
+
],
|
118 |
+
'Value': [
|
119 |
+
monthly_data.get('month', selected_month),
|
120 |
+
monthly_data.get('payment_count', 0),
|
121 |
+
f"${monthly_data.get('total_disbursed', 0):,.2f}",
|
122 |
+
f"${monthly_data.get('total_held', 0):,.2f}",
|
123 |
+
payment_info.get('estimate_number', 'N/A'),
|
124 |
+
payment_info.get('payment_type', 'N/A'),
|
125 |
+
payment_info.get('release_date', 'N/A')
|
126 |
+
]
|
127 |
+
}
|
128 |
+
summary_df = pd.DataFrame(summary_data)
|
129 |
+
|
130 |
+
# Create detailed items DataFrame
|
131 |
+
items_df = pd.DataFrame()
|
132 |
+
if payment_info and 'detailed_estimate' in payment_info:
|
133 |
+
detailed_estimate = payment_info['detailed_estimate']
|
134 |
+
prime_items = detailed_estimate.get('prime_contract', {}).get('items', [])
|
135 |
+
|
136 |
+
if prime_items:
|
137 |
+
# Process items for display
|
138 |
+
items_data = []
|
139 |
+
for item in prime_items:
|
140 |
+
if item.get('amounts', {}).get('this_period', 0) != 0: # Only show items with activity
|
141 |
+
items_data.append({
|
142 |
+
'Item #': item.get('item_number', ''),
|
143 |
+
'Description': item.get('description', '')[:50] + '...' if len(item.get('description', '')) > 50 else item.get('description', ''),
|
144 |
+
'Unit': item.get('units', ''),
|
145 |
+
'Unit Price': f"${item.get('unit_price', 0):,.2f}",
|
146 |
+
'Qty This Period': f"{item.get('quantities', {}).get('this_period', 0):,.2f}",
|
147 |
+
'Amount This Period': f"${item.get('amounts', {}).get('this_period', 0):,.2f}",
|
148 |
+
'Job to Date': f"${item.get('amounts', {}).get('job_to_date', 0):,.2f}",
|
149 |
+
'% Complete': f"{item.get('quantities', {}).get('percent_jtd', 0):.1f}%"
|
150 |
+
})
|
151 |
+
|
152 |
+
if items_data:
|
153 |
+
items_df = pd.DataFrame(items_data)
|
154 |
+
# Sort by amount this period (descending)
|
155 |
+
items_df['sort_value'] = items_df['Amount This Period'].str.replace('$', '').str.replace(',', '').astype(float)
|
156 |
+
items_df = items_df.sort_values('sort_value', ascending=False).drop('sort_value', axis=1)
|
157 |
+
else:
|
158 |
+
# Create empty dataframe with message
|
159 |
+
items_df = pd.DataFrame({'Message': ['No line items with activity for this period']})
|
160 |
+
else:
|
161 |
+
items_df = pd.DataFrame({'Message': ['No line items available']})
|
162 |
+
else:
|
163 |
+
items_df = pd.DataFrame({'Message': ['No line items available']})
|
164 |
+
|
165 |
+
# Create deductions DataFrame
|
166 |
+
deductions_df = pd.DataFrame()
|
167 |
+
if payment_info and 'detailed_estimate' in payment_info:
|
168 |
+
deductions = payment_info['detailed_estimate'].get('deductions', [])
|
169 |
+
if deductions:
|
170 |
+
deductions_data = []
|
171 |
+
for deduction in deductions:
|
172 |
+
deductions_data.append({
|
173 |
+
'Estimate #': deduction.get('estimate_no', ''),
|
174 |
+
'Description': deduction.get('description', ''),
|
175 |
+
'This Period': f"${deduction.get('this_period', 0):,.2f}",
|
176 |
+
'Job to Date': f"${deduction.get('job_to_date', 0):,.2f}"
|
177 |
+
})
|
178 |
+
deductions_df = pd.DataFrame(deductions_data)
|
179 |
+
else:
|
180 |
+
deductions_df = pd.DataFrame({'Message': ['No deductions for this period']})
|
181 |
+
else:
|
182 |
+
deductions_df = pd.DataFrame({'Message': ['No deductions available']})
|
183 |
+
|
184 |
+
# Create change orders DataFrame
|
185 |
+
change_orders_df = pd.DataFrame()
|
186 |
+
if payment_info and 'detailed_estimate' in payment_info:
|
187 |
+
change_orders = payment_info['detailed_estimate'].get('change_orders', {}).get('items', [])
|
188 |
+
if change_orders:
|
189 |
+
change_orders_data = []
|
190 |
+
for co in change_orders:
|
191 |
+
change_orders_data.append({
|
192 |
+
'Change Order #': co.get('change_order_no', ''),
|
193 |
+
'Description': co.get('description', '')[:50] + '...' if len(co.get('description', '')) > 50 else co.get('description', ''),
|
194 |
+
'This Period': f"${co.get('this_period_amt', 0):,.2f}",
|
195 |
+
'Job to Date': f"${co.get('job_to_date_amt', 0):,.2f}",
|
196 |
+
'Status': co.get('status', 'N/A')
|
197 |
+
})
|
198 |
+
if change_orders_data:
|
199 |
+
change_orders_df = pd.DataFrame(change_orders_data)
|
200 |
+
# Sort by amount this period (descending)
|
201 |
+
change_orders_df['sort_value'] = change_orders_df['This Period'].str.replace('$', '').str.replace(',', '').astype(float)
|
202 |
+
change_orders_df = change_orders_df.sort_values('sort_value', ascending=False).drop('sort_value', axis=1)
|
203 |
+
else:
|
204 |
+
change_orders_df = pd.DataFrame({'Message': ['No change orders for this period']})
|
205 |
+
else:
|
206 |
+
change_orders_df = pd.DataFrame({'Message': ['No change orders available']})
|
207 |
+
else:
|
208 |
+
change_orders_df = pd.DataFrame({'Message': ['No change orders available']})
|
209 |
+
|
210 |
+
# Create totals summary
|
211 |
+
if payment_info and 'detailed_estimate' in payment_info:
|
212 |
+
totals = payment_info['detailed_estimate'].get('overall_totals', {})
|
213 |
+
totals_info = f"""
|
214 |
+
### Payment Totals for {selected_month}
|
215 |
+
- **Original Contract Amount:** ${totals.get('original', 0):,.2f}
|
216 |
+
- **This Period:** ${totals.get('this_period', 0):,.2f}
|
217 |
+
- **Job to Date:** ${totals.get('job_to_date', 0):,.2f}
|
218 |
+
- **Extra Work This Estimate:** ${totals.get('extra_work_this_estimate', 0):,.2f}
|
219 |
+
- **Extra Work to Date:** ${totals.get('extra_work_to_date', 0):,.2f}
|
220 |
+
"""
|
221 |
+
else:
|
222 |
+
totals_info = "No detailed totals available"
|
223 |
+
|
224 |
+
return summary_df, items_df, deductions_df, change_orders_df, totals_info
|
225 |
+
|
226 |
+
except requests.exceptions.RequestException as e:
|
227 |
+
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), f"Error fetching monthly details: {str(e)}"
|
228 |
+
except Exception as e:
|
229 |
+
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), f"Unexpected error: {str(e)}"
|
230 |
+
|
231 |
+
# Create instance of the viewer
|
232 |
+
viewer = ContractPaymentViewer()
|
233 |
+
|
234 |
+
# Create Gradio interface
|
235 |
+
with gr.Blocks(title="Contract Payment Viewer", theme=gr.themes.Soft()) as app:
|
236 |
+
gr.Markdown("# Contract Payment Viewer")
|
237 |
+
gr.Markdown("Enter a contract number to view payment history and detailed monthly breakdowns")
|
238 |
+
|
239 |
+
with gr.Row():
|
240 |
+
with gr.Column(scale=1):
|
241 |
+
contract_input = gr.Textbox(
|
242 |
+
label="Contract Number",
|
243 |
+
placeholder="e.g., 03-3F0704",
|
244 |
+
value="03-3F0704"
|
245 |
+
)
|
246 |
+
fetch_button = gr.Button("Fetch Payment History", variant="primary")
|
247 |
+
|
248 |
+
with gr.Row():
|
249 |
+
with gr.Column():
|
250 |
+
summary_output = gr.Markdown(label="Contract Summary")
|
251 |
+
|
252 |
+
with gr.Row():
|
253 |
+
with gr.Column(scale=1):
|
254 |
+
month_dropdown = gr.Dropdown(
|
255 |
+
label="Select Month",
|
256 |
+
choices=[],
|
257 |
+
visible=False
|
258 |
+
)
|
259 |
+
fetch_monthly_button = gr.Button("Get Monthly Details", variant="secondary", visible=False)
|
260 |
+
|
261 |
+
with gr.Row():
|
262 |
+
with gr.Column():
|
263 |
+
with gr.Accordion("Payment History", open=False):
|
264 |
+
history_table = gr.Dataframe(
|
265 |
+
label="Payment History",
|
266 |
+
wrap=True
|
267 |
+
)
|
268 |
+
|
269 |
+
# Monthly details section
|
270 |
+
with gr.Row(visible=False) as monthly_section:
|
271 |
+
with gr.Column():
|
272 |
+
gr.Markdown("## Monthly Payment Details")
|
273 |
+
|
274 |
+
with gr.Tabs():
|
275 |
+
with gr.TabItem("Summary"):
|
276 |
+
monthly_summary_table = gr.Dataframe(
|
277 |
+
label="Monthly Summary",
|
278 |
+
wrap=True
|
279 |
+
)
|
280 |
+
totals_output = gr.Markdown(label="Payment Totals")
|
281 |
+
|
282 |
+
with gr.TabItem("Line Items"):
|
283 |
+
items_table = gr.Dataframe(
|
284 |
+
label="Contract Items (This Period Activity)",
|
285 |
+
wrap=True
|
286 |
+
)
|
287 |
+
|
288 |
+
with gr.TabItem("Deductions"):
|
289 |
+
deductions_table = gr.Dataframe(
|
290 |
+
label="Deductions",
|
291 |
+
wrap=True
|
292 |
+
)
|
293 |
+
|
294 |
+
with gr.TabItem("Change Orders"):
|
295 |
+
change_orders_table = gr.Dataframe(
|
296 |
+
label="Change Orders",
|
297 |
+
wrap=True
|
298 |
+
)
|
299 |
+
|
300 |
+
# Event handlers
|
301 |
+
def handle_fetch_history(contract_number):
|
302 |
+
dropdown, summary, history = viewer.fetch_payment_history(contract_number)
|
303 |
+
return {
|
304 |
+
month_dropdown: dropdown,
|
305 |
+
summary_output: summary,
|
306 |
+
history_table: history,
|
307 |
+
fetch_monthly_button: gr.Button(visible=True if dropdown.choices else False),
|
308 |
+
monthly_section: gr.Row(visible=False)
|
309 |
+
}
|
310 |
+
|
311 |
+
def handle_fetch_monthly(contract_number, selected_month):
|
312 |
+
summary_df, items_df, deductions_df, change_orders_df, totals = viewer.fetch_monthly_details(contract_number, selected_month)
|
313 |
+
return {
|
314 |
+
monthly_summary_table: summary_df,
|
315 |
+
items_table: items_df,
|
316 |
+
deductions_table: deductions_df,
|
317 |
+
change_orders_table: change_orders_df,
|
318 |
+
totals_output: totals,
|
319 |
+
monthly_section: gr.Row(visible=True)
|
320 |
+
}
|
321 |
+
|
322 |
+
fetch_button.click(
|
323 |
+
fn=handle_fetch_history,
|
324 |
+
inputs=[contract_input],
|
325 |
+
outputs=[month_dropdown, summary_output, history_table, fetch_monthly_button, monthly_section]
|
326 |
+
)
|
327 |
+
|
328 |
+
fetch_monthly_button.click(
|
329 |
+
fn=handle_fetch_monthly,
|
330 |
+
inputs=[contract_input, month_dropdown],
|
331 |
+
outputs=[monthly_summary_table, items_table, deductions_table, change_orders_table, totals_output, monthly_section]
|
332 |
+
)
|
333 |
+
|
334 |
+
# Auto-fetch monthly details when dropdown changes
|
335 |
+
month_dropdown.change(
|
336 |
+
fn=handle_fetch_monthly,
|
337 |
+
inputs=[contract_input, month_dropdown],
|
338 |
+
outputs=[monthly_summary_table, items_table, deductions_table, change_orders_table, totals_output, monthly_section]
|
339 |
+
)
|
340 |
+
|
341 |
+
# Launch the app
|
342 |
+
if __name__ == "__main__":
|
343 |
+
app.launch(
|
344 |
+
share=True,
|
345 |
+
server_name="0.0.0.0",
|
346 |
+
server_port=7860,
|
347 |
+
show_error=True,
|
348 |
+
debug=False,
|
349 |
+
max_threads=10
|
350 |
+
)
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
gradio==5.6.0
|
2 |
+
requests==2.32.4
|
3 |
+
pandas==2.3.1
|