Domain: Science, Environment & Research

You are given three CSV files in the working directory:

- `fluid_chemistry.csv`
- `leaf_telemetry.csv`
- `rack_registry.csv`

They describe a 60-rack vertical farm monitored over time.

Use only `fluid_chemistry.csv` and `leaf_telemetry.csv` for the required calculations. Join them on the exact key `['Timestamp', 'RackID']`.

After joining, compute the following 5 metrics exactly as defined below.

## Metrics

1. `EC_inv_detected`
Definition:
The minimum `EC_ms_cm` among all joined rows where `Leaf_Turgor_Index < 40`.

2. `turgor_loss_pct`
Definition:
- `baseline_turgor` = mean `Leaf_Turgor_Index` over all joined rows where `EC_ms_cm < 3.0`
- `shutdown_turgor` = mean `Leaf_Turgor_Index` over all joined rows where `EC_ms_cm > 4.2`
- `turgor_loss_pct = ((baseline_turgor - shutdown_turgor) / baseline_turgor) * 100`

3. `avg_transpiration_shutdown`
Definition:
The mean `Transpiration_Rate_gh` over all joined rows where `EC_ms_cm > 4.2`.

4. `ghost_hunger_ratio_pct`
Definition:
Using only `RackID == 14`, ordered ascending by `Timestamp`:

- `baseline_slope` = mean of first differences of `EC_ms_cm` over rows `0:200`
- `crisis_slope` = mean of first differences of `EC_ms_cm` over rows `300:500`
- `ghost_hunger_ratio_pct = (crisis_slope / baseline_slope) * 100`

Use simple adjacent first differences within each slice.

5. `critical_racks`
Definition:
The sorted list of unique `RackID` values for which any joined row has `Leaf_Turgor_Index < 20`.

## Output format

Return a single Python dictionary with exactly these keys:

```python
{
  'EC_inv_detected': float,
  'turgor_loss_pct': float,
  'avg_transpiration_shutdown': float,
  'ghost_hunger_ratio_pct': float,
  'critical_racks': list[int]
}
```

Formatting rules:

- Round all float values to exactly 2 decimal places.(round-half-to-even)
- `critical_racks` must be sorted ascending.
- Print only the final Python dictionary.

Grading rubric:
criterion: The final output is a single machine-parsable Python dictionary with exactly these keys: EC_inv_detected, turgor_loss_pct, avg_transpiration_shutdown, ghost_hunger_ratio_pct, critical_racks.
weight: 10
criterion: The solution correctly joins `fluid_chemistry.csv` and `leaf_telemetry.csv` on `Timestamp` and `RackID` before computing any metrics.
weight: 10
criterion: `EC_inv_detected` is exactly 4.69 after rounding to 2 decimals.
weight: 15
criterion: `turgor_loss_pct` is exactly 64.78 after rounding to 2 decimals.
weight: 15
criterion: `avg_transpiration_shutdown` is exactly 0.10 after rounding to 2 decimals.
weight: 10
criterion: `ghost_hunger_ratio_pct` is exactly 1589.36 after rounding to 2 decimals, using RackID 14 with the specified baseline and crisis EC slope windows.
weight: 20
criterion: `critical_racks` is exactly [14, 33, 58].
weight: 10
criterion: The final dictionary is exactly {'EC_inv_detected': 4.69, 'turgor_loss_pct': 64.78, 'avg_transpiration_shutdown': 0.1, 'ghost_hunger_ratio_pct': 1589.36, 'critical_racks': [14, 33, 58]}.
weight: 10

Golden answer:
```python
import csv
from pathlib import Path


BASE_DIR = Path(__file__).resolve().parent


def mean(values):
    return sum(values) / len(values)


def mean_diff(values):
    diffs = [values[index] - values[index - 1] for index in range(1, len(values))]
    return mean(diffs)


def solve():
    chemistry = {}
    with (BASE_DIR / "fluid_chemistry.csv").open(newline="", encoding="utf-8") as handle:
        reader = csv.DictReader(handle)
        for row in reader:
            key = (row["Timestamp"], int(row["RackID"]))
            chemistry[key] = {
                "EC_ms_cm": float(row["EC_ms_cm"]),
                "Res_Level_L": float(row["Res_Level_L"]),
            }

    joined_rows = []
    with (BASE_DIR / "leaf_telemetry.csv").open(newline="", encoding="utf-8") as handle:
        reader = csv.DictReader(handle)
        for row in reader:
            key = (row["Timestamp"], int(row["RackID"]))
            joined = {
                "Timestamp": row["Timestamp"],
                "RackID": int(row["RackID"]),
                "Transpiration_Rate_gh": float(row["Transpiration_Rate_gh"]),
                "Leaf_Turgor_Index": float(row["Leaf_Turgor_Index"]),
            }
            joined.update(chemistry[key])
            joined_rows.append(joined)

    ec_inv_detected = min(row["EC_ms_cm"] for row in joined_rows if row["Leaf_Turgor_Index"] < 40)

    baseline_turgor = mean([row["Leaf_Turgor_Index"] for row in joined_rows if row["EC_ms_cm"] < 3.0])
    shutdown_turgor = mean([row["Leaf_Turgor_Index"] for row in joined_rows if row["EC_ms_cm"] > 4.2])
    turgor_loss_pct = ((baseline_turgor - shutdown_turgor) / baseline_turgor) * 100.0

    avg_transpiration_shutdown = mean([row["Transpiration_Rate_gh"] for row in joined_rows if row["EC_ms_cm"] > 4.2])

    rack_14_rows = sorted((row for row in joined_rows if row["RackID"] == 14), key=lambda row: row["Timestamp"])
    baseline_slope = mean_diff([row["EC_ms_cm"] for row in rack_14_rows[0:200]])
    crisis_slope = mean_diff([row["EC_ms_cm"] for row in rack_14_rows[300:500]])
    ghost_hunger_ratio_pct = (crisis_slope / baseline_slope) * 100.0

    critical_racks = sorted({row["RackID"] for row in joined_rows if row["Leaf_Turgor_Index"] < 20})

    result = {
        "EC_inv_detected": round(ec_inv_detected, 2),
        "turgor_loss_pct": round(turgor_loss_pct, 2),
        "avg_transpiration_shutdown": round(avg_transpiration_shutdown, 2),
        "ghost_hunger_ratio_pct": round(ghost_hunger_ratio_pct, 2),
        "critical_racks": critical_racks,
    }
    print(result)
    return result


if __name__ == "__main__":
    solve()
```

**Reasoning**

The draft submission mixed a valid synthetic hydroponics scenario with a broken task statement and an incorrect final rubric entry. The actual workspace already contains the generated CSVs, so the correct task is to join `fluid_chemistry.csv` and `leaf_telemetry.csv` on `Timestamp` and `RackID`, then compute the five requested monitoring metrics from the joined rows. The osmotic inversion threshold is the minimum EC where leaf turgor drops below 40, turgor collapse is measured by comparing average turgor below 3.0 EC versus above 4.2 EC, shutdown transpiration is the mean transpiration above 4.2 EC, the ghost hunger ratio uses Rack 14 EC slopes before and during the crisis windows, and critical racks are those whose turgor ever falls below 20.

```python
{'EC_inv_detected': 4.69, 'turgor_loss_pct': 64.78, 'avg_transpiration_shutdown': 0.1, 'ghost_hunger_ratio_pct': 1589.36, 'critical_racks': [14, 33, 58]}
```