| |
|
| | import gradio as gr |
| | import pandas as pd |
| | import plotly.express as px |
| | import plotly.graph_objects as go |
| | import random |
| |
|
| | |
| | regions = ["Riyadh", "Makkah", "Eastern", "Madinah", "Qassim", "Asir", "Tabuk", "Hail", "Northern", |
| | "Jazan", "Najran", "Bahah", "Jawf"] |
| | income_bands = ["Low", "Mid", "High"] |
| | property_types = ["Land", "Off-plan", "Ready", "Self-build"] |
| |
|
| | def default_subsidy_values(): |
| | base = 300_000 |
| | factor = 0.5 |
| | return {(r, i, p): int((base - 100_000 * income_bands.index(i)) * factor) |
| | for r in regions for i in income_bands for p in property_types} |
| |
|
| | def default_supply_cost_values(): |
| | return {(p, r): {"Supply": random.randint(1000, 10000), "Discount (SAR)": random.randint(50_000, 300_000)} |
| | for p in ["Land", "Off-plan"] for r in regions} |
| |
|
| | def monte_carlo_optimization(subsidies, budget_limit, target_contracts, supply_dict, interest_rate, demand_increase, n_trials=10000): |
| | keys = list(subsidies.keys()) |
| | best_result = None |
| | best_score = float('-inf') |
| | fairness_penalty_weight = 10_000_000 |
| |
|
| | demand_multiplier = 1 + (demand_increase / 100) |
| | cost_multiplier = 1 + (interest_rate / 100) |
| |
|
| | for _ in range(n_trials): |
| | contracts = {} |
| | total_budget = 0 |
| | total_contracts = 0 |
| | shuffled_subs = {k: int(random.randint(150000, 400000) * cost_multiplier) for k in keys} |
| | available_supply = {(r, p): supply_dict.get((p, r), 0) for (p, r) in supply_dict} |
| |
|
| | for k in keys: |
| | r, i, p = k |
| | max_supply = available_supply.get((r, p), 10000) |
| | max_demand = int(max_supply * demand_multiplier) |
| | max_possible_contracts = int(min((budget_limit - total_budget) // shuffled_subs[k], max_demand)) if shuffled_subs[k] > 0 else 0 |
| | c = random.randint(0, max_possible_contracts) if max_possible_contracts > 0 else 0 |
| | contracts[k] = c |
| | total_budget += shuffled_subs[k] * c |
| | total_contracts += c |
| |
|
| | if total_budget > budget_limit: |
| | continue |
| |
|
| | achieved_regions = set(r for (r, i, p) in contracts if contracts[(r, i, p)] > 0) |
| | achieved_income = set(i for (r, i, p) in contracts if contracts[(r, i, p)] > 0) |
| | achieved_props = set(p for (r, i, p) in contracts if contracts[(r, i, p)] > 0) |
| | fairness_penalty = ( |
| | len(regions) - len(achieved_regions) + |
| | len(income_bands) - len(achieved_income) + |
| | len(property_types) - len(achieved_props) |
| | ) * fairness_penalty_weight |
| |
|
| | score = total_contracts - fairness_penalty |
| |
|
| | if score > best_score: |
| | best_score = score |
| | best_result = (contracts, total_budget, total_contracts, shuffled_subs) |
| |
|
| | return best_result if best_result else ({}, 0, 0, subsidies) |
| |
|
| | def create_gauge_chart(value, title, max_value=100): |
| | fig = go.Figure(go.Indicator( |
| | mode="gauge+number", |
| | value=value, |
| | gauge={ |
| | 'axis': {'range': [0, max_value]}, |
| | 'bar': {'color': "darkblue"}, |
| | 'steps': [ |
| | {'range': [0, max_value * 0.5], 'color': "lightgray"}, |
| | {'range': [max_value * 0.5, max_value], 'color': "lightgreen"} |
| | ] |
| | }, |
| | title={'text': title} |
| | )) |
| | fig.update_layout(height=350, width=450, margin=dict(t=40, b=40, l=40, r=40)) |
| | return fig |
| |
|
| | def build_app(): |
| | supply_costs = default_supply_cost_values() |
| |
|
| | with gr.Blocks() as app: |
| | gr.Markdown("# 🏨 Strategic Gears Housing Simulator – Auto Optimization") |
| |
|
| | with gr.Tab("Inputs"): |
| | with gr.Row(): |
| | with gr.Column(): |
| | ir = gr.Slider(0, 10, 5, label="Interest Rate (%)") |
| | dp = gr.Slider(0, 100, 10, label="Demand Increase (%)") |
| | n = gr.Slider(100, 10000, 1000, step=100, label="Number of Simulations") |
| | budget_limit = gr.Slider(10_000_000, 1_000_000_000, 500_000_000, step=10_000_000, label="Budget Limit (SAR)") |
| | target_contracts = gr.Slider(1000, 20000, 5000, step=100, label="Target Contracts") |
| | supply_inputs = {} |
| |
|
| | with gr.Accordion("Supply Inputs by Region and Property Type", open=False): |
| | for p in ["Land", "Off-plan"]: |
| | with gr.Accordion(p, open=False): |
| | for r in regions: |
| | supply_inputs[(p, r)] = gr.Slider(minimum=1, maximum=10000, |
| | value=supply_costs[(p, r)]["Supply"], |
| | step=1, |
| | label=f"{r} {p} Supply") |
| | run = gr.Button("Run Simulation") |
| |
|
| | with gr.Tab("Outputs"): |
| | summary = gr.Markdown("Optimization summary will appear here.") |
| |
|
| | with gr.Row(): |
| | with gr.Column(): |
| | df_subsidy_policy = gr.Dataframe(label="1️⃣ Recommended Subsidy Support (SAR)") |
| | subsidy_by_income_bar = gr.Plot(label="Average Subsidy by Income Band") |
| | with gr.Column(): |
| | df_contract_summary = gr.Dataframe(label="2️⃣ Contract Distribution") |
| | contracts_by_region_bar = gr.Plot(label="Contracts by Region") |
| |
|
| | with gr.Row(): |
| | with gr.Column(): |
| | df_budget_summary = gr.Dataframe(label="3️⃣ Budget Distribution") |
| | budget_util_gauge = gr.Plot(label="Budget Utilization (%)") |
| | with gr.Column(): |
| | df_discount_table = gr.Dataframe(label="4️⃣ Discount Table") |
| | contracts_by_property_pie = gr.Plot(label="Contract Distribution by Property Type") |
| | target_achievement_gauge = gr.Plot(label="Target Achievement (%)") |
| | total_contracts_gauge = gr.Plot(label="Total Contracts (Scaled to Target)") |
| |
|
| | def run_sim(interest, demand, sims, budget, target, *supplies): |
| | supply_dict = {(p, r): supplies[i] for i, (p, r) in enumerate(supply_inputs)} |
| | result, total_bgt, total_con, final_subs = monte_carlo_optimization( |
| | default_subsidy_values(), budget, target, supply_dict, interest, demand, sims |
| | ) |
| |
|
| | df = pd.DataFrame([{ |
| | "Region": r, "Income Band": i, "Property Type": p, |
| | "Contracts": result.get((r, i, p), 0), |
| | "Subsidy (SAR)": final_subs[(r, i, p)], |
| | "Budget (SAR)": result.get((r, i, p), 0) * final_subs[(r, i, p)] |
| | } for (r, i, p) in final_subs]) |
| |
|
| | subsidy_df = df[["Region", "Income Band", "Property Type", "Subsidy (SAR)"]].sort_values(by="Subsidy (SAR)", ascending=False) |
| | contract_df = df[["Region", "Income Band", "Property Type", "Contracts"]].sort_values(by="Contracts", ascending=False) |
| | budget_df = df[["Region", "Income Band", "Property Type", "Budget (SAR)"]].sort_values(by="Budget (SAR)", ascending=False) |
| |
|
| | df_discount = pd.DataFrame([{ |
| | "Region": r, "Property Type": p, |
| | "Discount (SAR)": random.randint(50_000, 300_000) |
| | } for p in ["Land", "Off-plan"] for r in regions]) |
| |
|
| | summary_text = f""" |
| | **Optimization Summary:** |
| | - Total Budget Used: {total_bgt:,.0f} SAR |
| | - Budget Utilization: {(total_bgt / budget) * 100:.1f}% |
| | - Total Contracts: {total_con:,} |
| | - Target Achievement: {(total_con / target) * 100:.1f}% |
| | """ |
| |
|
| | |
| | budget_util_pct = (total_bgt / budget) * 100 |
| | target_achieved_pct = (total_con / target) * 100 |
| | total_contracts_scaled = min(100, (total_con / target) * 100) |
| |
|
| | gauge_budget = create_gauge_chart(budget_util_pct, "Budget Utilization (%)") |
| | gauge_target = create_gauge_chart(target_achieved_pct, "Target Achievement (%)") |
| | gauge_contracts = create_gauge_chart(total_contracts_scaled, "Contracts (Scaled to Target %)") |
| |
|
| | |
| | contracts_region = df.groupby("Region")["Contracts"].sum().reset_index() |
| | bar_contract_region = px.bar(contracts_region, x="Region", y="Contracts", title="Contracts by Region") |
| | min_y = 10 |
| | max_y = contracts_region["Contracts"].max() * 1.1 |
| | bar_contract_region.update_layout( |
| | height=350, width=450, |
| | yaxis=dict(title="Contracts", tickformat=",d", range=[min_y, max_y]), |
| | margin=dict(t=50, b=40, l=60, r=40) |
| | ) |
| |
|
| | |
| | subsidy_income = df.groupby("Income Band")["Subsidy (SAR)"].mean().reset_index() |
| | bar_subsidy_income = px.bar(subsidy_income, x="Income Band", y="Subsidy (SAR)", title="Average Subsidy by Income Band") |
| | bar_subsidy_income.update_layout( |
| | height=350, width=450, |
| | yaxis=dict(tickprefix="SAR ", tickformat="~s"), |
| | margin=dict(t=50, b=40, l=60, r=40) |
| | ) |
| |
|
| | |
| | contracts_property = df.groupby("Property Type")["Contracts"].sum().reset_index() |
| | pie_property = px.pie(contracts_property, names="Property Type", values="Contracts", title="Contract Distribution by Property Type") |
| | pie_property.update_layout(height=350, width=450, margin=dict(t=50, b=40, l=40, r=40)) |
| |
|
| | return summary_text, subsidy_df, contract_df, budget_df, df_discount, gauge_budget, gauge_target, gauge_contracts, bar_contract_region, bar_subsidy_income, pie_property |
| |
|
| | run.click( |
| | fn=run_sim, |
| | inputs=[ir, dp, n, budget_limit, target_contracts] + list(supply_inputs.values()), |
| | outputs=[ |
| | summary, df_subsidy_policy, df_contract_summary, df_budget_summary, df_discount_table, |
| | budget_util_gauge, target_achievement_gauge, total_contracts_gauge, |
| | contracts_by_region_bar, subsidy_by_income_bar, contracts_by_property_pie |
| | ] |
| | ) |
| |
|
| | return app |
| |
|
| | app = build_app() |
| | app.launch() |
| |
|