pricing-matrix / app.py
buildinves's picture
Create app.py
361b49b verified
import gradio as gr
import pandas as pd
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
from datetime import datetime
import json
# Core pricing function matching your Excel formula
def calculate_price(lot_size, base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment,
user_adjustment=0):
"""Calculate price based on lot size using tiered pricing model"""
if lot_size >= base_size:
price = base_price + ((lot_size - base_size) / adjustment_increment) * over_adjustment
elif lot_size >= 250:
price = base_price + ((lot_size - base_size) / adjustment_increment) * under350_adjustment
else:
price = base_price + ((lot_size - base_size) / adjustment_increment) * under250_adjustment
price += user_adjustment
return np.ceil(price / 1000) * 1000
# Enhanced ML-based price predictor
def ml_price_suggestion(lot_size, location_score, market_trend, amenity_score):
"""Simple ML model for price suggestions based on additional factors"""
# Base calculation
base_suggestion = 255000 + (lot_size - 350) * 500
# Location multiplier (0.8 to 1.2)
location_mult = 0.8 + (location_score / 10) * 0.4
# Market trend adjustment (-10% to +10%)
trend_mult = 1 + (market_trend / 100)
# Amenity bonus (0 to 15%)
amenity_mult = 1 + (amenity_score / 10) * 0.15
final_price = base_suggestion * location_mult * trend_mult * amenity_mult
return np.ceil(final_price / 1000) * 1000
# Generate pricing table
def generate_pricing_table(base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment):
"""Generate comprehensive pricing table"""
lot_sizes = list(range(170, 580, 10))
prices = []
tiers = []
for size in lot_sizes:
price = calculate_price(size, base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment)
prices.append(price)
if size >= base_size:
tiers.append("Premium (≥350)")
elif size >= 250:
tiers.append("Standard (250-349)")
else:
tiers.append("Compact (<250)")
df = pd.DataFrame({
'Lot Size (sqm)': lot_sizes,
'Price ($)': prices,
'Price per sqm': [p/s for p, s in zip(prices, lot_sizes)],
'Tier': tiers
})
return df
# Visualization functions
def create_price_curve(df):
"""Create interactive price curve visualization"""
fig = px.line(df, x='Lot Size (sqm)', y='Price ($)',
color='Tier', markers=True,
title='Price Curve by Lot Size')
fig.update_layout(
hovermode='x unified',
xaxis_title="Lot Size (sqm)",
yaxis_title="Price ($)",
yaxis_tickformat='$,.0f'
)
return fig
def create_price_per_sqm(df):
"""Create price per sqm analysis"""
fig = px.scatter(df, x='Lot Size (sqm)', y='Price per sqm',
color='Tier', size='Price ($)',
title='Price per Square Meter Analysis')
fig.update_layout(
yaxis_tickformat='$,.0f',
xaxis_title="Lot Size (sqm)",
yaxis_title="Price per sqm ($)"
)
return fig
def create_sensitivity_analysis(base_size, base_price, adjustment_increment):
"""Create sensitivity analysis for different adjustment rates"""
lot_sizes = list(range(200, 500, 20))
scenarios = {
'Conservative': (15000, 30000, 35000),
'Current': (25000, 40000, 40000),
'Aggressive': (35000, 50000, 45000)
}
fig = go.Figure()
for scenario_name, (over, under350, under250) in scenarios.items():
prices = [calculate_price(size, base_size, base_price, adjustment_increment,
over, under350, under250) for size in lot_sizes]
fig.add_trace(go.Scatter(
x=lot_sizes, y=prices,
mode='lines+markers',
name=scenario_name
))
fig.update_layout(
title='Pricing Sensitivity Analysis',
xaxis_title='Lot Size (sqm)',
yaxis_title='Price ($)',
yaxis_tickformat='$,.0f',
hovermode='x unified'
)
return fig
# Main calculation function for Gradio
def calculate_all(lot_size, base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment,
user_adjustment, location_score, market_trend, amenity_score):
# Calculate traditional price
traditional_price = calculate_price(lot_size, base_size, base_price,
adjustment_increment, over_adjustment,
under350_adjustment, under250_adjustment,
user_adjustment)
# Calculate ML-suggested price
ml_price = ml_price_suggestion(lot_size, location_score, market_trend, amenity_score)
# Generate pricing table
df = generate_pricing_table(base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment)
# Create visualizations
price_curve = create_price_curve(df)
price_per_sqm = create_price_per_sqm(df)
sensitivity = create_sensitivity_analysis(base_size, base_price, adjustment_increment)
# Create detailed breakdown
if lot_size >= base_size:
tier = "Premium (≥350 sqm)"
adjustment_rate = over_adjustment
elif lot_size >= 250:
tier = "Standard (250-349 sqm)"
adjustment_rate = under350_adjustment
else:
tier = "Compact (<250 sqm)"
adjustment_rate = under250_adjustment
diff_from_base = lot_size - base_size
increments = diff_from_base / adjustment_increment
adjustment_amount = increments * adjustment_rate
breakdown = f"""
## Price Calculation Breakdown
**Lot Details:**
- Size: {lot_size} sqm
- Tier: {tier}
- Difference from base: {diff_from_base:+.1f} sqm
**Calculation:**
- Base Price: ${base_price:,.0f}
- Increments: {increments:.2f} × ${adjustment_rate:,.0f}
- Adjustment: ${adjustment_amount:,.0f}
- User Adjustment: ${user_adjustment:,.0f}
- **Final Price: ${traditional_price:,.0f}**
**ML-Enhanced Suggestion:** ${ml_price:,.0f}
- Location Factor: {location_score}/10
- Market Trend: {market_trend:+.0f}%
- Amenity Score: {amenity_score}/10
"""
# Create comparison table
comparison_df = pd.DataFrame({
'Pricing Method': ['Formula-Based', 'ML-Enhanced', 'Difference'],
'Price': [f'${traditional_price:,.0f}', f'${ml_price:,.0f}',
f'${abs(ml_price - traditional_price):,.0f}']
})
return (breakdown, comparison_df, price_curve, price_per_sqm, sensitivity)
# Create Gradio interface
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🏡 Real Estate Pricing Matrix Calculator
This enhanced version of your Excel pricing formula includes:
- **Traditional formula-based pricing** matching your Excel model
- **ML-enhanced pricing** considering location, market trends, and amenities
- **Interactive visualizations** for better decision making
- **Sensitivity analysis** for different pricing strategies
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Property Details")
lot_size = gr.Slider(150, 600, 350, step=0.5,
label="Lot Size (sqm)")
gr.Markdown("### ML Enhancement Factors")
location_score = gr.Slider(1, 10, 7, step=0.5,
label="Location Score (1-10)")
market_trend = gr.Slider(-20, 20, 0, step=1,
label="Market Trend (%)")
amenity_score = gr.Slider(1, 10, 5, step=0.5,
label="Amenity Score (1-10)")
with gr.Column(scale=1):
gr.Markdown("### Pricing Parameters")
base_size = gr.Number(350, label="Base Size (sqm)")
base_price = gr.Number(255000, label="Base Price ($)")
adjustment_increment = gr.Number(50, label="Adjustment Increment (sqm)")
gr.Markdown("### Adjustment Rates ($)")
over_adjustment = gr.Number(25000, label="Over Base Size")
under350_adjustment = gr.Number(40000, label="250-349 sqm")
under250_adjustment = gr.Number(40000, label="Under 250 sqm")
user_adjustment = gr.Number(0, label="User Adjustment ($)")
calculate_btn = gr.Button("Calculate Price", variant="primary")
with gr.Row():
with gr.Column():
breakdown_output = gr.Markdown()
comparison_output = gr.DataFrame()
with gr.Row():
price_curve_output = gr.Plot()
price_per_sqm_output = gr.Plot()
sensitivity_output = gr.Plot()
# Export functionality
gr.Markdown("### Export Options")
with gr.Row():
export_config = gr.JSON(label="Current Configuration")
export_btn = gr.Button("Export Configuration")
def export_configuration(base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment):
return {
"base_size": base_size,
"base_price": base_price,
"adjustment_increment": adjustment_increment,
"over_adjustment": over_adjustment,
"under350_adjustment": under350_adjustment,
"under250_adjustment": under250_adjustment,
"exported_at": datetime.now().isoformat()
}
calculate_btn.click(
calculate_all,
inputs=[lot_size, base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment,
user_adjustment, location_score, market_trend, amenity_score],
outputs=[breakdown_output, comparison_output, price_curve_output,
price_per_sqm_output, sensitivity_output]
)
export_btn.click(
export_configuration,
inputs=[base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment],
outputs=export_config
)
# Load default calculation on start
demo.load(
calculate_all,
inputs=[lot_size, base_size, base_price, adjustment_increment,
over_adjustment, under350_adjustment, under250_adjustment,
user_adjustment, location_score, market_trend, amenity_score],
outputs=[breakdown_output, comparison_output, price_curve_output,
price_per_sqm_output, sensitivity_output]
)
if __name__ == "__main__":
demo.launch()