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 explicitEnvelopeBoundaryDefinitionsurfaces, 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.jsonandresources/resource_statistics.md: simulated heat, electricity, CO2, irrigation and drain accounting integrated over the run horizon.resources/resource_comparison.jsonandresources/resource_comparison.mdwhen the case uses an AGC forcing source and the matchingdata/raw/agc_2019/<team>/Resources.csvtable is available.- optional PNG figures with
--plotsorplots=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:
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.
This writes:
projects/greenlight_hps_bleiswijk/project.jsonprojects/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:
GreenhouseUnitParametersfor the reusable explicit unit model.Greenhouse1Parametersfor the traceableGreenhouse_1bridge.Greenhouseassets with one or moreGreenhouseZoneobjects.EquipmentInventorywith ready-to-use lighting, screen, ventilation, humidity, CO2 and irrigation equipment objects.WeatherDatasetforcing fromproject.site.weather_fileor a caseforcing_sourcesuch asgreenlight:HPSoragc_2019:Reference.
For scalar greenhouse models, project conversion computes:
effective cover U = sum(area * U * outdoor_exposure_fraction) / floor_areawhen boundaries are provided, otherwise it usescover_u_w_m2_k.roof_to_floor_area_ratio = roof_vent_area_m2 / floor_areawhen a physical roof vent area is provided, otherwise it usesroof_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>/.