Skip to content

Greenhouse Project Management

The greenflux.projects package defines a project as a stable object that can be saved, loaded, validated and converted into executable model parameters.

Project Layers

  • SiteDefinition: location, timezone and weather/data source metadata.
  • GreenhouseGeometryDefinition: floor area, heights, zones, spans, bays and derived volume.
  • EnvelopeDefinition: cover and screen thermal/solar parameters. It can also define explicit EnvelopeBoundaryDefinition surfaces, which are reduced to an effective cover U only when a model requires that scalar.
  • EquipmentDefinition: lighting, screens, ventilation, humidity control, CO2, irrigation/fertigation and pipe heat split.
  • CropDefinition: crop type, cultivar and tomato crop/fruit-development parameters.
  • SimulationCaseDefinition: named simulation windows and model target.
  • GreenhouseCalibrationProfile: objective score, record counts and calibrated replay multipliers attached to a project.
  • GreenhouseProject: the full project definition.

Example

from greenflux.projects.builder import (
    build_greenhouse_unit,
    initial_greenhouse_unit_state,
    resolve_project_weather,
    simulate_project_greenhouse_unit,
)
from greenflux.projects.runs import ProjectWorkspace, run_project_greenhouse_unit_case
from greenflux.projects.schema import (
    CO2SourceDefinition,
    EnvelopeBoundaryDefinition,
    EnvelopeDefinition,
    EquipmentDefinition,
    GreenhouseGeometryDefinition,
    GreenhouseProject,
    HumidityControlDefinition,
    IrrigationDefinition,
    LightingDefinition,
    ScreenDefinition,
    SimulationCaseDefinition,
    SiteDefinition,
    VentilationDefinition,
    WeatherFileDefinition,
)
from greenflux.projects.store import ProjectStore

project = GreenhouseProject(
    project_id="bleiswijk_tomato",
    name="Bleiswijk tomato",
    site=SiteDefinition(
        name="WUR Bleiswijk",
        latitude_deg=52.03,
        longitude_deg=4.53,
        timezone="Europe/Amsterdam",
        weather_source="greenlight_led_hps",
        weather_file=WeatherFileDefinition(
            path="weather/bleiswijk.epw",
            file_type="epw",
            coerce_year=2020,
        ),
    ),
    geometry=GreenhouseGeometryDefinition(
        floor_area_m2=144.0,
        gutter_height_m=4.0,
        roof_height_m=5.0,
        zone_count=2,
    ),
    envelope=EnvelopeDefinition(
        boundaries=(
            EnvelopeBoundaryDefinition(name="roof", area_m2=156.0, u_w_m2_k=1.8),
            EnvelopeBoundaryDefinition(
                name="corridor_wall",
                area_m2=80.0,
                u_w_m2_k=4.0,
                outdoor_exposure_fraction=0.25,
            ),
        ),
    ),
    equipment=EquipmentDefinition(
        lighting=LightingDefinition(
            fixture_type="led",
            installed_power_w_m2=100.0,
            par_efficiency_umol_j=2.7,
        ),
        screens=(
            ScreenDefinition(name="thermal_screen", screen_type="thermal"),
            ScreenDefinition(name="blackout_screen", screen_type="blackout"),
        ),
        ventilation=VentilationDefinition(
            roof_vent_area_m2=18.0,
            fan_max_flow_m3_s=8.0,
        ),
        humidity=HumidityControlDefinition(
            dehumidifier_capacity_kg_m2_s=1.0e-5,
            fogging_capacity_kg_m2_s=2.0e-5,
        ),
        co2=CO2SourceDefinition(source_type="tank", capacity_mg_m2_s=20.0),
        irrigation=IrrigationDefinition(
            emitter_flow_l_h=2.0,
            emitters_per_m2=4.0,
            ec_ds_m=2.5,
            ph=5.8,
        ),
    ),
    simulations=(
        SimulationCaseDefinition(
            name="winter_day",
            stop_s=86400.0,
            dt_s=300.0,
            model="greenhouse_unit",
            forcing_source="greenlight:HPS",
        ),
    ),
)

ProjectStore("projects").save(project)
unit = build_greenhouse_unit(project)
inventory = project.equipment_inventory()
state = initial_greenhouse_unit_state(project)
weather = resolve_project_weather(project, case_name="winter_day")
result = simulate_project_greenhouse_unit(project, case_name="winter_day", weather=weather)
run = run_project_greenhouse_unit_case(
    "projects/bleiswijk_tomato/project.json",
    case_name="winter_day",
    run_id="baseline",
)
workspace = ProjectWorkspace.from_project_dir("projects/bleiswijk_tomato")
sdk_run = workspace.run_greenhouse_unit_case(run_id="sdk_baseline")

Project-Local Runs

Executable project work should live with the project, not in examples/.

Each project can have:

  • project.json: the stable project definition.
  • scripts/run_simulation.py: a thin launcher for that project.
  • results/<case_name>/<run_id>/: local generated outputs ignored by git.

The standard project runner writes:

  • timeseries.csv: weather inputs, greenhouse state, crop state and key heat/vapour/CO2 fluxes.
  • summary.json: project id, case, run id, record count and final state metrics.
  • resources/resource_statistics.json and resources/resource_statistics.md: simulated heat, electricity, CO2, irrigation and drain accounting integrated over the run horizon.
  • resources/resource_comparison.json and resources/resource_comparison.md when the case uses an AGC forcing source and the matching data/raw/agc_2019/<team>/Resources.csv table is available.
  • optional PNG figures with --plots or plots=True:
  • climate.png: indoor/outdoor temperature, vapour pressure and CO2.
  • actuation.png: roof, screen, heating and lighting traces.
  • crop.png: crop biomass pools and selected crop/energy fluxes.

Project resource accounting uses the project equipment definitions and runner controls. Constant controls can be passed through GreenhouseUnitWeatherControls for heating, lighting, CO2 dosing, humidity equipment, irrigation control and target drainage fraction. If no irrigation control is supplied, simulated irrigation remains zero and any measured-resource comparison will expose that gap instead of filling it implicitly.

When a simulation case uses forcing_source="greenlight:<installation>" or forcing_source="agc:<team>", the runner also tries to load the corresponding measured actuator/control records from data/raw/. If available, those records drive roof opening, screen closure, lighting power, CO2 dosing and a grow-pipe heating proxy using the project's calibration profile. Use --constant-controls in the CLI, or use_measured_controls=False in the SDK, when a controlled synthetic run should ignore measured actuator traces.

Examples:

uv run --extra plots greenflux project-run projects/greenlight_hps_bleiswijk/project.json --run-id baseline --plots
uv run --extra plots python projects/greenlight_hps_bleiswijk/scripts/run_simulation.py --run-id baseline --plots
uv run greenflux project-run projects/greenlight_hps_bleiswijk/project.json --run-id constant_controls --constant-controls --heating-power-w-m2 80

Both commands write to:

projects/greenlight_hps_bleiswijk/results/measured_open_loop/baseline/

examples/ remains for reusable demos, calibration utilities and package walkthroughs. Project-specific analysis should start from the project-local scripts and place generated artifacts under that project's results/ folder.

For Python-first workflows, use the SDK workspace:

from greenflux.projects.runs import ProjectWorkspace

workspace = ProjectWorkspace.from_project_dir("projects/greenlight_hps_bleiswijk")
run = workspace.run_greenhouse_unit_case(run_id="baseline", plots=True)

print(run.paths.timeseries_csv)
print(run.paths.resource_statistics_report)
print(run.paths.figures)
print(run.result.final_state.co2_mg_m3)

Calibrated GreenLight Projects

The GreenLight HPS/LED replay workflow exports calibrated projects instead of leaving calibration only in outputs/calibration.

uv run python examples/export_greenlight_calibrated_projects.py

This writes:

  • projects/greenlight_hps_bleiswijk/project.json
  • projects/greenlight_led_bleiswijk/project.json

Each exported project has calibrated physical parameters applied to envelope, roof ventilation and CO2 equipment, plus a measured_replay calibration profile for replay controls such as pipe heat multiplier, measured-opening scale, CO2 pulse/memory and condensation multiplier.

Model Conversion

GreenhouseProject can produce:

  • GreenhouseUnitParameters for the reusable explicit unit model.
  • Greenhouse1Parameters for the traceable Greenhouse_1 bridge.
  • Greenhouse assets with one or more GreenhouseZone objects.
  • EquipmentInventory with ready-to-use lighting, screen, ventilation, humidity, CO2 and irrigation equipment objects.
  • WeatherDataset forcing from project.site.weather_file or a case forcing_source such as greenlight:HPS or agc_2019:Reference.

For scalar greenhouse models, project conversion computes:

  • effective cover U = sum(area * U * outdoor_exposure_fraction) / floor_area when boundaries are provided, otherwise it uses cover_u_w_m2_k.
  • roof_to_floor_area_ratio = roof_vent_area_m2 / floor_area when a physical roof vent area is provided, otherwise it uses roof_to_floor_area_ratio.

For GreenhouseUnit, explicit envelope boundaries are also passed through as an EnvelopeAssembly. The step result keeps the aggregate fluxes.envelope_loss_w_m2 and adds fluxes.envelope_boundary_losses, so CSV or xarray-oriented analysis can inspect losses by names such as roof or corridor_wall while preserving the scalar balance used by the air model.

This keeps project definition separate from equation implementation. Named simulation cases can now be run against real weather with simulate_project_greenhouse_unit; project-local output writers place the resulting time series under projects/<project_id>/results/<case_name>/<run_id>/.