Import existing project data from ODS

This guide explains how to import one existing project into Rediflow using the example.ods template (or your own ODS file that follows the same structure). For first-time setup and navigation, see Overview and Initial data entry.

Overview

Project list after import

  • Script: scripts/seed_from_ods.py
  • Default template: docs/example.ods
  • Result: One project (name "Example project" when using the default script, or a custom name if you use per-org import) with organisations, work packages, WBS, tasks, milestones, deliverables, and EST capacity. Lookup data (deliverable types, dissemination levels, role types, etc.) is created if missing.
  • Idempotent: Running the script again updates the same project and skips orgs that already exist (by short name). Safe to run multiple times.

What is not in the ODS: Role demand (planned FTE % per role per month) and capacity requests (cross-org request–supply) are not read from the ODS. Configure those in the app after import if you use person capacity. See feature flags.

Prerequisites

  • Database: Containerised (Podman or Docker) PostgreSQL 18+ running; DATABASE_URL set (e.g. in .env.dev).
  • Migrations: Applied. Run uv run python main.py migrate before importing.
  • ODS file: Either use docs/example.ods as-is, or copy it and fill in your data so the sheet names and layout match (see below).

Quick import (default example file)

From the project root:

uv run python scripts/seed_from_ods.py

This uses docs/example.ods (or docs/content.xml if the ODS is not present), creates or updates the Example project, and populates orgs, WPs, WBS, tasks, milestones, deliverables, and EST Capa.

Import when containerised

When the app and database run in containers (e.g. compose.deploy-qa.yml or compose.deploy-prod.yml), run the import inside the app container. Mount the ODS file from the host and pass the container path to the script.

Prerequisites: Migrations applied. Run seed-calendars first for country holidays. See Import from ODS — overview and Seed projects and calendars for the full workflow.

Default example import

Download the example ODS from the project repository (docs/example.ods or docs/content.xml) and run:

QA:

podman compose -f compose.deploy-qa.yml run --rm \
  -v "$(pwd)/example.ods:/app/example.ods:ro" \
  app-qa python scripts/seed_from_ods.py --ods /app/example.ods

Production:

podman compose -f compose.deploy-prod.yml run --rm \
  -v "$(pwd)/example.ods:/app/example.ods:ro" \
  app-prod python scripts/seed_from_ods.py --ods /app/example.ods

Custom ODS file

Mount your ODS file and pass its container path:

podman compose -f compose.deploy-qa.yml run --rm \
  -v "$(pwd)/my_project.ods:/app/my_project.ods:ro" \
  app-qa python scripts/seed_from_ods.py --ods /app/my_project.ods

Validate before import (optional)

To validate the ODS before importing:

podman compose -f compose.deploy-qa.yml run --rm \
  -v "$(pwd)/my_project.ods:/app/my_project.ods:ro" \
  app-qa python scripts/validate_example_ods.py --ods /app/my_project.ods

Custom project name or begin date

podman compose -f compose.deploy-qa.yml run --rm \
  -v "$(pwd)/my_project.ods:/app/my_project.ods:ro" \
  app-qa python scripts/seed_from_ods.py --ods /app/my_project.ods \
  --project-name "My project" --begin-date 2026-04-01

Replace compose.deploy-qa.yml with compose.deploy-prod.yml and app-qa with app-prod for production.

Import your own ODS file

  1. Get the template
    Copy docs/example.ods to a new file (e.g. my_project.ods).

  2. Fill in the sheets (see Sheet layout reference below). Keep sheet names and column/row positions exactly as in the template so the importer can find the data.

  3. Validate (optional)
    Run the validation script to catch common issues before import:

    uv run python scripts/validate_example_ods.py
    uv run python scripts/validate_example_ods.py --ods path/to/my_project.ods
    
  4. Import

    uv run python scripts/seed_from_ods.py --ods path/to/my_project.ods
    

    Or with short option:

    uv run python scripts/seed_from_ods.py -o path/to/my_project.ods
    

The script extracts content.xml from the ODS into tmp/seed_import/ and runs the same import logic as for the default example. The Example project (or your file’s project) is created or updated from your file.

WPs+Tasks after import

Note: Each run with the same project name overwrites/updates that project. To import a different project name (e.g. per-org views), use the per-org workflow: Import per-org ODS (build ODS per org, then seed_all_ods_per_org.py to create one project per file).

Overriding the begin date (month 1)

You can set the project’s planned begin date (the calendar date for “month 1”) without editing the ODS:

uv run python scripts/seed_from_ods.py --begin-date 2026-04-01
uv run python scripts/seed_from_ods.py -b 2026-04-01 --ods path/to/my_project.ods

The planned end date is set from begin + (duration − 1) months so the project timeline stays consistent.

Using a custom project name

To create or update a project under a different name (e.g. for reuse or testing):

uv run python scripts/seed_from_ods.py --project-name "My test project"
uv run python scripts/seed_from_ods.py -n "Test project A" --ods path/to/my_project.ods

The name must be non-empty and at most 255 characters. Re-running with the same name updates that project; a different name creates an additional project.

Seeding variant projects with altered data

To feed the system with multiple projects from the same ODS but with unique names, random planned begin dates, and (when the begin date is in the past) actual start date set, use the sibling script with a seed for repeatability:

uv run python scripts/seed_example_variants.py --seed 42 --count 5
uv run python scripts/seed_example_variants.py --seed 42 --count 3 --name-source generator
  • --seed (required): Same seed produces the same project names and dates every run.
  • --count: Number of variant projects to create (default 1).
  • --ods: ODS file (default: docs/example.ods).
  • --past-months / --future-months: Range for random planned begin date (default: past 24 months, future 12 months).
  • --name-source: list (names from scripts/data/fictional_project_names.txt) or generator (names like Example variant 42-0).

When the chosen planned begin date is in the past, the script sets the project’s actual begin date to that date. The script uses the same import logic as seed_from_ods.py (same fixture API).

Timeline profile (30 projects): To generate exactly 30 projects with fixed timing buckets—10 already ended, 5 ending in 1/3/6/9/12 months, 10 running, 5 starting in 14/11/8/7/3 months—use:

uv run python scripts/seed_example_variants.py --profile timeline --seed 42
uv run python scripts/seed_example_variants.py -p timeline   # seed defaults to 42

Project names are taken from scripts/data/fictional_project_names.txt (first 30 lines); if there are fewer than 30 names, the rest use Timeline 42-0 etc. Same seed gives the same dates every run. ORGs come from the ODS ORGS sheet—use --ods path/to/your.ods to use your org names. Actual PMs are seeded: 28 projects at 100% of planned PMs (actual = plan for past months), 2 projects at 60% (40% behind); for the 2 behind, EST is also scaled to 60%. The script sets lifecycle state and actual begin/end so they satisfy the system’s data quality rules: ended projects are Closed with actual end set; running and ending-soon projects are Running with actual begin set; not-yet-started projects are Waiting for launch (no actual begin required).

Sheet layout reference

The importer expects these sheet names and layouts. Column indices below are 0-based (first column = 0). Rows are 0-based in code; “row 1” below means the second row of the sheet.

ORGS

  • Sheet name: ORGS
  • From row 2: One row per organisation.
  • Columns: Column 1 = Org # (optional), Column 2 = Org short name (required). The short name must be unique; it is used as the organisation identifier (e.g. in WPs, WBS, HOME ORG).
  • Header row: Row 0–1 may contain headers (e.g. "Org short name"); rows with empty short name or header text are skipped.

WPs+Tasks (project settings and task list)

  • Sheet name: WPs+Tasks
  • Project settings (first rows): Values are read from column index 2 (third column):
    • Row 1: Project Duration (months, integer).
    • Row 2: Project Planned begin date (YYYY-MM-DD).
    • Row 3: Project Actual begin date (YYYY-MM-DD).
    • Row 5: Project Planned end date (YYYY-MM-DD).
    • Row 6: Project Actual end date (YYYY-MM-DD).
    • Row 7: HOME ORG — Short name of the project owner org (must match an org in the ORGS sheet).
  • Task data: From row 13 onward. Columns (0-based):
    • 1 = WP ID (e.g. WP1, WP2)
    • 2 = Task ID (e.g. T1.1)
    • 3 = Task name
    • 4 = Lead ORG (short name)
    • 5 = HOME ORG Role
    • 6 = Awarded PMs (person-months)
    • 7 = Begin month (1..duration)
    • 8 = End month (1..duration)
  • Rules: Begin month ≤ end month; both in 1..duration; no duplicate (WP, Task ID) per project. HOME ORG must exist in ORGS.

WPs

  • Sheet name: WPs
  • From row 2: One row per work package.
  • Columns: 0 = empty/skip, 1 = WP # (number), 2 = WPID (e.g. WP1), 3 = WP Name, 4 = Leader ORG (short name from ORGS).
  • Rows with empty WP # or header "WP #" are skipped.

WBS

  • Sheet name: WBS
  • From row 3: One row per participant organisation.
  • Columns: 1 = Participant ORG (short name); 2, 3, … = person-months per WP (WP1, WP2, …). At least 11 columns are read (participant + up to 8 WP columns).
  • Participant org must exist in ORGS.

Milestones

  • Sheet name: Milestones
  • From row 2: One row per milestone.
  • Columns: 1 = MS #, 2 = Name, 3 = WPID, 4 = Lead, 5 = Home Org Role, 6 = Means of verification, 7 = Due month (1..duration).
  • At least 8 columns. Due month must be in 1..project duration.

Deliverables

  • Sheet name: Deliverables
  • From row 2: One row per deliverable.
  • Columns: 0 = Del #, 1 = Name, 2 = WPID, 3 = Lead, 4 = HOME ORG ROLE, 5 = Type, 6 = Dissemination, 7 = Due month (1..duration).
  • At least 9 columns. Due month must be in 1..project duration.

EST Capa need (estimated capacity per task per month)

  • Sheet name: EST Capa need
  • From row 7: One row per task. Same task order as WPs+Tasks is not required; matching is by (WP, Task ID).
  • Columns: 1 = WP (e.g. WP1), 2 = Task ID (e.g. T1.1), 3 = Month 1, 4 = Month 2, … (one column per project month).
  • Values are numeric (PM per month). Blank or "-" is treated as no value. Only months within the project duration are imported.

Calendar (optional)

  • Sheet name: calendar
  • Used to import country (FI) holidays and home-org non-working days.
  • FI holidays: Column 2 = date (ISO), column 15 = Country (FI) working day (0 = holiday). Only weekdays with value 0 are stored as country holidays.
  • Home-org non-working days: Column 2 = date, column 16 = ORG Working day (0 = non-working). Stored for the HOME ORG (e.g. EXAMPLE) and its descendants.
  • Additive, non-overriding: The ODS calendar import adds to existing country holidays and org non-working days; it does not overwrite or remove them. Existing dates are skipped; only new ones are added. Safe to run multiple times.
  • Recommended order: Run seed-calendars first (FI, SE from bundled JSON), then seed_from_ods (projects and optional ODS calendar data). Both steps are idempotent. See Seed projects and calendars for the full container workflow.
  • If you do not need to import calendar data, you can leave the calendar sheet as in the example or omit it; the importer will still create the project, WPs, WBS, tasks, milestones, deliverables, and EST Capa.

Lookup data created by the import

If the database is empty of lookup data, the importer creates:

  • Deliverable type group "Default" and deliverable types (R, DEM, DEC, OTHER, DMP)
  • Dissemination level group "Default" and levels (PU, SEN, EU-R/R-EU), plus Horizon Europe, TLP v2, TLL groups and levels
  • Home org roles (BEN, LEAD, n/a, Rev2)
  • Project states (lifecycle)
  • Role type group "Default" and preset role types (ea, ia, comm_spec, controller, pm, secretary, researcher, sw_dev, solution_arch, task_leader, tech_lead, topic_leader, wp_leader)

Organisations from the ORGS sheet are created if they do not already exist (matched by short name). Existing orgs are reused.

After import

Do these steps for each imported project:

  1. Open the project — Go to Projects in the app and open the imported project (e.g. "Example project").
  2. Verify settings — Check Settings: name, duration, dates, Project Owner Organisation (POO). Set Home organisation role group and Role type group in Project schemes if needed.
  3. Verify data — Check Work packages (WPs), Work breakdown structure (WBS), WPs+Tasks, Milestones, Deliverables, and Capacity demand (Estimated (EST) capacity).
  4. Run data quality check — Open Project → SettingsData quality check. Fix any reported issues; see How to fix data quality errors.
  5. Person capacity (if enabled) — None of this is read from the ODS; configure in the app:
    • Import people via Import persons and related data from ODS (seed_people_from_ods.py), or add people manually.
    • Set Role type group on the project (Project → Settings → Project schemes).
    • Edit Role demand in the Capacity demand view (planned Full-time equivalent (FTE) % per role per month).
    • Create capacity requests if you use cross-org capacity; see Cross-org capacity requests.

Evaluating with many projects

Organisations evaluating the system with a large number of projects can use the ODS template and import scripts in several ways:

Approach Use when
One project per ODS file You have one ODS per project. Run seed_from_ods.py --ods path/to/project.ods --project-name "Project Name" for each.
Multiple variants from one ODS Same structure, different names. Use seed_example_variants.py --count 30 --seed 42 (see Seeding variant projects above).
Per-org ODS files One project per org. Build ODS files with build_ods_per_org.py, then import all with seed_all_ods_per_org.py. See Import per-org ODS.
People and assignments After projects exist, run seed_people_from_ods.py to add persons and project assignments. See Import persons and related data from ODS.

After importing, follow the After import checklist for each project. Use the project list filters (organisation, lifecycle state, search) and pagination (per_page=100 in the URL) to navigate large portfolios.

Troubleshooting

  • "HOME ORG … is not in ORGS sheet" — Ensure the value in WPs+Tasks row 7, column 2 (HOME ORG) exactly matches an organisation short name in the ORGS sheet (e.g. ACR or EXAMPLE).
  • Duplicate task WPx / "T…" — Each (WP, Task ID) pair must be unique in the WPs+Tasks data rows.
  • Begin month > end month — For each task, begin month must be ≤ end month, and both in 1..duration.
  • Due month outside duration — Milestones and deliverables: due month must be between 1 and the project duration (from WPs+Tasks row 1).
  • EST Capa not loading — Ensure sheet name is exactly EST Capa need, data starts at row 7, columns 1–2 are WP and Task ID (matching WPs+Tasks), and columns 3+ are numeric values for month 1, 2, …

For persons, employee groups, leave types, and project assignments, see Import persons and related data from ODS. For more options (per-org ODS, multiple projects), see Import per-org ODS and How to populate fixtures.