# Synced from viable-api-js/doc/api-definition-base.yml for the public API walkthrough.
# Schema document only — calculate and most path operations are assembled at runtime.
# Nested meta/target (datastructureversion: 2) on the walkthrough is the preferred payload shape.
openapi: "3.1.0"
info:
  title: "Viable Pathway API"
  version: "1.0.0"
  description: |
    # Viable Pathway Emissions Projection API

    Calculate comprehensive emissions projections for your organisation using Viable Pathway's proprietary calculation engine.

    ## Overview

    The Viable Pathway API enables you to:
    - Calculate three emission pathway scenarios (BAU, External Trends, Internal Actions)
    - Project emissions from 2024 to 2050
    - Model the impact of external decarbonization trends
    - Evaluate internal emission reduction initiatives
    - Generate SBTi-aligned pathways
    - Perform scenario analysis across multiple climate narratives

    ## Getting Started Workflow

    Follow these steps to use the API effectively:

    ### Step 0: Get the Template Structure
    ```
    GET /api/data/template
    ```
    Returns an empty template showing the required data structure for your payload.

    ### Step 1: Get Naming Standards
    ```
    GET /api/data/standardnames
    ```
    Returns standardized activity names, categories, and emission factor names to ensure consistency.

    ### Step 2: Prepare Your Payload

    Structure your organization's data following the template:

    **Essential Elements (Shared Parameters):**
    - `organisationid` - Your organization name
    - `desiredgrowth` - Annual growth rate (% e.g., 3)
    - `projectionstartyear` - Start year for projections (max 2024)
    - `projectionendyear` - End year for projections (default 2050)

    **Essential Elements (Per Organizational Unit):**
    - `country` - Country location (e.g., "australia")
    - `region` - **Org-unit default** geography (e.g., "au-nsw") used when a row has no per-activity override. This is not the same as optional `region` on each activity leaf inside `activitydata`, which overrides emission-factor and cost resolution for that row only (see `ActivityDataEntry`).
    - `activitydata` - Nested object containing activity data by category

    **Optional Elements (Shared Parameters):**
    - `neartermsbtitarget` - SBTi target ("1.5" or "WB2")
    - `neartermsbtitargetyear` - Year for near-term target (typically 2030)
    - `userdefinedtarget` - Custom target with per-scope reductions (see UserDefinedTarget schema)
    - `userdefinedtargetbaseyear` - Base year for targets

    **Optional Elements (Per Organizational Unit):**
    - `revenue` - Annual revenue in millions
    - `currency` - Currency code (e.g., "aud")
    - `employees` - Number of full-time employees
    - `workdaysperyear` - Working days per year (default 245)
    - `internalactions` - Custom emission reduction actions
    - `historicemissionsyears` - Array of historic years
    - `historicemissions` - Historic emissions by scope (1, 2, 3)

    ### Step 3: Call the Calculation Endpoint
    ```
    POST /api/calculate
    ```
    Send your prepared payload and receive comprehensive emissions projections.

    API clients should call `POST /api/calculate?savePayload=false`.
    Organisation input is stored by the Viable Pathway web app, not partner API keys.

    ## Additional Resources

    - **Jupyter notebook**: sent after an API key is issued (not published on this site).
    - **Walkthrough**: https://viablepathway.net/Resources/api-access/
  contact:
    name: "Viable Pathway Support"
    email: "info@viablepathway.net"
  license:
    name: "Proprietary"

servers:
  - url: "https://app.viablepathway.net"
    description: "Production Server"
  - url: "http://localhost:3003"
    description: "Local Development Server"

paths: {}

components:
  schemas:
    Data:
      type: "object"
      description: "Generic data response (used for template, standardnames, and other reference data endpoints)"
      additionalProperties: true

    UserDefinedTarget:
      type: "object"
      nullable: true
      description: "Custom targets per scope for Near Term and Net Zero milestones (% vs base-year absolute emissions per scope; negative = reduction, positive = increase vs base). Omit or set null to disable user-defined target."
      properties:
        neartermyear:
          type: "integer"
          nullable: true
          description: "Year for near-term target. Omit for single segment (base to net zero only)."
          example: 2028
          minimum: 2025
          maximum: 2040
        neartermscope1:
          type: "number"
          nullable: true
          description: "Scope 1 % change vs base-year absolute emissions for near-term (e.g., -20 for 20% reduction, +10 for 10% above base)"
          example: -20
          minimum: -100
          maximum: 1000
        neartermscope2:
          type: "number"
          nullable: true
          description: "Scope 2 % change vs base-year absolute emissions for near-term"
          example: -20
          minimum: -100
          maximum: 1000
        neartermscope3:
          type: "number"
          nullable: true
          description: "Scope 3 % change vs base-year absolute emissions for near-term"
          example: -10
          minimum: -100
          maximum: 1000
        netzeroyear:
          type: "integer"
          description: "Year for net-zero target"
          example: 2050
          minimum: 2030
          maximum: 2050
        netzeroscope1:
          type: "number"
          description: "Scope 1 % change vs base-year absolute emissions for net-zero (e.g., -80 for 80% reduction)"
          example: -80
          minimum: -100
          maximum: 1000
        netzeroscope2:
          type: "number"
          description: "Scope 2 % change vs base-year absolute emissions for net-zero"
          example: -80
          minimum: -100
          maximum: 1000
        netzeroscope3:
          type: "number"
          description: "Scope 3 % change vs base-year absolute emissions for net-zero"
          example: -80
          minimum: -100
          maximum: 1000
        targetKind:
          type: "string"
          nullable: true
          description: "absolute_per_scope (default) = % vs base-year absolute emissions per scope; intensity = % vs base-year emission intensity per scope using intensityOutput* denominators"
          enum:
            - "absolute_per_scope"
            - "intensity"
          default: "absolute_per_scope"
        intensityDenominatorUnit:
          type: "string"
          nullable: true
          description: "Label for the activity denominator (e.g. MWh, tonnes, AUD revenue). Required when targetKind is intensity."
          example: "tonnes of product"
        intensityOutputBase:
          type: "number"
          nullable: true
          description: "Physical or economic output Q in the target base year (userdefinedtargetbaseyear). Required when targetKind is intensity."
          exclusiveMinimum: 0
          example: 1000
        intensityOutputNearTerm:
          type: "number"
          nullable: true
          description: "Output Q at the near-term target year (neartermyear). Required when targetKind is intensity and near-term row is used."
          exclusiveMinimum: 0
        intensityOutputNetZero:
          type: "number"
          nullable: true
          description: "Output Q at the net-zero year (netzeroyear). Required when targetKind is intensity."
          exclusiveMinimum: 0
      required:
        - netzeroyear
        - netzeroscope1
        - netzeroscope2
        - netzeroscope3

    UserDefinedTargetAlternative:
      type: "object"
      description: "Comparison custom target for pathway chart overlay only (not used in waterfall, scenarios, or exports). Same milestone fields as UserDefinedTarget plus id and label."
      required:
        - id
        - label
        - netzeroyear
        - netzeroscope1
        - netzeroscope2
        - netzeroscope3
      properties:
        id:
          type: "string"
          description: "Stable identifier for this comparison target"
          example: "alt-a1b2c3"
        label:
          type: "string"
          description: "Display name on the pathways chart legend"
          example: "Higher Scope 3 ambition"
        neartermyear:
          type: "integer"
          nullable: true
        neartermscope1:
          type: "number"
          nullable: true
        neartermscope2:
          type: "number"
          nullable: true
        neartermscope3:
          type: "number"
          nullable: true
        netzeroyear:
          type: "integer"
        netzeroscope1:
          type: "number"
        netzeroscope2:
          type: "number"
        netzeroscope3:
          type: "number"
        targetKind:
          type: "string"
          nullable: true
          enum:
            - "absolute_per_scope"
            - "intensity"
        intensityDenominatorUnit:
          type: "string"
          nullable: true
        intensityOutputBase:
          type: "number"
          nullable: true
        intensityOutputNearTerm:
          type: "number"
          nullable: true
        intensityOutputNetZero:
          type: "number"
          nullable: true

    UserDefinedPathwayAlternative:
      type: "object"
      description: "Computed pathway for a comparison custom target (extended calculation only)"
      properties:
        id:
          type: "string"
        label:
          type: "string"
        values:
          type: "array"
          description: "Total tCO2e per target year"
          items:
            type: "number"
        byScope:
          type: "object"
          properties:
            "1":
              type: "array"
              items:
                type: "number"
            "2":
              type: "array"
              items:
                type: "number"
            "3":
              type: "array"
              items:
                type: "number"

    CalculationInputs:
      type: "object"
      description: |
        Input payload for emissions calculations. See GET /api/data/template for the complete structure.

        **Root grouping (recommended):** organisation profile and projection settings may live under `meta`,
        and target settings under `target`. Flat root keys remain supported for backwards compatibility.
        When both nested and flat values are present for the same field, the nested value wins.
        The API normalizes nested fields onto the root before calculation. Calculate responses remain flat.
      required:
        - organisationid
        - orgunits
      properties:
        organisationid:
          type: "string"
          description: "Unique identifier for your organisation"
          example: "Org54315432643"
        orgunits:
          type: "object"
          description: "Map of organisational units keyed by org unit id"
          additionalProperties:
            $ref: "#/components/schemas/OrganisationalUnit"
        meta:
          $ref: "#/components/schemas/CalculationMeta"
        target:
          $ref: "#/components/schemas/CalculationTarget"
        datastructureversion:
          type: "integer"
          description: |
            Payload data-structure version. `2` means nested `meta`/`target` (when used) and flat activitydata maps.
            Prefer this over the legacy `activitydataversion` alias.
          enum: [1, 2]
          example: 2
        activitydataversion:
          type: "integer"
          deprecated: true
          description: "DEPRECATED alias for `datastructureversion`. Still accepted on input; normalized to `datastructureversion`."
          enum: [1, 2]
        name:
          type: "string"
          description: "Identifying name of the organisation. Optional. Prefer `meta.name` for new payloads."
          example: "New Organisation"
        desiredgrowth:
          type: "number"
          description: "Annual growth rate as a percentage (e.g., 3 for 3% growth). Applied to all activity data in BAU scenario. Prefer `meta.desiredgrowth`."
          example: 2
          default: 2
          minimum: -10
          maximum: 20
        hqcountry:
          type: "string"
          description: "Country where the organisation is headquatered (lowercase). Prefer `meta.hqcountry`."
          example: "australia"
        revenue:
          type: "number"
          description: "Annual revenue in millions (in specified currency). Prefer `meta.revenue`."
          example: 1000
          minimum: 0
        grossassets:
          type: "number"
          description: "Gross assets in millions (in specified currency). Used for AASB Group 1/2/3 reporting. Prefer `meta.grossassets`."
          example: 500
          minimum: 0
        currency:
          type: "string"
          description: "Organisation reporting currency. Prefer `meta.currency`. Distinct from per-org-unit local currency."
          example: "aud"
          default: "aud"  
        employees:
          type: "number"
          description: "Number of full-time equivalent employees. Prefer `meta.employees`."
          example: 100
          minimum: 1
        projectionstartyear:
          type: "integer"
          description: "Starting year for emissions projections (typically year with full GHG data). Prefer `meta.projectionstartyear`."
          example: 2024
          default: 2025
          minimum: 2024
          maximum: 2025
        projectionendyear:
          type: "integer"
          description: "End year for projections (maximum 2050). Prefer `meta.projectionendyear`."
          example: 2050
          default: 2050
          minimum: 2025
          maximum: 2050
        industrytype:
          type: "string"
          description: "Industry classification for benchmarking and default action suggestions. Prefer `meta.industrytype`."
          example: "advertising, marketing & pr"
        electricityapproach:
          type: "string"
          description: "Electricity accounting approach: 'location' (grid average) or 'market' (contractal or residual). Prefer `meta.electricityapproach`."
          example: "location"
          enum:
            - "location"
            - "market"
          default: "location"
        discountrate:
          type: "number"
          description: |
            Real discount rate as a percentage (e.g. 8 for 8% per year) used for MAC NPV.
            Applied to capital and operational cash flows in constant-currency terms; missing/null defaults to 0 (undiscounted).
            Prefer `meta.discountrate`. Same percentage style as `desiredgrowth`.
          example: 8
          minimum: 0
          maximum: 100
        neartermsbtitarget:
          type: "string"
          nullable: true
          description: "Science Based Targets initiative (SBTi) pathway alignment: '1.5' for 1.5°C pathway, 'WB2' for Well Below 2°C, '2' for 2°C pathway. Set to null to disable SBTi pathway. Prefer `target.neartermsbtitarget`."
          example: "WB2"
          enum:
            - "1.5"
            - "WB2"
            - "2"
            - null
          default: "WB2"
        neartermsbtitargetyear:
          type: "integer"
          nullable: true
          description: "Target year for near-term SBTi goal (SBTi guidance: 5-10 years from base year, typically 2030). Set to null when no SBTi target is configured. Prefer `target.neartermsbtitargetyear`."
          example: 2028
          default: 2028
          minimum: 2025
          maximum: 2035
        userdefinedtarget:
          $ref: "#/components/schemas/UserDefinedTarget"
        userdefinedtargetalternatives:
          type: "array"
          description: "Optional comparison custom targets (max 5). Shown on pathways chart overlays only; primary userdefinedtarget drives waterfall and other outputs. Prefer `target.userdefinedtargetalternatives`."
          maxItems: 5
          items:
            $ref: "#/components/schemas/UserDefinedTargetAlternative"
        userdefinedneartermtarget:
          type: "number"
          nullable: true
          deprecated: true
          description: "DEPRECATED: Use userdefinedtarget.neartermscope1/2/3 instead. Legacy flat near-term target %."
          minimum: -100
          maximum: 1000
        userdefinedneartermtargetyear:
          type: "integer"
          nullable: true
          deprecated: true
          description: "DEPRECATED: Use userdefinedtarget.neartermyear instead."
          minimum: 2025
          maximum: 2040
        userdefinednetzerotarget:
          type: "number"
          nullable: true
          deprecated: true
          description: "DEPRECATED: Use userdefinedtarget.netzeroscope1/2/3 instead. Legacy flat net-zero target %."
          minimum: -100
          maximum: 1000
        userdefinednetzerotargetyear:
          type: "integer"
          nullable: true
          deprecated: true
          description: "DEPRECATED: Use userdefinedtarget.netzeroyear instead."
          minimum: 2030
          maximum: 2050
        userdefinedtargetbaseyear:
          type: "integer"
          description: "Base year for all reduction targets (must have historic emissions data if different from projection start year). Prefer `target.userdefinedtargetbaseyear`."
          example: 2022
          minimum: 2015
          maximum: 2025

    CalculationMeta:
      type: "object"
      description: |
        Organisation profile and projection settings. Preferred grouping for new payloads.
        Flat root equivalents remain accepted; nested values win when both are present.
      properties:
        name:
          type: "string"
          example: "Example"
        hqcountry:
          type: "string"
          example: "australia"
        industrytype:
          type: "string"
          example: "advertising, marketing & pr"
        currency:
          type: "string"
          description: "Organisation reporting currency (ISO 4217). Distinct from per-org-unit local currency."
          example: "aud"
        revenue:
          type: "number"
          minimum: 0
        grossassets:
          type: "number"
          minimum: 0
        employees:
          type: "number"
          minimum: 1
        workdaysperyear:
          type: "number"
          example: 245
        desiredgrowth:
          type: "number"
          description: "Organisation-level default annual growth %. Org units may still override with their own desiredgrowth."
          example: 2
        projectionstartyear:
          type: "integer"
          example: 2025
        projectionendyear:
          type: "integer"
          example: 2050
        electricityapproach:
          type: "string"
          enum: ["location", "market"]
          default: "location"
        discountrate:
          type: "number"
          description: |
            Real discount rate as a percentage (e.g. 8 for 8% per year) for MAC NPV.
            Costs remain in constant (real) currency; do not also inflate cash flows.
            Missing/null defaults to 0 (legacy undiscounted MAC). Same percentage style as `desiredgrowth`.
          example: 8
          minimum: 0
          maximum: 100
        reportingyearbasis:
          type: "string"
          description: |
            Reporting year basis for Australia market-based RPP/JRPP lookup.
            `"calendar"` (default) uses the calendar-year factor; `"financial"` averages the
            current and previous calendar-year RPP/JRPP because a financial year spans two
            half calendar years. Missing/legacy payloads default to `"calendar"`.
          enum: ["calendar", "financial"]
          default: "calendar"
        changeHistory:
          type: "array"
          description: |
            Optional append-only edit history for organisation meta fields (organisation
            profile, pathway growth, carbon price, discount rate). Ignored by calculation.
          items:
            $ref: "#/components/schemas/ChangeHistoryEntry"

    CalculationTarget:
      type: "object"
      description: |
        Target pathway configuration. Preferred grouping for new payloads.
        Flat root equivalents remain accepted; nested values win when both are present.
      properties:
        neartermsbtitarget:
          type: "string"
          nullable: true
          enum: ["1.5", "WB2", "2", null]
        neartermsbtitargetyear:
          type: "integer"
          nullable: true
        sbtiversion:
          type: "string"
          description: "Optional SBTi pathway version selector"
        showsbtialternative:
          type: "boolean"
          description: "When true, include the alternate SBTi pathway overlay in outputs"
        userdefinedtargetbaseyear:
          type: "integer"
        userdefinedtarget:
          $ref: "#/components/schemas/UserDefinedTarget"
        userdefinedtargetalternatives:
          type: "array"
          maxItems: 5
          items:
            $ref: "#/components/schemas/UserDefinedTargetAlternative"
        userdefinedneartermtarget:
          type: "number"
          deprecated: true
        userdefinedneartermtargetyear:
          type: "integer"
          deprecated: true
        userdefinednetzerotarget:
          type: "number"
          deprecated: true
        userdefinednetzerotargetyear:
          type: "integer"
          deprecated: true
        changeHistory:
          type: "array"
          description: |
            Optional append-only edit history for target inputs (base year, SBTi, custom targets).
            Ignored by calculation.
          items:
            $ref: "#/components/schemas/ChangeHistoryEntry"

    OrganisationalUnit:
      type: "object"
      description: |
        Data for a single organisational unit (location, division, or subsidiary). 
        
        **Important**: Organisational units are identified by their Map key in the `orgunits` object (e.g., `"orgunit_id1"`, `"orgunit_id2"`). 
        The `orgunitid` field is automatically derived from this Map key during calculation.
        The `id` field is optional and should match the Map key if provided (for frontend compatibility).
      required:
        - country
        - activitydata
      properties:
        id:
          type: "string"
          description: |
            Optional identifier that should match the Map key (e.g., if Map key is "orgunit_id1", id should be "orgunit_id1"). 
            If omitted, the system will use the Map key as the identifier. This field is primarily for frontend compatibility.
          example: "orgunit_id1"
        orgunitid:
          type: "string"
          description: |
            **DEPRECATED**: This field is automatically derived from the Map key during calculation and does not need to be provided in the input payload.
            The Map key (e.g., "orgunit_id1") becomes the orgunitid internally.
          example: "orgunit_id1"
        country:
          type: "string"
          description: "Country where this unit operates (lowercase)"
          example: "australia"
        region:
          type: "string"
          description: |
            Default region for this organisational unit (see /api/data/standardnames for valid region codes by country).
            Used for emission-factor and cost lookups when an activity row does not set its own `region`.
            **Not** the same as optional per-leaf `region` on each `ActivityDataEntry`, which overrides EF/cost resolution for that activity row only.
          example: "au-nsw"
        companytype:
          type: "string"
          description: "Business type for this unit. Optional but if added it affects default actions (and benchmarks in future)"
          example: "office"
        revenue:
          type: "number"
          description: "Annual revenue in millions (in specified currency)"
          example: 1000
          minimum: 0
        currency:
          type: "string"
          description: "Currency code for financial data"
          example: "aud"
          default: "aud"
        employees:
          type: "number"
          description: "Number of full-time equivalent employees"
          example: 100
          minimum: 1
        activitydata:
          type: "object"
          description: |
            Activity input for this org unit. Supports two formats (see `datastructureversion`):

            - **v1 (nested):** category key → item key → ActivityDataEntry
            - **v2 (flat, recommended):** instance id → ActivityDataEntryV2. Set `datastructureversion: 2`.

            v1 category/item keys and v2 instance ids may use `catalogItemKey@@regionSlug` for regional instances.
            See `Docs/DATA_ARCHITECTURE.md` and `Docs/FRONTEND_ACTIVITY_DATA_V2.md`.
          additionalProperties: true
        datastructureversion:
          type: "integer"
          description: |
            Data-structure version for this org unit. `2` = flat activitydata map (and aligns with root payload v2).
            Prefer this over the legacy `activitydataversion` alias.
          enum: [1, 2]
          example: 2
        activitydataversion:
          type: "integer"
          deprecated: true
          description: "DEPRECATED alias for `datastructureversion`. Still accepted on input."
          enum: [1, 2]
          example: 2
        internalactions:
          type: "object"
          description: "Custom emission reduction actions for this organizational unit"
          additionalProperties:
            oneOf:
              - $ref: "#/components/schemas/InternalAction"
              - type: "object"
                description: "Deletion marker for library actions"
                properties:
                  originalLibraryIndex:
                    type: "integer"
                    description: "Index of the original library action that was deleted"
                  deletedAt:
                    type: "string"
                    format: "date-time"
                    description: "Timestamp when the action was deleted"
                  actionname:
                    type: "string"
                    description: "Name of the deleted action for reference"
                required:
                  - originalLibraryIndex
                  - deletedAt
                  - actionname
        historicemissionsyears:
          type: "array"
          description: |
            Optional array of years for which historic emissions data is available. 
            Only needed if you want to use a base year (`userdefinedtargetbaseyear`) that is before the projection start year.
            If omitted, the system will use calculated BAU emissions from the projection start year as the base year.
          example: [2020, 2021, 2022, 2023, 2024]
          items:
            type: "integer"
        historicemissions:
          type: "object"
          description: |
            Optional historic emissions data by GHG Protocol scope.
            Only needed if you want to use a base year before the projection start year.
            If omitted, the system will calculate base year emissions from activity data.

            Scope 2 may be a legacy flat number array, or the preferred nested
            `{ "location": [...], "market": [...] }` shape used with location/market accounting.
          properties:
            "1":
              type: "array"
              description: "Scope 1 emissions (direct) in tCO2e for each historic year"
              items:
                type: "number"
              example: [100, 100, 100, 100, 100]
            "2":
              description: |
                Scope 2 emissions (purchased energy) in tCO2e for each historic year.
                Prefer `{ location, market }` arrays aligned to `historicemissionsyears`.
                A single number array is accepted for legacy payloads.
              oneOf:
                - type: "array"
                  items:
                    type: "number"
                  example: [100, 100, 100, 100, 100]
                - $ref: "#/components/schemas/HistoricScope2Emissions"
            "3":
              type: "array"
              description: "Scope 3 emissions (value chain) in tCO2e for each historic year"
              items:
                type: "number"
              example: [100, 100, 100, 100, 100]
        historicemissionsunit:
          type: "string"
          description: "Optional unit of measurement for historic emissions. Defaults to tCO2e if not specified."
          example: "tCO2e"
          default: "tCO2e"
        changeHistory:
          type: "array"
          description: |
            Optional append-only edit history for this org unit (org-unit profile,
            org-unit growth, historic emissions). Ignored by calculation.
          items:
            $ref: "#/components/schemas/ChangeHistoryEntry"

    ChangeHistoryEntry:
      type: "object"
      description: |
        One edit-save on an activity, action, target, pathway, investment, historic-emissions,
        or organisation-profile input. Append-only client metadata; the calculation engine
        ignores this field.
      required:
        - timestamp
        - changes
      properties:
        timestamp:
          type: "string"
          format: "date-time"
          description: "ISO-8601 time when the edit was saved"
          example: "2026-08-17T07:11:00.000Z"
        user:
          type: "object"
          description: "User who saved the edit"
          properties:
            name:
              type: "string"
              example: "Elliott Smith"
            email:
              type: "string"
              example: "elliott@example.com"
        comment:
          type: "string"
          description: "Optional free-text comment for this save. Empty string when omitted."
          example: "Updated EF after new NGA release"
        changes:
          type: "array"
          description: "Field-level diffs for this save (not a full object snapshot)"
          items:
            type: "object"
            required:
              - field
            properties:
              field:
                type: "string"
                description: "Persisted field name that changed"
                example: "emissionfactorvalue"
              from:
                description: "Previous persisted value (any JSON type)"
              to:
                description: "New persisted value (any JSON type)"
        section:
          type: "string"
          description: |
            Optional grouping when several screens share one changeHistory array
            (e.g. `pathway` vs `investment` vs `organisation` on `meta.changeHistory`,
            or `pathway` vs `historicemissions` vs `orgunit` on an org unit).
          example: "pathway"

    ActivityDataEntry:
      type: "object"
      description: "Individual activity data entry (one leaf under activitydata category → item key) with associated emission factor"
      required:
        - activityuom
        - activitydatavalue
        - emissionfactorname
        - emissionfactorvalue
        - ghgpscope
        - ghgpscopecategory
      properties:
        region:
          type: "string"
          description: |
            Optional per-row region override for emission-factor and cost resolution for this activity (e.g. `au-nsw`).
            When omitted, the org-unit `OrganisationalUnit.region` applies. See GET /api/data/standardnames for valid region codes.
          example: "au-nsw"
        catalogactivityid:
          type: "string"
          description: |
            Optional catalog / standardnames activity id when the map key is opaque or encodes region (e.g. `utilities_electricity_grid`).
            When present, the engine prefers this for catalog resolution vs parsing the path key alone. See `Docs/DATA_ARCHITECTURE.md`.
          example: "utilities_electricity_grid"
        category:
          type: "string"
          description: |
            Optional user-defined category for grouping / charts. When omitted, falls back to `navigationcategory`,
            then the nested parent key (v1), then the first segment of the catalog activity id.
          example: "Fleet & Transport"
        growthoverride:
          type: "number"
          description: |
            Optional annual activity growth override (%) for this row at org-unit level. When set, overrides default growth for this flattened activity in the calculation pipeline.
          example: 2.5
        activityuom:
          type: "string"
          description: "Unit of measurement for the activity (e.g., 'kWh', 'km', 'kg', 'tonnes')"
          example: "kWh"
        activitydatavalue:
          type: "number"
          description: "Numerical value of the activity for the projection start year"
          example: 100000
          minimum: 0
        activitydatatype:
          type: "string"
          description: "Type of activity data: 'mass' for physical quantities, 'spend' for financial data, 'energy' for energy consumption"
          example: "mass"
          enum:
            - "mass"
            - "spend"
            - "energy"
        emissionfactorname:
          type: "string"
          description: "Standardized name of the emission factor. Must match naming in standardnames database."
          example: "utilities_electricity_grid"
        emissionfactorvalue:
          type: "number"
          description: "Emission factor value in kgCO2e per unit of activity. For Scope 1/2 migrated rows this is the direct (operational) component; for Scope 3 or legacy rows it is the total factor."
          example: 0.55159
          minimum: 0
        emissionfactorvalue_location:
          type: "number"
          description: "Optional location-based grid emission factor (direct) for electricity activities, kgCO2e per kWh."
          minimum: 0
        emissionfactorvalue_market:
          type: "number"
          description: "Optional market-based grid emission factor (direct) for electricity activities, kgCO2e per kWh."
          minimum: 0
        emissionfactorvalue_indirect:
          type: "number"
          description: "Optional fuel-and-energy related activities (Scope 3.3) indirect emission factor for Scope 1/2 rows, kgCO2e per unit. Legacy single value; for grid electricity prefer emissionfactorvalue_indirect_location and emissionfactorvalue_indirect_market."
          minimum: 0
        emissionfactorvalue_indirect_location:
          type: "number"
          description: "Optional location-based fuel-and-energy related activities (Scope 3.3) indirect factor for grid electricity, kgCO2e per kWh."
          minimum: 0
        emissionfactorvalue_indirect_market:
          type: "number"
          description: "Optional market-based fuel-and-energy related activities (Scope 3.3) indirect factor for grid electricity, kgCO2e per kWh."
          minimum: 0
        emissionfactorunit:
          type: "string"
          description: "Unit for the emission factor (typically 'kgCO2e')"
          example: "kgCO2e"
          default: "kgCO2e"
        emissionfactorsource:
          type: "string"
          description: "Source/reference for the emission factor (e.g., 'EPA 2024', 'Ember Australia 2024')"
          example: "Ember Australia 2024"
        description:
          type: "string"
          description: "Human-readable description of this activity"
          example: "Electricity consumption using the location-based approach"
        ghgpscope:
          type: "integer"
          description: "GHG Protocol Scope: 1 (direct), 2 (purchased energy), or 3 (value chain)"
          example: 2
          enum: [1, 2, 3]
        ghgpscopecategory:
          type: "string"
          description: "Detailed GHG Protocol scope category (e.g., '1-1', '2-1', '3-7'). See standardnames for complete list."
          example: "2-1"
        changeHistory:
          type: "array"
          description: |
            Optional append-only edit history for this activity leaf (user, timestamp, field diffs, comment).
            Written by the web client on edit-save. Ignored by calculation.
          items:
            $ref: "#/components/schemas/ChangeHistoryEntry"

    ActivityDataEntryV2:
      type: "object"
      description: |
        Flat activity-data entry (activitydata v2). Each org-unit `activitydata` map is keyed by a unique
        **instance id** (same id used in calculation outputs and internal-action `activity` fields).
        Display names and emission-factor labels are decoupled from trend-matching catalog ids.
        See `Docs/FRONTEND_ACTIVITY_DATA_V2.md`.
      required:
        - activityname
        - catalogactivityid
        - activityuom
        - activitydatavalue
        - emissionfactorlabel
        - emissionfactortrendid
        - emissionfactorvalue
        - ghgpscope
        - ghgpscopecategory
      properties:
        activityname:
          type: "string"
          description: "Free-form display label for this activity row."
          example: "NSW fleet — petrol cars"
        catalogactivityid:
          type: "string"
          description: "Catalog / standardnames id used to match external activity trends, costs, and emission-factor database records."
          example: "companyvehicle_car_petrol"
        region:
          type: "string"
          description: "Optional per-row region override (e.g. au-nsw)."
          example: "au-nsw"
        category:
          type: "string"
          description: |
            Optional user-defined category for grouping, charts, and filters (any string).
            When omitted, falls back to `navigationcategory`, then the first segment of `catalogactivityid`
            (e.g. `companyvehicle` from `companyvehicle_car_petrol`). Does not affect trend or emission-factor matching.
          example: "Fleet & Transport"
        navigationcategory:
          type: "string"
          description: |
            Optional legacy alias for `category`. Prefer `category` for new payloads.
            Used for UI navigation / filter grouping when `category` is omitted.
          example: "companyvehicle"
        growthoverride:
          type: "number"
          description: "Optional annual activity growth override (%) for this row."
        activityuom:
          type: "string"
          example: "km"
        activitydatavalue:
          type: "number"
          minimum: 0
        activitydatatype:
          type: "string"
          enum: ["mass", "spend", "energy"]
        emissionfactorlabel:
          type: "string"
          description: "Free-form display label for the emission factor."
          example: "DESNZ petrol cars"
        emissionfactortrendid:
          type: "string"
          description: "Match key for emission-factor external trends (emissionFactorsEXTtrends.json)."
          example: "companyvehicle_car_petrol"
        emissionfactorvalue:
          type: "number"
          minimum: 0
        emissionfactorvalue_location:
          type: "number"
          minimum: 0
        emissionfactorvalue_market:
          type: "number"
          minimum: 0
        emissionfactorvalue_indirect:
          type: "number"
          minimum: 0
        emissionfactorvalue_indirect_location:
          type: "number"
          minimum: 0
        emissionfactorvalue_indirect_market:
          type: "number"
          minimum: 0
        emissionfactorunit:
          type: "string"
          default: "kgCO2e"
        emissionfactorsource:
          type: "string"
        description:
          type: "string"
        ghgpscope:
          type: "integer"
          enum: [1, 2, 3]
        ghgpscopecategory:
          type: "string"
        changeHistory:
          type: "array"
          description: |
            Optional append-only edit history for this activity instance (user, timestamp, field diffs, comment).
            Written by the web client on edit-save. Ignored by calculation.
          items:
            $ref: "#/components/schemas/ChangeHistoryEntry"

    HistoricScope2Emissions:
      type: "object"
      description: |
        Scope 2 historic emissions split by electricity accounting approach.
        Arrays must align with `historicemissionsyears`.
      required:
        - location
        - market
      properties:
        location:
          type: "array"
          description: "Location-based Scope 2 emissions (tCO2e) per historic year"
          items:
            type: "number"
          example: [100, 95, 90]
        market:
          type: "array"
          description: "Market-based Scope 2 emissions (tCO2e) per historic year"
          items:
            type: "number"
          example: [80, 70, 60]

    InternalAction:
      type: "object"
      description: "Emission reduction action that your organization can implement"
      required:
        - actionname
        - activity
        - startyear
        - multiplier
      properties:
        actiontype:
          type: "string"
          description: "Category of action (e.g., 'Energy Efficiency', 'Fleet Electrification', 'Renewable Energy')"
          example: "Energy Efficiency"
        actionname:
          type: "string"
          description: "Name/title of the action"
          example: "Add motion sensors to lighting"
        activity:
          type: "string"
          description: |
            Target row as a **flattened activity instance id**—the same `activity` string returned on projected activity arrays after calculate.
            When only one row exists per catalog activity, this is typically the catalog id (e.g. `utilities_electricity_grid`).
            When multiple regional instances exist, use the full instance id including the `@@region` suffix (e.g. `utilities_electricity_grid@@au-vic`), matching how the row was keyed or flattened from nested `activitydata`.
          example: "utilities_electricity_grid"
        startyear:
          type: "integer"
          description: "Year when this action begins (must be >= projection start year)"
          example: 2029
          minimum: 2024
          maximum: 2050
        multiplier:
          type: "number"
          description: "Percentage change in activity as a decimal (e.g., -0.1 for 10% reduction, -1.0 for 100% elimination)"
          example: -0.1
          minimum: -1
          maximum: 1
        absolutechange:
          type: "number"
          description: |
            Absolute change in activity units in the action start year (alternative to multiplier; use 0 if using multiplier).
            By default the qty grows with the primary activity's desired growth in later years
            (absoluteChangeGrows omitted or true): change(y) = absolutechange × (1 + g/100)^(y − startyear).
            Set absoluteChangeGrows to false to apply the same fixed amount every year.
          example: 0
          default: 0
        absoluteChangeGrows:
          type: "boolean"
          description: |
            When absolutechange is non-zero, whether the qty grows with primary activity desired growth after startyear.
            Defaults to true. Set false for a fixed qty each year (legacy).
          example: true
          default: true
        enabled:
          type: "boolean"
          description: |
            When false, the action remains in the org-unit payload/UI but is skipped by calculation
            (getCombinedInternalActions). Omit or true to include the action (default).
          example: true
          default: true
        balancingactivity:
          type: "string"
          description: |
            Balancing row as a **flattened activity instance id** (same rules as `activity`): catalog id when unique, or full `@@region` form when multiple instances exist (e.g. grid uptake when reducing `fuel_naturalgas@@au-nsw` may target `utilities_electricity_grid@@au-nsw`). Leave empty if none.
          example: ""
        balancingactivityuom:
          type: "string"
          nullable: true
          description: "Unit of measure for the balancing activity when it differs from the primary activity UOM"
          example: "kWh"
        balancingmultiplier:
          type: "number"
          description: "Multiplier for balancing activity (e.g., 0.25 if heat pump is 4x more efficient than gas boiler)"
          example: 0
          default: 0
        balancingabsolutechange:
          type: "number"
          description: "Absolute change for balancing activity"
          example: 0
          default: 0
        description:
          type: "string"
          nullable: true
          description: "Detailed description of the action and its implementation. Null/omitted when unspecified."
          example: "Install motion sensors in all office areas to reduce lighting energy consumption by 10%"
        source:
          type: "string"
          nullable: true
          description: "Source or reference for action assumptions. Null/omitted when unspecified."
          example: "Energy audit 2024"
        capitalcost:
          type: "number"
          nullable: true
          description: |
            Fixed implementation cost (optional). Use `null` when cost is unspecified or when using
            `capitalcostperunit` instead. The engine treats null/omitted as no fixed capital cost.
          example: 10000
        capitalcostperunit:
          type: "number"
          nullable: true
          description: |
            Capital cost per unit of activity change (optional). Use with
            `capitalcostcalculationmethod: perunit`. Null/omitted means unused.
          example: 10
        capitalcostcalculationmethod:
          type: "string"
          nullable: true
          description: |
            How capital cost is derived for MAC / financial displays (client metadata).
            Preferred values: `absolute` (with `capitalcost`) or `perunit` (with `capitalcostperunit`).
            Null, omit, blank, or any other unset placeholder is accepted — the engine derives cost from
            `capitalcost` / `capitalcostperunit` precedence and does not require this field.
          example: "absolute"
        opex:
          type: "number"
          nullable: true
          description: |
            Simple annualised operating cost as a present-day lump ($/year), applied every year from
            `startyear`. Use `null` when unspecified or when using `opexperunit` instead.
            Stored input is never rewritten with discounting.
          example: 1000
        opexperunit:
          type: "number"
          nullable: true
          description: |
            Annual opex per unit of start-year activity change (optional). Use with
            `opexcalculationmethod: perunit`. Null/omitted means unused. Engine uses
            `abs(startyear activity reduction) × opexperunit` as a constant annual amount.
          example: 0.5
        opexcalculationmethod:
          type: "string"
          nullable: true
          description: |
            How opex is derived for MAC / financial displays (client metadata).
            Preferred values: `absolute` (with `opex`) or `perunit` (with `opexperunit`).
            Null, omit, blank, or any other unset placeholder is accepted — the engine derives opex from
            `opex` / `opexperunit` precedence and does not require this field.
          example: "absolute"
        costcurrency:
          type: "string"
          description: "Currency for cost data"
          example: "aud"
          default: "aud"
        companytype:
          description: "Optional company-type targeting metadata from action libraries"
          oneOf:
            - type: "string"
            - type: "array"
              items:
                type: "string"
        changeHistory:
          type: "array"
          description: |
            Optional append-only edit history for this action (user, timestamp, field diffs, comment).
            Written by the web client on edit-save. Ignored by calculation.
          items:
            $ref: "#/components/schemas/ChangeHistoryEntry"

    CalculationOutputs:
      type: "object"
      description: "Comprehensive emissions projections and analysis results"
      properties:
        organisationid:
          type: "string"
          description: "Organisation identifier from input"
        orgUnits:
          type: "array"
          description: "List of organizational units included in this calculation"
          items:
            type: "object"
            properties:
              id:
                type: "string"
                description: "Org unit ID"
              name:
                type: "string"
                description: "Org unit name"
        orgunitResults:
          type: "array"
          description: "Detailed results for each organizational unit (allows drill-down analysis)"
          items:
            type: "object"
            description: "Complete calculation results for one organizational unit"
        projectionyears:
          type: "array"
          description: "Array of years covered by the projection (e.g., [2024, 2025, ..., 2050])"
          items:
            type: "integer"
          example: [2024, 2025, 2026, 2027, 2028, 2029, 2030]
        targetyears:
          type: "array"
          description: "Array of years from base year to projection end year (for target pathway calculations)"
          items:
            type: "integer"
        historicemissionsyears:
          type: "array"
          description: "Years for which historic emissions were provided"
          items:
            type: "integer"
        projectionstartyearactivitydata:
          type: "array"
          description: "Activity data for the projection start year (flattened from nested input structure)"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Unique flattened activity instance id for this row (catalog id when a single row exists; may include `@@region` suffix, e.g. `utilities_electricity_grid@@au-nsw`, when input used regional instance keys)."
              values:
                type: "array"
                description: "Activity values (single value for start year)"
                items:
                  type: "number"
        projectedBAUAD:
          type: "array"
          description: "Business As Usual scenario - Projected Activity Data for each activity and year (growth only, no trends or actions)"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Unique flattened activity instance id (may include `@@region` when input used regional instance keys)."
              values:
                type: "array"
                items:
                  type: "number"
        projectedEXTAD2:
          type: "array"
          description: "External Trends scenario - Projected Activity Data including external societal/technological trends"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Unique flattened activity instance id (may include `@@region` when input used regional instance keys)."
              values:
                type: "array"
                items:
                  type: "number"
        projectedINTAD2:
          type: "array"
          description: "Internal Actions scenario - Projected Activity Data including both external trends and internal reduction actions"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Unique flattened activity instance id (may include `@@region` when input used regional instance keys)."
              values:
                type: "array"
                items:
                  type: "number"
        ghgBAUsumperyear:
          type: "array"
          description: "Total GHG emissions per year for Business As Usual pathway (tCO2e)"
          items:
            type: "number"
          example: [1000, 1030, 1060, 1090]
        ghgEXTsumperyear:
          type: "array"
          description: "Total GHG emissions per year for External Trends pathway (tCO2e)"
          items:
            type: "number"
        ghgEXTsumperyearMin:
          type: "array"
          description: "Minimum GHG emissions per year for External Trends pathway (uncertainty range, tCO2e)"
          items:
            type: "number"
        ghgEXTsumperyearMax:
          type: "array"
          description: "Maximum GHG emissions per year for External Trends pathway (uncertainty range, tCO2e)"
          items:
            type: "number"
        ghgINTsumperyear:
          type: "array"
          description: "Total GHG emissions per year for Internal Actions pathway (tCO2e)"
          items:
            type: "number"
        ghgINTsumperyearMin:
          type: "array"
          description: "Minimum GHG emissions per year for Internal Actions pathway (uncertainty range, tCO2e)"
          items:
            type: "number"
        ghgINTsumperyearMax:
          type: "array"
          description: "Maximum GHG emissions per year for Internal Actions pathway (uncertainty range, tCO2e)"
          items:
            type: "number"
        bauScopeEmissions:
          type: "object"
          description: "Business As Usual emissions by GHG Protocol scope for each year"
          properties:
            "1":
              type: "array"
              items:
                type: "number"
            "2":
              type: "array"
              items:
                type: "number"
            "3":
              type: "array"
              items:
                type: "number"
        extScopeEmissions:
          type: "object"
          description: "External Trends pathway emissions by GHG Protocol scope for each year"
          properties:
            "1":
              type: "array"
              items:
                type: "number"
            "2":
              type: "array"
              items:
                type: "number"
            "3":
              type: "array"
              items:
                type: "number"
        intScopeEmissions:
          type: "object"
          description: "Internal Actions pathway emissions by GHG Protocol scope for each year"
          properties:
            "1":
              type: "array"
              items:
                type: "number"
            "2":
              type: "array"
              items:
                type: "number"
            "3":
              type: "array"
              items:
                type: "number"
        sbtiPathwayValues:
          type: "array"
          description: "Science Based Targets initiative (SBTi) pathway emissions for each target year (tCO2e)"
          items:
            type: "number"
        sbtiPathwayValuesWithoutFLA:
          type: "array"
          description: "SBTi pathway without Forward Looking Ambition adjustment (for comparison)"
          items:
            type: "number"
        userDefinedPathwayValues:
          type: "array"
          description: "Primary user-defined custom target pathway emissions for each target year (tCO2e)"
          items:
            type: "number"
        userDefinedPathwayAlternatives:
          type: "array"
          description: "Computed pathways for comparison custom targets (extended calculation; chart overlay only)"
          items:
            $ref: "#/components/schemas/UserDefinedPathwayAlternative"
        baseYearEmissionsPerScope:
          type: "object"
          description: "Total emissions in the base year by GHG Protocol scope (tCO2e)"
          properties:
            "1":
              type: "number"
              description: "Scope 1 base year emissions"
            "2":
              type: "number"
              description: "Scope 2 base year emissions"
            "3":
              type: "number"
              description: "Scope 3 base year emissions"
        historicemissions:
          type: "object"
          description: "Aggregated historic emissions across all org units by scope"
          properties:
            "1":
              type: "array"
              items:
                type: "number"
            "2":
              description: "Scope 2 historic emissions (legacy flat array or nested location/market)"
              oneOf:
                - type: "array"
                  items:
                    type: "number"
                - $ref: "#/components/schemas/HistoricScope2Emissions"
            "3":
              type: "array"
              items:
                type: "number"
        waterfalloutput:
          type: "array"
          description: "LMDI waterfall decomposition showing contribution of growth, activity trends, emission factors, and internal actions to emissions changes"
          items:
            type: "object"
        internalActionsContributions:
          type: "array"
          description: "Individual contribution of each internal action in its first year of implementation (tCO2e saved)"
          items:
            type: "object"
            properties:
              actionname:
                type: "string"
              activity:
                type: "string"
                description: "Flattened activity instance id the action applies to (same `activity` as projected arrays; may include `@@region`)."
              startyear:
                type: "integer"
              firstYearImpact:
                type: "number"
                description: "tCO2e saved in first year"
        externalActivityTrendsContributions:
          type: "array"
          description: "Cumulative carbon impact and present-valued operational cost savings of external activity trends over the projection period. Org-level rows are rolled up by activitytrendid in reporting currency. Savings fields are null when no unit cost resolves. See Docs/FRONTEND_EXTERNAL_TRENDS_COST_HANDOVER.md."
          items:
            type: "object"
        emissionFactorTrendsContributions:
          type: "array"
          description: "Cumulative impact of emission factor trends (e.g., grid decarbonization) over the projection period"
          items:
            type: "object"
        emissions2030EXT:
          type: "array"
          description: "Emissions breakdown by activity for year 2030 (External Trends pathway)"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Flattened activity instance id for this breakdown row (may include `@@region`)."
              displayName:
                type: "string"
              category:
                type: "string"
              emissions:
                type: "number"
              ghgpscope:
                type: "string"
        emissions2030INT:
          type: "array"
          description: "Emissions breakdown by activity for year 2030 (Internal Actions pathway)"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Flattened activity instance id for this breakdown row (may include `@@region`)."
        emissions2040EXT:
          type: "array"
          description: "Emissions breakdown by activity for year 2040 (External Trends pathway)"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Flattened activity instance id for this breakdown row (may include `@@region`)."
        emissions2040INT:
          type: "array"
          description: "Emissions breakdown by activity for year 2040 (Internal Actions pathway)"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Flattened activity instance id for this breakdown row (may include `@@region`)."
        emissions2050EXT:
          type: "array"
          description: "Emissions breakdown by activity for year 2050 (External Trends pathway)"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Flattened activity instance id for this breakdown row (may include `@@region`)."
        emissions2050INT:
          type: "array"
          description: "Emissions breakdown by activity for year 2050 (Internal Actions pathway)"
          items:
            type: "object"
            properties:
              activity:
                type: "string"
                description: "Flattened activity instance id for this row"
        methodology:
          $ref: "#/components/schemas/MethodologyOutput"

    MethodologyOutput:
      type: "object"
      description: "Methodology statement describing calculation assumptions, sources, and outputs"
      properties:
        title:
          type: "string"
          description: "Title of the methodology statement"
          example: "Viable Pathway Projection Methodology"
        content:
          type: "string"
          description: "HTML-formatted methodology content with detailed explanation of calculations"
          example: "<div><h1>Overview</h1><p>The Viable Pathway projection engine generates three distinct emission pathway scenarios...</p></div>"

    TemplateResponse:
      type: "object"
      description: "Empty template showing the required structure for calculation input payload"

    StandardNamesResponse:
      type: "object"
      description: "Standardized naming conventions for activities, categories, emission factors, and regions"
      properties:
        activity:
          type: "array"
          description: "Catalog (standardnames) activity identifiers. Calculate request/response flattened rows use **instance ids** (`activity` on projected arrays), which may append `@@region`; see `Docs/DATA_ARCHITECTURE.md`."
          items:
            type: "string"
        category:
          type: "array"
          description: "List of valid category names"
          items:
            type: "string"
        country:
          type: "array"
          description: "List of supported countries"
          items:
            type: "string"
        region:
          type: "array"
          description: "Regions organized by country"
          items:
            type: "array"
        ghgpscopecategorynames:
          type: "object"
          description: "Human-readable names for GHG Protocol scope categories"

    EmissionFactorTrend:
      type: "object"
      description: "Detailed information about a specific emission factor trend"
      properties:
        emissionfactorid:
          type: "integer"
          description: "Unique identifier for this emission factor trend"
        emissionfactorname:
          type: "string"
          description: "Name of the emission factor"
        category:
          type: "string"
          description: "Category of the emission factor"
        activityuom:
          type: "string"
          description: "Unit of measurement for associated activity"
        emissionfactorunit:
          type: "string"
          description: "Unit of measurement for the emission factor"
        country:
          type: "string"
          description: "Relevant country"
        region:
          type: "string"
          description: "Relevant region"
        sources:
          type: "array"
          description: "Multiple data sources with their projections"
          items:
            type: "object"

    ActivityTrend:
      type: "object"
      description: "Detailed information about a specific external activity trend"
      properties:
        activityid:
          type: "integer"
          description: "Unique identifier for this activity trend"
        trend:
          type: "string"
          description: "Name/description of the trend"
        trendtype:
          type: "string"
          description: "Type of trend (Social, Environmental, Technological, etc.)"
        category:
          type: "string"
          description: "Activity category affected"
        activity:
          type: "string"
          description: "Catalog activity id from the trend library (trend definitions use standardnames-style ids; calculate I/O rows use flattened instance ids when regional)."
        activityuom:
          type: "string"
          description: "Unit of measurement"
        balancingactivity:
          type: "string"
          description: "Catalog balancing activity id from the trend library (may be resolved to an instance id in the engine when matching projection rows)"
        trendshape:
          type: "string"
          description: "Shape of trend over time (Linear, S-curve, Exponential)"
        multiplierperyear:
          type: "array"
          description: "Multiplier for each projection year"
          items:
            type: "number"
        startyear:
          type: "integer"
          description: "Year when trend begins"
        endyear:
          type: "integer"
          description: "Year when trend reaches maximum effect"

    ScenarioAnalysisResults:
      type: "object"
      description: "Results from scenario analysis across multiple climate narratives"
      properties:
        organisationid:
          type: "string"
          description: "Organisation identifier"
        projectionstartyear:
          type: "integer"
          description: "Start year for projections"
        projectionendyear:
          type: "integer"
          description: "End year for projections"
        baselineEmissions:
          type: "number"
          description: "Total base year emissions (tCO2e)"
        sections:
          type: "array"
          description: "Formatted report sections for display"
          items:
            type: "object"
            properties:
              title:
                type: "string"
              content:
                type: "string"
                description: "HTML-formatted content"
        narrativeSummaries:
          type: "array"
          description: "Summary results for each climate narrative scenario"
          items:
            type: "object"
            properties:
              narrativeid:
                type: "string"
              narrativename:
                type: "string"
              projectionyears:
                type: "array"
                items:
                  type: "integer"
              ghgBAUsumperyear:
                type: "array"
                items:
                  type: "number"
              ghgEXTsumperyear:
                type: "array"
                items:
                  type: "number"
              ghgINTsumperyear:
                type: "array"
                items:
                  type: "number"

    OrganizationInputPathOperation:
      type: "object"
      description: "Single nested path assignment operation for org input save requests. v1 supports deterministic object-key assignment and updates to existing array indexes; insert/delete/reorder structural array edits are rejected."
      required:
        - path
        - value
      properties:
        path:
          description: "Nested path to assign (dot notation, slash notation, or path array). Array segments must reference existing indexes."
          oneOf:
            - type: "string"
              example: "orgunits.orgunit1.name"
            - type: "array"
              items:
                  oneOf:
                    - type: "string"
                    - type: "integer"
        value:
          description: "Value to assign at the nested path"


    OrganizationInputSaveFullRequest:
      type: "object"
      description: "Save request carrying the full organization input payload"
      required:
        - baseRevision
        - inputData
      properties:
        baseRevision:
          type: "string"
          nullable: true
          description: "Client's current latest revision ID at save time"
          example: "0f6d52de-bf0f-498e-bf4f-534f0ae71465"
        calculationType:
          type: "string"
          description: "Calculation profile associated with the input payload"
          default: "standard"
        inputData:
          $ref: "#/components/schemas/CalculationInputs"

    OrganizationInputSaveOperationsRequest:
      type: "object"
      description: "Save request carrying a batch of nested path operations"
      required:
        - baseRevision
        - operations
      properties:
        baseRevision:
          type: "string"
          nullable: true
          description: "Client's current latest revision ID at save time"
          example: "0f6d52de-bf0f-498e-bf4f-534f0ae71465"
        calculationType:
          type: "string"
          description: "Calculation profile associated with the patched payload"
          default: "standard"
        operations:
          type: "array"
          minItems: 1
          items:
            $ref: "#/components/schemas/OrganizationInputPathOperation"

    OrganizationInputRevisionMetadata:
      type: "object"
      description: "Canonical metadata describing a saved organization input revision"
      properties:
        revision:
          type: "string"
        updatedAt:
          type: "string"
          format: "date-time"
        updatedBy:
          type: "string"
          nullable: true
        changedPaths:
          type: "array"
          description: "Paths persisted in this revision. `*` indicates a full-payload save."
          items:
            type: "string"
        ownerType:
          type: "string"
        ownerId:
          type: "string"
        timestamp:
          type: "string"
          format: "date-time"
        calculationId:
          type: "string"
        organisationId:
          type: "string"
          nullable: true
        calculationType:
          type: "string"
        userId:
          type: "string"
          nullable: true

    OrganizationInputSaveResponse:
      type: "object"
      description: "Response returned after save-only org input persistence"
      properties:
        mode:
          type: "string"
          enum:
            - "full"
            - "operations"
        baseRevision:
          type: "string"
          nullable: true
        latestRevision:
          type: "string"
          nullable: true
        revision:
          $ref: "#/components/schemas/OrganizationInputRevisionMetadata"
        inputData:
          type: "object"
          additionalProperties: true

    OrganizationInputSaveConflictResponse:
      type: "object"
      description: "Response returned when a save request overlaps with a newer revision"
      properties:
        error:
          type: "string"
          enum:
            - "Conflict"
        message:
          type: "string"
        conflict:
          type: "object"
          properties:
            mode:
              type: "string"
              enum:
                - "full"
                - "operations"
            baseRevision:
              type: "string"
              nullable: true
            latestRevision:
              type: "string"
              nullable: true
            localChangedPaths:
              type: "array"
              description: "Normalized paths from the local save request."
              items:
                type: "string"
            remoteChangedPaths:
              type: "array"
              description: "Normalized paths from the latest persisted remote revision."
              items:
                type: "string"
            overlappingPaths:
              type: "array"
              description: "Path pairs that overlap and therefore cannot be auto-merged."
              items:
                type: "object"
                properties:
                  localPath:
                    type: "string"
                  remotePath:
                    type: "string"
            latest:
              $ref: "#/components/schemas/OrganizationInputRevisionMetadata"

    ErrorResponse:
      type: "object"
      description: "Error response returned when API request fails"
      properties:
        error:
          type: "string"
          description: "Error type"
          example: "Request validation failed"
        message:
          type: "string"
          description: "Human-readable summary of the failure"
          example: "body.neartermsbtitarget: must be equal to one of the allowed values (allowed: \"1.5\", \"WB2\", \"2\", null)"
        path:
          type: "string"
          description: "Request path that failed"
          example: "/api/calculate"
        details:
          type: "array"
          description: "Per-field OpenAPI / AJV validation failures (when request validation fails)"
          items:
            type: "object"
            properties:
              location:
                type: "string"
                description: "Where validation failed (body, query, path, headers)"
                example: "body"
              path:
                description: "Property path within the location"
                oneOf:
                  - type: "string"
                  - type: "array"
                    items:
                      type: "string"
              message:
                type: "string"
                description: "Detailed validation message, including allowed values or limits when available"
              errorCode:
                type: "string"
              keyword:
                type: "string"
              schemaPath:
                type: "string"
              allowedValues:
                type: "array"
                items: {}
              summary:
                type: "string"
                description: "Combined location.path + message for logging and UI toasts"
                example: "body.neartermsbtitarget: must be equal to one of the allowed values (allowed: \"1.5\", \"WB2\", \"2\", null)"
        stack:
          type: "string"
          description: "Error stack trace (only in development)"
