Spaces:
Sleeping
Sleeping
import gradio as gr | |
import requests | |
import pandas as pd | |
from typing import Dict, List, Optional, Tuple | |
import json | |
# Base URL for the API | |
BASE_URL = "https://gljx3devrf.execute-api.us-west-1.amazonaws.com/prod/" | |
class ContractPaymentViewer: | |
def __init__(self): | |
self.current_contract_data = {} | |
self.current_monthly_data = {} | |
def fetch_payment_history(self, contract_number: str) -> Tuple[gr.Dropdown, str, pd.DataFrame]: | |
""" | |
Fetch payment history for a given contract number | |
""" | |
if not contract_number: | |
return gr.Dropdown(choices=[], value=None), "Please enter a contract number", pd.DataFrame() | |
try: | |
# Clean the contract number | |
contract_number = contract_number.strip() | |
# Call the API | |
url = f"{BASE_URL}/contracts/{contract_number}/payments" | |
response = requests.get(url, timeout=10) | |
response.raise_for_status() | |
data = response.json() | |
self.current_contract_data = data | |
# Extract monthly breakdown for dropdown | |
monthly_breakdown = data.get('monthly_breakdown', {}) | |
if not monthly_breakdown: | |
return gr.Dropdown(choices=[], value=None), "No payment history found", pd.DataFrame() | |
# Create choices for dropdown (sorted by date, newest first) | |
choices = sorted(list(monthly_breakdown.keys()), reverse=True) | |
# Create summary info | |
contract_details = data.get('contract_details', {}) | |
summary = data.get('summary', {}) | |
# Clean job description by removing carriage returns | |
job_description = contract_details.get('job_description', 'N/A').replace('\r', ' ') | |
summary_info = f""" | |
### Contract Summary | |
- **Contract Number:** {contract_details.get('contract_number', contract_number)} | |
- **Contractor:** {contract_details.get('contractor_name', 'N/A')} | |
- **Job Description:** {job_description} | |
- **Total Payments:** {summary.get('total_payments', 0)} | |
- **Total Disbursed:** ${summary.get('total_disbursed', 0):,.2f} | |
- **Total Held:** ${summary.get('total_held', 0):,.2f} | |
- **Estimated Completion:** {contract_details.get('estimated_completion', 'N/A')} | |
- **Last Updated:** {contract_details.get('last_updated', 'N/A')} | |
""" | |
# Create payment history DataFrame | |
payment_history = data.get('payment_history', []) | |
if payment_history: | |
history_df = pd.DataFrame(payment_history) | |
history_df = history_df[['estimate_number', 'type', 'payment_type', 'release_date', 'disbursed_amount', 'held_amount']] | |
history_df.columns = ['Estimate #', 'Type', 'Payment Type', 'Release Date', 'Disbursed Amount', 'Held Amount'] | |
history_df['Disbursed Amount'] = history_df['Disbursed Amount'].apply(lambda x: f"${x:,.2f}") | |
history_df['Held Amount'] = history_df['Held Amount'].apply(lambda x: f"${x:,.2f}") | |
else: | |
history_df = pd.DataFrame() | |
return ( | |
gr.Dropdown(choices=choices, value=choices[0] if choices else None, visible=True, interactive=True), | |
summary_info, | |
history_df | |
) | |
except requests.exceptions.RequestException as e: | |
return gr.Dropdown(choices=[], value=None), f"Error fetching data: {str(e)}", pd.DataFrame() | |
except Exception as e: | |
return gr.Dropdown(choices=[], value=None), f"Unexpected error: {str(e)}", pd.DataFrame() | |
def fetch_monthly_details(self, contract_number: str, selected_month: str) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame, str]: | |
""" | |
Fetch detailed monthly payment information | |
""" | |
if not contract_number or not selected_month: | |
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), "Please select a month" | |
try: | |
# Clean inputs | |
contract_number = contract_number.strip() | |
selected_month = selected_month.strip() | |
# Call the API for monthly details | |
url = f"{BASE_URL}/contracts/{contract_number}/payments/monthly/{selected_month}" | |
response = requests.get(url, timeout=10) | |
response.raise_for_status() | |
monthly_data = response.json() | |
self.current_monthly_data = monthly_data | |
# Process the payment data | |
payment_info = monthly_data.get('payments', [{}])[0] if monthly_data.get('payments') else {} | |
# Create monthly summary DataFrame | |
summary_data = { | |
'Metric': [ | |
'Month', | |
'Payment Count', | |
'Total Disbursed', | |
'Total Held', | |
'Estimate Number', | |
'Payment Type', | |
'Release Date' | |
], | |
'Value': [ | |
monthly_data.get('month', selected_month), | |
monthly_data.get('payment_count', 0), | |
f"${monthly_data.get('total_disbursed', 0):,.2f}", | |
f"${monthly_data.get('total_held', 0):,.2f}", | |
payment_info.get('estimate_number', 'N/A'), | |
payment_info.get('payment_type', 'N/A'), | |
payment_info.get('release_date', 'N/A') | |
] | |
} | |
summary_df = pd.DataFrame(summary_data) | |
# Create detailed items DataFrame | |
items_df = pd.DataFrame() | |
if payment_info and 'detailed_estimate' in payment_info: | |
detailed_estimate = payment_info['detailed_estimate'] | |
prime_items = detailed_estimate.get('prime_contract', {}).get('items', []) | |
if prime_items: | |
# Process items for display | |
items_data = [] | |
for item in prime_items: | |
if item.get('amounts', {}).get('this_period', 0) != 0: # Only show items with activity | |
items_data.append({ | |
'Item #': item.get('item_number', ''), | |
'Description': item.get('description', '')[:50] + '...' if len(item.get('description', '')) > 50 else item.get('description', ''), | |
'Unit': item.get('units', ''), | |
'Unit Price': f"${item.get('unit_price', 0):,.2f}", | |
'Qty This Period': f"{item.get('quantities', {}).get('this_period', 0):,.2f}", | |
'Amount This Period': f"${item.get('amounts', {}).get('this_period', 0):,.2f}", | |
'Job to Date': f"${item.get('amounts', {}).get('job_to_date', 0):,.2f}", | |
'% Complete': f"{item.get('quantities', {}).get('percent_jtd', 0):.1f}%" | |
}) | |
if items_data: | |
items_df = pd.DataFrame(items_data) | |
# Sort by amount this period (descending) | |
items_df['sort_value'] = items_df['Amount This Period'].str.replace('$', '').str.replace(',', '').astype(float) | |
items_df = items_df.sort_values('sort_value', ascending=False).drop('sort_value', axis=1) | |
else: | |
# Create empty dataframe with message | |
items_df = pd.DataFrame({'Message': ['No line items with activity for this period']}) | |
else: | |
items_df = pd.DataFrame({'Message': ['No line items available']}) | |
else: | |
items_df = pd.DataFrame({'Message': ['No line items available']}) | |
# Create deductions DataFrame | |
deductions_df = pd.DataFrame() | |
if payment_info and 'detailed_estimate' in payment_info: | |
deductions = payment_info['detailed_estimate'].get('deductions', []) | |
if deductions: | |
deductions_data = [] | |
for deduction in deductions: | |
deductions_data.append({ | |
'Estimate #': deduction.get('estimate_no', ''), | |
'Description': deduction.get('description', ''), | |
'This Period': f"${deduction.get('this_period', 0):,.2f}", | |
'Job to Date': f"${deduction.get('job_to_date', 0):,.2f}" | |
}) | |
deductions_df = pd.DataFrame(deductions_data) | |
else: | |
deductions_df = pd.DataFrame({'Message': ['No deductions for this period']}) | |
else: | |
deductions_df = pd.DataFrame({'Message': ['No deductions available']}) | |
# Create change orders DataFrame | |
change_orders_df = pd.DataFrame() | |
if payment_info and 'detailed_estimate' in payment_info: | |
change_orders = payment_info['detailed_estimate'].get('change_orders', {}).get('items', []) | |
if change_orders: | |
change_orders_data = [] | |
for co in change_orders: | |
change_orders_data.append({ | |
'Change Order #': co.get('change_order_no', ''), | |
'Description': co.get('description', '')[:50] + '...' if len(co.get('description', '')) > 50 else co.get('description', ''), | |
'This Period': f"${co.get('this_period_amt', 0):,.2f}", | |
'Job to Date': f"${co.get('job_to_date_amt', 0):,.2f}", | |
'Status': co.get('status', 'N/A') | |
}) | |
if change_orders_data: | |
change_orders_df = pd.DataFrame(change_orders_data) | |
# Sort by amount this period (descending) | |
change_orders_df['sort_value'] = change_orders_df['This Period'].str.replace('$', '').str.replace(',', '').astype(float) | |
change_orders_df = change_orders_df.sort_values('sort_value', ascending=False).drop('sort_value', axis=1) | |
else: | |
change_orders_df = pd.DataFrame({'Message': ['No change orders for this period']}) | |
else: | |
change_orders_df = pd.DataFrame({'Message': ['No change orders available']}) | |
else: | |
change_orders_df = pd.DataFrame({'Message': ['No change orders available']}) | |
# Create totals summary | |
if payment_info and 'detailed_estimate' in payment_info: | |
totals = payment_info['detailed_estimate'].get('overall_totals', {}) | |
totals_info = f""" | |
### Payment Totals for {selected_month} | |
- **Original Contract Amount:** ${totals.get('original', 0):,.2f} | |
- **This Period:** ${totals.get('this_period', 0):,.2f} | |
- **Job to Date:** ${totals.get('job_to_date', 0):,.2f} | |
- **Extra Work This Estimate:** ${totals.get('extra_work_this_estimate', 0):,.2f} | |
- **Extra Work to Date:** ${totals.get('extra_work_to_date', 0):,.2f} | |
""" | |
else: | |
totals_info = "No detailed totals available" | |
return summary_df, items_df, deductions_df, change_orders_df, totals_info | |
except requests.exceptions.RequestException as e: | |
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), f"Error fetching monthly details: {str(e)}" | |
except Exception as e: | |
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), f"Unexpected error: {str(e)}" | |
# Create instance of the viewer | |
viewer = ContractPaymentViewer() | |
# Create Gradio interface | |
with gr.Blocks(title="Contract Payment Viewer", theme=gr.themes.Soft()) as app: | |
gr.Markdown("# Contract Payment Viewer") | |
gr.Markdown("Enter a contract number to view payment history and detailed monthly breakdowns") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
contract_input = gr.Textbox( | |
label="Contract Number", | |
placeholder="e.g., 03-3F0704", | |
value="03-3F0704" | |
) | |
fetch_button = gr.Button("Fetch Payment History", variant="primary") | |
with gr.Row(): | |
with gr.Column(): | |
summary_output = gr.Markdown(label="Contract Summary") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
month_dropdown = gr.Dropdown( | |
label="Select Month", | |
choices=[], | |
visible=False | |
) | |
fetch_monthly_button = gr.Button("Get Monthly Details", variant="secondary", visible=False) | |
with gr.Row(): | |
with gr.Column(): | |
with gr.Accordion("Payment History", open=False): | |
history_table = gr.Dataframe( | |
label="Payment History", | |
wrap=True | |
) | |
# Monthly details section | |
with gr.Row(visible=False) as monthly_section: | |
with gr.Column(): | |
gr.Markdown("## Monthly Payment Details") | |
with gr.Tabs(): | |
with gr.TabItem("Summary"): | |
monthly_summary_table = gr.Dataframe( | |
label="Monthly Summary", | |
wrap=True | |
) | |
totals_output = gr.Markdown(label="Payment Totals") | |
with gr.TabItem("Line Items"): | |
items_table = gr.Dataframe( | |
label="Contract Items (This Period Activity)", | |
wrap=True | |
) | |
with gr.TabItem("Deductions"): | |
deductions_table = gr.Dataframe( | |
label="Deductions", | |
wrap=True | |
) | |
with gr.TabItem("Change Orders"): | |
change_orders_table = gr.Dataframe( | |
label="Change Orders", | |
wrap=True | |
) | |
# Event handlers | |
def handle_fetch_history(contract_number): | |
dropdown, summary, history = viewer.fetch_payment_history(contract_number) | |
return { | |
month_dropdown: dropdown, | |
summary_output: summary, | |
history_table: history, | |
fetch_monthly_button: gr.Button(visible=True if dropdown.choices else False), | |
monthly_section: gr.Row(visible=False) | |
} | |
def handle_fetch_monthly(contract_number, selected_month): | |
summary_df, items_df, deductions_df, change_orders_df, totals = viewer.fetch_monthly_details(contract_number, selected_month) | |
return { | |
monthly_summary_table: summary_df, | |
items_table: items_df, | |
deductions_table: deductions_df, | |
change_orders_table: change_orders_df, | |
totals_output: totals, | |
monthly_section: gr.Row(visible=True) | |
} | |
fetch_button.click( | |
fn=handle_fetch_history, | |
inputs=[contract_input], | |
outputs=[month_dropdown, summary_output, history_table, fetch_monthly_button, monthly_section] | |
) | |
fetch_monthly_button.click( | |
fn=handle_fetch_monthly, | |
inputs=[contract_input, month_dropdown], | |
outputs=[monthly_summary_table, items_table, deductions_table, change_orders_table, totals_output, monthly_section] | |
) | |
# Auto-fetch monthly details when dropdown changes | |
month_dropdown.change( | |
fn=handle_fetch_monthly, | |
inputs=[contract_input, month_dropdown], | |
outputs=[monthly_summary_table, items_table, deductions_table, change_orders_table, totals_output, monthly_section] | |
) | |
# Launch the app | |
if __name__ == "__main__": | |
app.launch( | |
share=True, | |
server_name="0.0.0.0", | |
server_port=7860, | |
show_error=True, | |
debug=False, | |
max_threads=10 | |
) |