Project structure🔗
Projects built around earthdaily-agriculture follow a standard layout that separates configuration, business logic, data, and execution. The cookiecutter template at project_template/ scaffolds this layout for you, but the convention is just as useful if you want to wire up a project by hand.
Standard layout🔗
my_project/
├── .env # Credentials (gitignored, copy from .env.template)
├── .env.template # Credential template
├── requirements.txt # Pins `earthdaily-agriculture==X.Y.Z` and your project deps
├── README.md # Project-specific docs
│
├── app/ # Project-specific business logic
│ ├── __init__.py
│ └── my_transforms.py # Transform functions between workflow steps
│
├── configuration/ # YAML workflow definitions
│ └── my_workflow.yml # Multi-step pipeline configuration
│
├── docs/ # Guides, copied in when the project is scaffolded
│ ├── 01 - Quick_Start_Guide.md # ... the numbered reference set
│ └── study/ # Your exploratory / service-delivery notebooks
│
├── inputs/ # Input data (shapefiles, parquet, CSV)
├── results/ # Final extraction outputs
├── partials/ # Intermediate bulk processing results
├── cache/ # Entity extraction cache (when enabled)
├── logs/ # Loguru log files (daily rotation)
│
└── EDAgriculture_My_Project.ipynb # Main execution notebook
Directory reference🔗
| Directory | Purpose | Git-tracked |
|---|---|---|
app/ |
Transform functions that adapt extractor outputs into downstream inputs. Each function follows the standard transform signature. | Yes |
configuration/ |
YAML workflow definitions. Each file describes a multi-step pipeline with dependencies, extractors, transforms, and parameters. | Yes |
docs/ |
The numbered guides, copied from the earthdaily-agriculture repo at scaffold time rather than vendored in the template — so they match the version you generated from. Re-scaffolding overwrites them; keep your own writing in docs/study/. |
Yes |
docs/study/ |
Yours. Exploratory and service-delivery work — coverage checks, zonal stats, custom maps, the notebook that answers a client question. Ships empty; never overwritten. | Yes |
inputs/ |
Input entity data — shapefiles (.shp), parquet (.parquet), CSV. Geometries and metadata for entities to process. |
Project-dependent |
results/ |
Final outputs — CSV, parquet, TIFF, HTML reports, manifest JSON. Created by extractors and export transforms. | No |
partials/ |
Intermediate results saved during bulk parallel processing. Includes partial CSVs and failed-ID lists. Auto-cleaned on success. | No |
cache/ |
Local parquet cache. Avoids re-fetching unchanged entities across runs. See 07 - Cache_design_context.md. |
No |
logs/ |
Dated log files from loguru. Default rotation: daily, retention: 30 days. | No |
Recommended .gitignore🔗
# Credentials
.env
# Output directories
results/
partials/
cache/
logs/
# Python
__pycache__/
*.pyc
.ipynb_checkpoints/
# OS
.DS_Store
Thumbs.db
app/ — Transform functions🔗
Transforms are the business logic layer between generic extractors. They reshape, enrich, or aggregate one extractor's output into the format the next one expects.
Function signature🔗
Every transform must follow this contract:
import pandas as pd
def my_transform(
entity_list: pd.DataFrame,
upstream_results: dict[str, dict],
params: dict,
) -> pd.DataFrame:
"""
Args:
entity_list: Original input entities for the workflow.
upstream_results: Dict mapping step_name -> result_dict. Each result_dict
contains:
- "results_df": pd.DataFrame
- "global_errors": list[dict]
- "failed_ids": list[str]
params: Transform-specific parameters from YAML.
Returns:
Modified entity_list DataFrame for the next step.
"""
Common patterns🔗
Pass-through — use upstream results as the entity list for the next extractor:
def use_upstream_entities(entity_list, upstream_results, params):
source_step = params.get("depends_on")
return upstream_results[source_step]["results_df"]
Enrichment — add columns from upstream results onto entity_list:
def enrich_entities(entity_list, upstream_results, params):
source_df = upstream_results[params["depends_on"]]["results_df"]
new_cols = source_df.groupby("id")["value"].first().reset_index()
return entity_list.merge(new_cols, on="id", how="inner")
Filtering — drop entities that don't meet criteria:
def filter_by_status(entity_list, upstream_results, params):
source_df = upstream_results[params["depends_on"]]["results_df"]
valid_ids = source_df.loc[source_df["status"] == "confirmed", "id"].unique()
return entity_list[entity_list["id"].isin(valid_ids)]
Aggregation — compute summary statistics from upstream results:
def compute_stats(entity_list, upstream_results, params):
source_df = upstream_results[params["depends_on"]]["results_df"]
stats = source_df.groupby("id").agg({"value": ["mean", "min", "max"]})
stats.columns = ["avg_value", "min_value", "max_value"]
return entity_list.merge(stats.reset_index(), on="id", how="inner")
configuration/ — YAML workflows🔗
Structure🔗
workflow:
name: "My Workflow"
description: "What this workflow does"
settings:
max_workers: 10
partial_frequency: 50
column_mapping: # Optional: map entity columns to canonical names
crop: "crop_code"
start_date: "sowingDate"
steps:
- name: step_name
# ...
Step types🔗
Extractor step (API call)🔗
Instantiates an extractor class, configures it, and runs bulk extraction:
- name: coverage
extractor: CoverageExtractor
module: earthdaily.agriculture.extractors.coverage_function
setup:
method: setup_coverage_parameters
params:
vegetation_index: NDVI
start_date: "2025-01-01"
end_date: "2025-12-31"
clear_cover_min: 90
run:
method: process_entity_coverage_bulk_parallel
params:
prefix: coverage
skip_export: true
Transform-only step (no API call)🔗
Runs a Python function to reshape data between extraction steps:
- name: image_selection
depends_on: coverage
transform:
module: app.my_transforms
function: select_best_images
params:
depends_on: coverage
max_images: 3
Combined step (transform + extractor)🔗
Runs a transform to prepare entities, then an extractor on the result:
- name: difference
depends_on: image_selection
transform:
module: app.my_transforms
function: use_upstream_entities
params:
depends_on: image_selection
extractor: DifferenceExtractor
module: earthdaily.agriculture.extractors.difference_functions
setup:
method: setup_difference_parameters
params:
product: NDVI
postprocess: stats
run:
method: process_entity_difference_bulk_parallel
params:
prefix: difference
skip_export: true
Dependencies🔗
Steps declare dependencies with depends_on. The workflow manager resolves the dependency graph and executes steps in order. A step without depends_on runs first.
- name: step_a # Runs first (no dependency)
- name: step_b
depends_on: step_a # Runs after step_a
- name: step_c
depends_on: step_b # Runs after step_b
Multiple dependencies are supported — pass a list:
Full YAML reference: 09b - Workflow_YAML_reference.md. Architecture rationale: 09 - Workflow_architecture.md.
The notebook — execution entry point🔗
Standard structure🔗
Every project notebook follows this pattern:
| Section | Purpose |
|---|---|
| Step 0: Environment setup | Configure sys.path, call init() |
| Step 1: Initialize WorkflowManager | Create manager, authenticate with EarthDaily Agro API |
| Step 2: Load entities | From the platform, a file, or a manually-built DataFrame |
| Step 3: Manual pipeline (optional) | Run each step individually for inspection and debugging |
| Step 4: YAML workflow execution | Automated pipeline via manager.run_workflow() |
| Step 5: Explore results | Visualise, analyse, threshold sensitivity |
| Step 6: Export | Save final results to results/ |
Bootstrap cell🔗
Every notebook starts with the same bootstrap cell. After pip install earthdaily-agriculture the sys.path block becomes unnecessary; the init() call handles working-directory and .env loading either way:
init():
- Walks up from the notebook's location to find the project root (the folder containing
pyproject.tomlor.env). - Sets
cwdto that root so relative paths work correctly. - Loads
.envso credentials are available toWorkflowManager.
Creating a new project by hand🔗
If you don't want to use the cookiecutter template (Path A in 01 - Quick_Start_Guide.md), the layout is short enough to build manually:
-
Create the directory structure:
-
Configure credentials:
-
Create
requirements.txt: -
Write transforms in
app/my_transforms.pyfollowing the standard signature. -
Write a workflow YAML in
configuration/my_workflow.yml. -
Create a notebook
EDAgriculture_My_Project.ipynbfollowing the standard section structure.
Checklist🔗
- [ ] Directory structure created (
app/,configuration/,inputs/,results/, etc.) - [ ]
.envconfigured with API credentials - [ ]
requirements.txtpinsearthdaily-agriculture==X.Y.Zand your project deps - [ ]
app/__init__.pyexists - [ ] Transform functions follow
(entity_list, upstream_results, params) -> DataFrame - [ ] YAML workflow steps have correct
module,extractor,setup,runfields - [ ] YAML transform steps reference functions in
app/with correctmoduleandfunction - [ ] Notebook bootstrap cell calls
init()fromearthdaily.agriculture.notebook_setup - [ ]
.gitignoreexcludesresults/,partials/,cache/,logs/,.env