malcolmSQ commited on
Commit ·
efb9a96
1
Parent(s): bc8f256
Fix download functionality with working CSV generation - Implement proper download functions that generate fresh CSV files - Use current parameter values to regenerate data for download - Create properly formatted CSV files with clean numeric data - Fix button click handlers to actually work - Update environment with correct Gradio version
Browse files- README.md +5 -0
- dashboard/app.py +145 -77
- environment.yml +16 -0
- requirements.txt +1 -1
README.md
CHANGED
|
@@ -25,11 +25,16 @@ This project implements a flexible, modular dashboard for mangrove carbon seques
|
|
| 25 |
|
| 26 |
1. **Install dependencies:**
|
| 27 |
```bash
|
|
|
|
| 28 |
conda env create -f environment.yml
|
| 29 |
conda activate er-model
|
|
|
|
|
|
|
|
|
|
| 30 |
```
|
| 31 |
2. **Run the dashboard:**
|
| 32 |
```bash
|
|
|
|
| 33 |
python dashboard/app.py
|
| 34 |
```
|
| 35 |
The terminal will display a public share link.
|
|
|
|
| 25 |
|
| 26 |
1. **Install dependencies:**
|
| 27 |
```bash
|
| 28 |
+
# Method 1: Using conda (recommended)
|
| 29 |
conda env create -f environment.yml
|
| 30 |
conda activate er-model
|
| 31 |
+
|
| 32 |
+
# Method 2: Using pip in existing environment
|
| 33 |
+
pip install -r requirements.txt
|
| 34 |
```
|
| 35 |
2. **Run the dashboard:**
|
| 36 |
```bash
|
| 37 |
+
conda activate er-model # if using conda
|
| 38 |
python dashboard/app.py
|
| 39 |
```
|
| 40 |
The terminal will display a public share link.
|
dashboard/app.py
CHANGED
|
@@ -441,16 +441,20 @@ This dashboard visualizes these values annually over the project duration, provi
|
|
| 441 |
with gr.Tabs():
|
| 442 |
with gr.Tab("Project Results (Annual)"):
|
| 443 |
results_box = gr.Dataframe(label="Project Results (Annual)")
|
| 444 |
-
|
|
|
|
| 445 |
with gr.Tab("Species Results (Annual)"):
|
| 446 |
species_box = gr.Dataframe(label="Species Results (Annual)")
|
| 447 |
-
|
|
|
|
| 448 |
with gr.Tab("Surviving Trees Table"):
|
| 449 |
survival_box = gr.Dataframe(label="Surviving Trees Table")
|
| 450 |
-
|
|
|
|
| 451 |
with gr.Tab("Biomass Table"):
|
| 452 |
biomass_debug_table = gr.Dataframe(label="Biomass Table (inputs & outputs per year)")
|
| 453 |
-
|
|
|
|
| 454 |
# --- End tabbed tables ---
|
| 455 |
# Update the update_declining_increment callback to use these new inputs
|
| 456 |
def update_declining_increment(y1, y2, y3, y4, y5,
|
|
@@ -491,12 +495,6 @@ This dashboard visualizes these values annually over the project duration, provi
|
|
| 491 |
survival_table = to_native(survival_table)
|
| 492 |
biomass_debug_df = to_native(biomass_debug_df)
|
| 493 |
|
| 494 |
-
# Store raw data for downloads (before formatting)
|
| 495 |
-
update_declining_increment._last_results = results
|
| 496 |
-
update_declining_increment._last_species_results = species_results
|
| 497 |
-
update_declining_increment._last_survival_table = model.species_metrics.pivot(index="Year", columns="Species", values="Surviving Trees").reset_index()
|
| 498 |
-
update_declining_increment._last_biomass_table = model.species_metrics
|
| 499 |
-
|
| 500 |
# --- Ensure all other values are native types ---
|
| 501 |
if hasattr(summary, 'item'):
|
| 502 |
summary = summary.item()
|
|
@@ -510,100 +508,170 @@ This dashboard visualizes these values annually over the project duration, provi
|
|
| 510 |
)
|
| 511 |
|
| 512 |
# Download functionality for tables
|
| 513 |
-
def
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 519 |
try:
|
| 520 |
# Try to convert formatted strings back to numbers
|
| 521 |
-
|
| 522 |
except:
|
| 523 |
pass # Keep as string if conversion fails
|
| 524 |
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
temp_file.close()
|
| 528 |
-
return temp_file.name
|
| 529 |
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
|
| 531 |
-
def download_species_csv():
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
|
|
|
| 542 |
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
|
| 549 |
-
def download_survival_csv():
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
|
|
|
| 560 |
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
|
| 567 |
-
def download_biomass_csv():
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
|
|
|
| 578 |
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
|
|
|
|
| 585 |
download_results_btn.click(
|
| 586 |
download_results_csv,
|
| 587 |
-
inputs=[
|
| 588 |
-
|
|
|
|
|
|
|
| 589 |
)
|
| 590 |
|
| 591 |
download_species_btn.click(
|
| 592 |
download_species_csv,
|
| 593 |
-
inputs=[
|
| 594 |
-
|
|
|
|
|
|
|
| 595 |
)
|
| 596 |
|
| 597 |
download_survival_btn.click(
|
| 598 |
download_survival_csv,
|
| 599 |
-
inputs=[
|
| 600 |
-
|
|
|
|
|
|
|
| 601 |
)
|
| 602 |
|
| 603 |
download_biomass_btn.click(
|
| 604 |
download_biomass_csv,
|
| 605 |
-
inputs=[
|
| 606 |
-
|
|
|
|
|
|
|
| 607 |
)
|
| 608 |
|
| 609 |
# Show initial results
|
|
|
|
| 441 |
with gr.Tabs():
|
| 442 |
with gr.Tab("Project Results (Annual)"):
|
| 443 |
results_box = gr.Dataframe(label="Project Results (Annual)")
|
| 444 |
+
download_results_file = gr.File(label="Download CSV", visible=False)
|
| 445 |
+
download_results_btn = gr.Button("📥 Download CSV", variant="secondary")
|
| 446 |
with gr.Tab("Species Results (Annual)"):
|
| 447 |
species_box = gr.Dataframe(label="Species Results (Annual)")
|
| 448 |
+
download_species_file = gr.File(label="Download CSV", visible=False)
|
| 449 |
+
download_species_btn = gr.Button("📥 Download CSV", variant="secondary")
|
| 450 |
with gr.Tab("Surviving Trees Table"):
|
| 451 |
survival_box = gr.Dataframe(label="Surviving Trees Table")
|
| 452 |
+
download_survival_file = gr.File(label="Download CSV", visible=False)
|
| 453 |
+
download_survival_btn = gr.Button("📥 Download CSV", variant="secondary")
|
| 454 |
with gr.Tab("Biomass Table"):
|
| 455 |
biomass_debug_table = gr.Dataframe(label="Biomass Table (inputs & outputs per year)")
|
| 456 |
+
download_biomass_file = gr.File(label="Download CSV", visible=False)
|
| 457 |
+
download_biomass_btn = gr.Button("📥 Download CSV", variant="secondary")
|
| 458 |
# --- End tabbed tables ---
|
| 459 |
# Update the update_declining_increment callback to use these new inputs
|
| 460 |
def update_declining_increment(y1, y2, y3, y4, y5,
|
|
|
|
| 495 |
survival_table = to_native(survival_table)
|
| 496 |
biomass_debug_df = to_native(biomass_debug_df)
|
| 497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
# --- Ensure all other values are native types ---
|
| 499 |
if hasattr(summary, 'item'):
|
| 500 |
summary = summary.item()
|
|
|
|
| 508 |
)
|
| 509 |
|
| 510 |
# Download functionality for tables
|
| 511 |
+
def create_download_file(df, filename):
|
| 512 |
+
"""Create a temporary CSV file for download"""
|
| 513 |
+
import tempfile
|
| 514 |
+
import os
|
| 515 |
+
|
| 516 |
+
# Create a temporary file
|
| 517 |
+
temp_dir = tempfile.gettempdir()
|
| 518 |
+
filepath = os.path.join(temp_dir, filename)
|
| 519 |
+
|
| 520 |
+
# Convert formatted strings back to numbers where possible
|
| 521 |
+
if df is not None and not df.empty:
|
| 522 |
+
df_clean = df.copy()
|
| 523 |
+
for col in df_clean.columns:
|
| 524 |
+
if df_clean[col].dtype == 'object' and col.lower() != 'year':
|
| 525 |
try:
|
| 526 |
# Try to convert formatted strings back to numbers
|
| 527 |
+
df_clean[col] = df_clean[col].astype(str).str.replace(',', '').astype(float)
|
| 528 |
except:
|
| 529 |
pass # Keep as string if conversion fails
|
| 530 |
|
| 531 |
+
df_clean.to_csv(filepath, index=False)
|
| 532 |
+
return filepath
|
|
|
|
|
|
|
| 533 |
return None
|
| 534 |
+
|
| 535 |
+
def download_results_csv(y1, y2, y3, y4, y5, planting_density_A, planting_density_B, *mortality_inputs_and_soil_carbon):
|
| 536 |
+
"""Generate and download project results CSV"""
|
| 537 |
+
try:
|
| 538 |
+
# Recreate the results using current parameters
|
| 539 |
+
*mortality_inputs, soil_carbon = mortality_inputs_and_soil_carbon
|
| 540 |
+
config = update_planting_schedule(MODEL_CONFIGS["Declining Increment"], [y1, y2, y3, y4, y5])
|
| 541 |
+
config['species'][0]['planting_density'] = planting_density_A
|
| 542 |
+
config['species'][1]['planting_density'] = planting_density_B
|
| 543 |
+
for i in [0, 1]:
|
| 544 |
+
for yr_idx, yr in enumerate(range(1, 11)):
|
| 545 |
+
config['species'][i]['mortality_rates'][f'year_{yr}'] = mortality_inputs[yr_idx]
|
| 546 |
+
config['species'][i]['mortality_rates']['subsequent'] = mortality_inputs[10]
|
| 547 |
+
config['carbon']['soil_carbon_per_ha_per_year'] = soil_carbon
|
| 548 |
+
|
| 549 |
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp:
|
| 550 |
+
yaml.dump(config, tmp)
|
| 551 |
+
tmp_path = tmp.name
|
| 552 |
+
model = ERModel(Path(tmp_path))
|
| 553 |
+
results, _ = model.run()
|
| 554 |
+
|
| 555 |
+
filepath = create_download_file(results, "project_results.csv")
|
| 556 |
+
return gr.File(value=filepath, visible=True) if filepath else gr.File(visible=False)
|
| 557 |
+
except Exception as e:
|
| 558 |
+
print(f"Error creating download: {e}")
|
| 559 |
+
return gr.File(visible=False)
|
| 560 |
|
| 561 |
+
def download_species_csv(y1, y2, y3, y4, y5, planting_density_A, planting_density_B, *mortality_inputs_and_soil_carbon):
|
| 562 |
+
"""Generate and download species results CSV"""
|
| 563 |
+
try:
|
| 564 |
+
*mortality_inputs, soil_carbon = mortality_inputs_and_soil_carbon
|
| 565 |
+
config = update_planting_schedule(MODEL_CONFIGS["Declining Increment"], [y1, y2, y3, y4, y5])
|
| 566 |
+
config['species'][0]['planting_density'] = planting_density_A
|
| 567 |
+
config['species'][1]['planting_density'] = planting_density_B
|
| 568 |
+
for i in [0, 1]:
|
| 569 |
+
for yr_idx, yr in enumerate(range(1, 11)):
|
| 570 |
+
config['species'][i]['mortality_rates'][f'year_{yr}'] = mortality_inputs[yr_idx]
|
| 571 |
+
config['species'][i]['mortality_rates']['subsequent'] = mortality_inputs[10]
|
| 572 |
+
config['carbon']['soil_carbon_per_ha_per_year'] = soil_carbon
|
| 573 |
|
| 574 |
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp:
|
| 575 |
+
yaml.dump(config, tmp)
|
| 576 |
+
tmp_path = tmp.name
|
| 577 |
+
model = ERModel(Path(tmp_path))
|
| 578 |
+
_, species_results = model.run()
|
| 579 |
+
|
| 580 |
+
filepath = create_download_file(species_results, "species_results.csv")
|
| 581 |
+
return gr.File(value=filepath, visible=True) if filepath else gr.File(visible=False)
|
| 582 |
+
except Exception as e:
|
| 583 |
+
print(f"Error creating download: {e}")
|
| 584 |
+
return gr.File(visible=False)
|
| 585 |
|
| 586 |
+
def download_survival_csv(y1, y2, y3, y4, y5, planting_density_A, planting_density_B, *mortality_inputs_and_soil_carbon):
|
| 587 |
+
"""Generate and download survival table CSV"""
|
| 588 |
+
try:
|
| 589 |
+
*mortality_inputs, soil_carbon = mortality_inputs_and_soil_carbon
|
| 590 |
+
config = update_planting_schedule(MODEL_CONFIGS["Declining Increment"], [y1, y2, y3, y4, y5])
|
| 591 |
+
config['species'][0]['planting_density'] = planting_density_A
|
| 592 |
+
config['species'][1]['planting_density'] = planting_density_B
|
| 593 |
+
for i in [0, 1]:
|
| 594 |
+
for yr_idx, yr in enumerate(range(1, 11)):
|
| 595 |
+
config['species'][i]['mortality_rates'][f'year_{yr}'] = mortality_inputs[yr_idx]
|
| 596 |
+
config['species'][i]['mortality_rates']['subsequent'] = mortality_inputs[10]
|
| 597 |
+
config['carbon']['soil_carbon_per_ha_per_year'] = soil_carbon
|
| 598 |
|
| 599 |
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp:
|
| 600 |
+
yaml.dump(config, tmp)
|
| 601 |
+
tmp_path = tmp.name
|
| 602 |
+
model = ERModel(Path(tmp_path))
|
| 603 |
+
model.run()
|
| 604 |
+
survival_table = create_survival_table(model)
|
| 605 |
+
|
| 606 |
+
# Convert survival table to raw numbers
|
| 607 |
+
df_raw = model.species_metrics.copy()
|
| 608 |
+
surv_raw = df_raw.pivot(index="Year", columns="Species", values="Surviving Trees")
|
| 609 |
+
surv_raw.columns = [SPECIES_DISPLAY_NAMES.get(sp, sp) for sp in surv_raw.columns]
|
| 610 |
+
surv_raw["Total Surviving Trees"] = surv_raw.sum(axis=1)
|
| 611 |
+
surv_raw = surv_raw.reset_index()
|
| 612 |
+
|
| 613 |
+
filepath = create_download_file(surv_raw, "surviving_trees.csv")
|
| 614 |
+
return gr.File(value=filepath, visible=True) if filepath else gr.File(visible=False)
|
| 615 |
+
except Exception as e:
|
| 616 |
+
print(f"Error creating download: {e}")
|
| 617 |
+
return gr.File(visible=False)
|
| 618 |
|
| 619 |
+
def download_biomass_csv(y1, y2, y3, y4, y5, planting_density_A, planting_density_B, *mortality_inputs_and_soil_carbon):
|
| 620 |
+
"""Generate and download biomass table CSV"""
|
| 621 |
+
try:
|
| 622 |
+
*mortality_inputs, soil_carbon = mortality_inputs_and_soil_carbon
|
| 623 |
+
config = update_planting_schedule(MODEL_CONFIGS["Declining Increment"], [y1, y2, y3, y4, y5])
|
| 624 |
+
config['species'][0]['planting_density'] = planting_density_A
|
| 625 |
+
config['species'][1]['planting_density'] = planting_density_B
|
| 626 |
+
for i in [0, 1]:
|
| 627 |
+
for yr_idx, yr in enumerate(range(1, 11)):
|
| 628 |
+
config['species'][i]['mortality_rates'][f'year_{yr}'] = mortality_inputs[yr_idx]
|
| 629 |
+
config['species'][i]['mortality_rates']['subsequent'] = mortality_inputs[10]
|
| 630 |
+
config['carbon']['soil_carbon_per_ha_per_year'] = soil_carbon
|
| 631 |
|
| 632 |
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp:
|
| 633 |
+
yaml.dump(config, tmp)
|
| 634 |
+
tmp_path = tmp.name
|
| 635 |
+
model = ERModel(Path(tmp_path))
|
| 636 |
+
model.run()
|
| 637 |
+
|
| 638 |
+
filepath = create_download_file(model.species_metrics, "biomass_table.csv")
|
| 639 |
+
return gr.File(value=filepath, visible=True) if filepath else gr.File(visible=False)
|
| 640 |
+
except Exception as e:
|
| 641 |
+
print(f"Error creating download: {e}")
|
| 642 |
+
return gr.File(visible=False)
|
| 643 |
|
| 644 |
+
# Connect download buttons to functions
|
| 645 |
download_results_btn.click(
|
| 646 |
download_results_csv,
|
| 647 |
+
inputs=[year_1, year_2, year_3, year_4, year_5,
|
| 648 |
+
planting_density_A, planting_density_B,
|
| 649 |
+
*mort_vars, mort_sub, soil_carbon],
|
| 650 |
+
outputs=[download_results_file]
|
| 651 |
)
|
| 652 |
|
| 653 |
download_species_btn.click(
|
| 654 |
download_species_csv,
|
| 655 |
+
inputs=[year_1, year_2, year_3, year_4, year_5,
|
| 656 |
+
planting_density_A, planting_density_B,
|
| 657 |
+
*mort_vars, mort_sub, soil_carbon],
|
| 658 |
+
outputs=[download_species_file]
|
| 659 |
)
|
| 660 |
|
| 661 |
download_survival_btn.click(
|
| 662 |
download_survival_csv,
|
| 663 |
+
inputs=[year_1, year_2, year_3, year_4, year_5,
|
| 664 |
+
planting_density_A, planting_density_B,
|
| 665 |
+
*mort_vars, mort_sub, soil_carbon],
|
| 666 |
+
outputs=[download_survival_file]
|
| 667 |
)
|
| 668 |
|
| 669 |
download_biomass_btn.click(
|
| 670 |
download_biomass_csv,
|
| 671 |
+
inputs=[year_1, year_2, year_3, year_4, year_5,
|
| 672 |
+
planting_density_A, planting_density_B,
|
| 673 |
+
*mort_vars, mort_sub, soil_carbon],
|
| 674 |
+
outputs=[download_biomass_file]
|
| 675 |
)
|
| 676 |
|
| 677 |
# Show initial results
|
environment.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: er-model
|
| 2 |
+
channels:
|
| 3 |
+
- conda-forge
|
| 4 |
+
- defaults
|
| 5 |
+
dependencies:
|
| 6 |
+
- python=3.10
|
| 7 |
+
- pip
|
| 8 |
+
- pip:
|
| 9 |
+
- click>=8.1.7
|
| 10 |
+
- gradio>=4.0.0
|
| 11 |
+
- matplotlib>=3.8.2
|
| 12 |
+
- numpy>=1.26.3
|
| 13 |
+
- pandas>=2.2.0
|
| 14 |
+
- plotly>=5.0.0
|
| 15 |
+
- pyyaml>=6.0.1
|
| 16 |
+
- scipy>=1.12.0
|
requirements.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
click>=8.1.7
|
| 2 |
-
gradio>=
|
| 3 |
matplotlib>=3.8.2
|
| 4 |
numpy>=1.26.3
|
| 5 |
pandas>=2.2.0
|
|
|
|
| 1 |
click>=8.1.7
|
| 2 |
+
gradio>=4.0.0
|
| 3 |
matplotlib>=3.8.2
|
| 4 |
numpy>=1.26.3
|
| 5 |
pandas>=2.2.0
|