Spaces:
Sleeping
Sleeping
| 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() |