# Journey Emissions API - Combined Distance & Emissions Calculator

## Base URL

```
https://greenhouse.vixsoft.systems/api/
```

## Endpoint

**POST** `/journey-emissions.php`

Calculate distances and greenhouse gas emissions for freight journeys in a single request. Supports multi-segment routes with optional port/airport segments and automatically calculates emissions for road freight segments using DEFRA 2024 conversion factors.

---

## Request Format

### Headers

```
Content-Type: application/json
```

### Request Body Schema

```json
{
  "journeyId": "string (optional)",
  "startTown": "string (required)",
  "startTownPostcode": "string (optional)",
  "departurePort": "string (optional)",
  "departurePortType": "string (optional: 'airport' | 'port' | auto-detected)",
  "arrivalPort": "string (optional)",
  "arrivalPortType": "string (optional: 'airport' | 'port' | auto-detected)",
  "destinationTown": "string (optional - required if no ports specified)",
  "destinationTownPostcode": "string (optional)",
  "mode": "string (optional: 'road' | 'air' | 'sea' | 'combined', default: 'road')",
  "vehicle": {
    "category": "string (required for emissions calculation)",
    "fuelType": "string (required for emissions calculation)"
  },
  "activity": {
    "payloadTonnes": "number (optional)"
  },
  "consignments": [
    {
      "consignmentId": "string (optional)",
      "customerId": "string (optional)",
      "weightTonnes": "number (optional)"
    }
  ],
  "reportingContext": {
    "countryCode": "string (optional, default: 'GB')",
    "reportingYear": "number (optional, default: 2024)",
    "factorSet": "string (optional)"
  }
}
```

---

## Field Descriptions

### Journey Routing Fields

- **`startTown`** (required): Starting/collection town/city name
- **`startTownPostcode`** (optional): ZIP/postcode for startTown. Helps disambiguate cities with the same name (e.g., "Pasadena, CA 91101" vs "Pasadena, TX 77501"). Supports US ZIP codes and UK postcodes.
- **`destinationTown`** (optional): Destination/delivery town/city name. Required if no ports are specified. If omitted, journey ends at the last port.
- **`destinationTownPostcode`** (optional): ZIP/postcode for destinationTown. Helps disambiguate cities with the same name.
- **`departurePort`** (optional): Departure port/airport code (IATA for airports, UN/LOCODE for ports)
- **`departurePortType`** (optional): Explicitly specify `"airport"` or `"port"`. If omitted, auto-detected from code format
- **`arrivalPort`** (optional): Arrival port/airport code
- **`arrivalPortType`** (optional): Explicitly specify `"airport"` or `"port"`. If omitted, auto-detected from code format
- **`mode`** (optional): Transport mode preference for port-to-port segments. Defaults to `"road"`. Used when port types are ambiguous

### Emissions Calculation Fields

- **`vehicle.category`** (required for emissions): One of the supported vehicle categories (see below)
- **`vehicle.fuelType`** (required for emissions): One of the supported fuel types (see below)
- **`activity.payloadTonnes`** (optional): Weight of freight carried in tonnes. Enables tonne-km calculation
- **`consignments`** (optional): Array of consignment objects for per-customer allocation
- **`reportingContext.countryCode`** (optional): ISO 2-letter country code. Defaults to `"GB"`
- **`reportingContext.reportingYear`** (optional): Year for emission factors. Defaults to `2024`

---

## Journey Types

The API supports multiple journey configurations:

### Type 1: Direct Town-to-Town
**Fields:** `startTown` + `destinationTown`  
**Segments:** 1 road segment  
**Example:**
```json
{
  "startTown": "Leeds",
  "destinationTown": "Birmingham",
  "vehicle": { "category": "artic_over_33t", "fuelType": "diesel" }
}
```

**Example with postcodes (disambiguating cities):**
```json
{
  "startTown": "Pasadena",
  "startTownPostcode": "91101",
  "destinationTown": "Pasadena",
  "destinationTownPostcode": "77501",
  "vehicle": { "category": "artic_over_33t", "fuelType": "diesel" }
}
```

### Type 2: Collection Town → Departure Port (Journey ends at port)
**Fields:** `startTown` + `departurePort`  
**Segments:** 1 road segment  
**Example:**
```json
{
  "startTown": "Leeds",
  "departurePort": "MAN",
  "departurePortType": "airport",
  "vehicle": { "category": "artic_over_33t", "fuelType": "diesel" }
}
```

### Type 3: Collection Town → Arrival Port (Journey ends at port)
**Fields:** `startTown` + `arrivalPort`  
**Segments:** 1 road segment  
**Example:**
```json
{
  "startTown": "London",
  "arrivalPort": "USNYC",
  "arrivalPortType": "port",
  "vehicle": { "category": "rigid_7_5t_to_17t", "fuelType": "diesel" }
}
```

### Type 4: Collection Town → Departure Port → Arrival Port (Journey ends at arrival port)
**Fields:** `startTown` + `departurePort` + `arrivalPort`  
**Segments:** 1 road + 1 air/sea segment  
**Example:**
```json
{
  "startTown": "Leeds",
  "departurePort": "MAN",
  "departurePortType": "airport",
  "arrivalPort": "JFK",
  "arrivalPortType": "airport",
  "vehicle": { "category": "artic_over_33t", "fuelType": "diesel" }
}
```

### Type 5: Collection Town → Departure Port → Destination Town
**Fields:** `startTown` + `departurePort` + `destinationTown`  
**Segments:** 2 road segments  
**Example:**
```json
{
  "startTown": "Birmingham",
  "departurePort": "BHX",
  "destinationTown": "London",
  "vehicle": { "category": "rigid_7_5t_to_17t", "fuelType": "diesel" }
}
```

### Type 6: Collection Town → Arrival Port → Destination Town
**Fields:** `startTown` + `arrivalPort` + `destinationTown`  
**Segments:** 2 road segments  
**Example:**
```json
{
  "startTown": "London",
  "arrivalPort": "USNYC",
  "arrivalPortType": "port",
  "destinationTown": "New York",
  "vehicle": { "category": "rigid_7_5t_to_17t", "fuelType": "diesel" }
}
```

### Type 7: Full Multi-Modal Journey (Collection Town → Departure Port → Arrival Port → Destination Town)
**Fields:** `startTown` + `departurePort` + `arrivalPort` + `destinationTown`  
**Segments:** 2 road + 1 air/sea segment  
**Example:**
```json
{
  "startTown": "Leeds",
  "departurePort": "MAN",
  "departurePortType": "airport",
  "arrivalPort": "JFK",
  "arrivalPortType": "airport",
  "destinationTown": "New York",
  "vehicle": { "category": "artic_over_33t", "fuelType": "diesel" }
}
```

---

## Supported Vehicle Categories

### Light Goods Vehicles (LGVs/Vans)
- `lgv_up_to_3_5t` - Vans up to 3.5 tonnes
- `lgv_class_i` - Class I (up to 1.305 tonnes)
- `lgv_class_ii` - Class II (1.305 to 1.74 tonnes)
- `lgv_average` - Average van (up to 3.5 tonnes)

### Heavy Goods Vehicles - Rigid Trucks
- `rigid_3_5t_to_7_5t` - Rigid trucks 3.5t to 7.5t
- `rigid_7_5t_to_17t` - Rigid trucks 7.5t to 17t
- `rigid_over_17t` - Rigid trucks over 17t

### Heavy Goods Vehicles - Articulated Trucks
- `artic_up_to_33t` - Articulated trucks up to 33t
- `artic_over_33t` - Articulated trucks over 33t

### Refrigerated Vehicles
Add `_refrigerated` suffix to any category above (e.g., `artic_over_33t_refrigerated`)

---

## Supported Fuel Types

- `diesel` - Diesel (average biofuel blend)
- `petrol` - Petrol (average biofuel blend)
- `cng` - Compressed Natural Gas
- `lng` - Liquefied Natural Gas
- `lpg` - Liquefied Petroleum Gas
- `electric` - Battery Electric Vehicle

**Note:** All HGVs are assumed to be diesel. Alternative fuels are primarily available for LGVs.

---

## Response Format

### Success Response (200 OK)

```json
{
  "journeyId": "string",
  "distance": {
    "requestId": "string | null",
    "route": {
      "startTown": "string",
      "departurePort": "string | null",
      "departurePortType": "string | null",
      "arrivalPort": "string | null",
      "arrivalPortType": "string | null",
      "destinationTown": "string | null",
      "journeyEndsAt": "destinationTown | arrivalPort | departurePort"
    },
    "segments": [
      {
        "segmentNumber": 1,
        "description": "string",
        "from": "string",
        "fromType": "string | null",
        "fromCoordinates": {
          "lat": 53.8008,
          "lon": -1.5491,
          "name": "string",
          "type": "string"
        },
        "to": "string",
        "toType": "string | null",
        "toCoordinates": {
          "lat": 52.4862,
          "lon": -1.8904,
          "name": "string",
          "type": "string"
        },
        "mode": "road | air | sea",
        "transportType": "Truck/Road | Airplane | Ship",
        "distanceKm": 120.5,
        "distanceMiles": 74.8
      }
    ],
    "segmentSummary": [
      {
        "segment": 1,
        "route": "Leeds → MAN",
        "mode": "road",
        "transportType": "Truck/Road",
        "distanceKm": 82.86
      }
    ],
    "totalDistanceKm": 5444.75,
    "totalDistanceMiles": 3383.21,
    "calculationMethod": "haversine_great_circle",
    "notes": "string"
  },
  "emissions": {
    "roadSegments": {
      "totalDistanceKm": 82.86,
      "totalEmissionsKgCO2e": 75.61,
      "segments": [
        {
          "segmentNumber": 1,
          "distanceKm": 82.86,
          "emissionsKgCO2e": 75.61,
          "from": "Leeds",
          "to": "MAN"
        }
      ]
    },
    "airSegments": {
      "totalDistanceKm": 5361.89,
      "totalEmissionsKgCO2e": 98221.79,
      "segments": [
        {
          "segmentNumber": 2,
          "distanceKm": 5361.89,
          "emissionsKgCO2e": 98221.79,
          "from": "MAN",
          "to": "JFK"
        }
      ]
    },
    "seaSegments": {
      "totalDistanceKm": 0,
      "totalEmissionsKgCO2e": null,
      "segments": []
    },
    "totalEmissionsKgCO2e": 98297.40
  }
}
```

When air or sea factors are not available, `airSegments` / `seaSegments` may include a **`note`** instead of emissions; `totalEmissionsKgCO2e` then includes only road (and any available air/sea).

### Response Fields

#### Distance Section
- **`distance.route`**: Complete route information including where the journey ends
- **`distance.route.journeyEndsAt`**: Indicates where the journey terminates (`destinationTown`, `arrivalPort`, or `departurePort`)
- **`distance.segments`**: Array of all journey segments with full details
- **`distance.segmentSummary`**: Simplified segment summary for quick reference
- **`distance.totalDistanceKm`**: Total distance across all segments

#### Emissions Section
- **`emissions.roadSegments`**: Road segment emissions
  - **`totalDistanceKm`**: Sum of all road segment distances
  - **`totalEmissionsKgCO2e`**: Total CO₂e for road segments
  - **`segments`**: Per-segment emissions breakdown
- **`emissions.airSegments`**: Air segment distance and, when DEFRA factors and `activity.payloadTonnes` are provided, CO₂e per segment
- **`emissions.seaSegments`**: Sea segment distance and, when DEFRA factors and `activity.payloadTonnes` are provided, CO₂e per segment
- **`emissions.totalEmissionsKgCO2e`**: Total CO₂e across road, air, and sea (when factors are available). May include a **`note`** when air/sea factors are missing

---

## Error Responses

### 400 Bad Request

**Missing Required Field:**
```json
{
  "error": "Bad request",
  "details": "Missing required field \"startTown\"."
}
```

**Invalid Journey Configuration:**
```json
{
  "error": "Bad request",
  "details": "Either destinationTown or at least one port (departurePort/arrivalPort) must be provided."
}
```

**Missing Vehicle Information:**
```json
{
  "error": "Bad request",
  "details": "Missing required field \"vehicle.category\"."
}
```

### 404 Not Found

**Emission Factor Not Found:**
```json
{
  "error": "Emission factor not found",
  "details": "No emission factor found for the given year/country/vehicle/fuel."
}
```

### 429 Too Many Requests (Rate limit exceeded)

When the request rate for your IP exceeds the limit (e.g. 100 requests per hour):

```json
{
  "error": "Rate limit exceeded",
  "message": "Too many requests. Please try again later.",
  "limit": 100,
  "windowMinutes": 60,
  "resetAt": "2024-01-15T11:30:00+00:00"
}
```

Response headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`.

### 500 Internal Server Error

```json
{
  "error": "Internal server error",
  "details": "Error message..."
}
```

Clients must treat 500 responses as opaque and must not rely on or parse any implementation-specific fields.

---

## Example Requests

### Example 1: Simple Town-to-Town Journey

**Request:**
```bash
POST https://greenhouse.vixsoft.systems/api/journey-emissions.php
Content-Type: application/json

{
  "journeyId": "JRN-2024-001",
  "startTown": "Leeds",
  "destinationTown": "Birmingham",
  "vehicle": {
    "category": "artic_over_33t",
    "fuelType": "diesel"
  },
  "activity": {
    "payloadTonnes": 20.0
  },
  "reportingContext": {
    "countryCode": "GB",
    "reportingYear": 2024
  }
}
```

**Response:**
```json
{
  "journeyId": "JRN-2024-001",
  "distance": {
    "route": {
      "startTown": "Leeds",
      "destinationTown": "Birmingham",
      "journeyEndsAt": "destinationTown"
    },
    "segments": [
      {
        "segmentNumber": 1,
        "mode": "road",
        "distanceKm": 120.5,
        "from": "Leeds",
        "to": "Birmingham"
      }
    ],
    "totalDistanceKm": 120.5
  },
  "emissions": {
    "roadSegments": {
      "totalDistanceKm": 120.5,
      "totalEmissionsKgCO2e": 109.95,
      "segments": [
        {
          "segmentNumber": 1,
          "distanceKm": 120.5,
          "emissionsKgCO2e": 109.95,
          "from": "Leeds",
          "to": "Birmingham"
        }
      ]
    },
    "airSegments": {
      "totalDistanceKm": 0,
      "note": "Air freight emissions calculation not yet supported. DEFRA 2024 factors required."
    },
    "seaSegments": {
      "totalDistanceKm": 0,
      "note": "Sea freight emissions calculation not yet supported. DEFRA 2024 factors required."
    },
    "totalEmissionsKgCO2e": 109.95
  }
}
```

---

### Example 2: Multi-Segment Journey Ending at Arrival Port

**Request:**
```bash
POST https://greenhouse.vixsoft.systems/api/journey-emissions.php
Content-Type: application/json

{
  "journeyId": "JRN-2024-005",
  "startTown": "Leeds",
  "departurePort": "MAN",
  "departurePortType": "airport",
  "arrivalPort": "JFK",
  "arrivalPortType": "airport",
  "vehicle": {
    "category": "artic_over_33t",
    "fuelType": "diesel"
  },
  "activity": {
    "payloadTonnes": 20.0
  },
  "reportingContext": {
    "countryCode": "GB",
    "reportingYear": 2024
  }
}
```

**Response:**
```json
{
  "journeyId": "JRN-2024-005",
  "distance": {
    "route": {
      "startTown": "Leeds",
      "departurePort": "MAN",
      "departurePortType": "airport",
      "arrivalPort": "JFK",
      "arrivalPortType": "airport",
      "destinationTown": null,
      "journeyEndsAt": "arrivalPort"
    },
    "segments": [
      {
        "segmentNumber": 1,
        "mode": "road",
        "distanceKm": 82.86,
        "from": "Leeds",
        "to": "MAN"
      },
      {
        "segmentNumber": 2,
        "mode": "air",
        "distanceKm": 5361.89,
        "from": "MAN",
        "to": "JFK"
      }
    ],
    "totalDistanceKm": 5444.75
  },
  "emissions": {
    "roadSegments": {
      "totalDistanceKm": 82.86,
      "totalEmissionsKgCO2e": 75.61,
      "segments": [
        {
          "segmentNumber": 1,
          "distanceKm": 82.86,
          "emissionsKgCO2e": 75.61,
          "from": "Leeds",
          "to": "MAN"
        }
      ]
    },
    "airSegments": {
      "totalDistanceKm": 5361.89,
      "note": "Air freight emissions calculation not yet supported. DEFRA 2024 factors required."
    },
    "seaSegments": {
      "totalDistanceKm": 0,
      "note": "Sea freight emissions calculation not yet supported. DEFRA 2024 factors required."
    },
    "totalEmissionsKgCO2e": 75.61,
    "note": "Total includes road segments only. Air/sea segments excluded until emission factors are added."
  }
}
```

---

### Example 3: Full Multi-Modal Journey

**Request:**
```bash
POST https://greenhouse.vixsoft.systems/api/journey-emissions.php
Content-Type: application/json

{
  "journeyId": "JRN-2024-007",
  "startTown": "Leeds",
  "departurePort": "MAN",
  "departurePortType": "airport",
  "arrivalPort": "JFK",
  "arrivalPortType": "airport",
  "destinationTown": "New York",
  "vehicle": {
    "category": "artic_over_33t",
    "fuelType": "diesel"
  },
  "activity": {
    "payloadTonnes": 20.0
  },
  "consignments": [
    {
      "consignmentId": "CON-1001",
      "customerId": "CUST-A",
      "weightTonnes": 12.0
    },
    {
      "consignmentId": "CON-1002",
      "customerId": "CUST-B",
      "weightTonnes": 8.0
    }
  ],
  "reportingContext": {
    "countryCode": "GB",
    "reportingYear": 2024
  }
}
```

**Response:**
```json
{
  "journeyId": "JRN-2024-007",
  "distance": {
    "route": {
      "startTown": "Leeds",
      "departurePort": "MAN",
      "departurePortType": "airport",
      "arrivalPort": "JFK",
      "arrivalPortType": "airport",
      "destinationTown": "New York",
      "journeyEndsAt": "destinationTown"
    },
    "segments": [
      {
        "segmentNumber": 1,
        "mode": "road",
        "distanceKm": 82.86,
        "from": "Leeds",
        "to": "MAN"
      },
      {
        "segmentNumber": 2,
        "mode": "air",
        "distanceKm": 5361.89,
        "from": "MAN",
        "to": "JFK"
      },
      {
        "segmentNumber": 3,
        "mode": "road",
        "distanceKm": 60.0,
        "from": "JFK",
        "to": "New York"
      }
    ],
    "totalDistanceKm": 5504.75
  },
  "emissions": {
    "roadSegments": {
      "totalDistanceKm": 142.86,
      "totalEmissionsKgCO2e": 130.35,
      "segments": [
        {
          "segmentNumber": 1,
          "distanceKm": 82.86,
          "emissionsKgCO2e": 75.61,
          "from": "Leeds",
          "to": "MAN"
        },
        {
          "segmentNumber": 3,
          "distanceKm": 60.0,
          "emissionsKgCO2e": 54.74,
          "from": "JFK",
          "to": "New York"
        }
      ]
    },
    "airSegments": {
      "totalDistanceKm": 5361.89,
      "note": "Air freight emissions calculation not yet supported. DEFRA 2024 factors required."
    },
    "seaSegments": {
      "totalDistanceKm": 0,
      "note": "Sea freight emissions calculation not yet supported. DEFRA 2024 factors required."
    },
    "totalEmissionsKgCO2e": 130.35,
    "note": "Total includes road segments only. Air/sea segments excluded until emission factors are added."
  }
}
```

---

### Example 4: Journey Ending at Departure Port

**Request:**
```bash
POST https://greenhouse.vixsoft.systems/api/journey-emissions.php
Content-Type: application/json

{
  "journeyId": "JRN-2024-004",
  "startTown": "Leeds",
  "departurePort": "MAN",
  "departurePortType": "airport",
  "vehicle": {
    "category": "artic_over_33t",
    "fuelType": "diesel"
  },
  "activity": {
    "payloadTonnes": 20.0
  },
  "reportingContext": {
    "countryCode": "GB",
    "reportingYear": 2024
  }
}
```

**Response:**
```json
{
  "journeyId": "JRN-2024-004",
  "distance": {
    "route": {
      "startTown": "Leeds",
      "departurePort": "MAN",
      "departurePortType": "airport",
      "journeyEndsAt": "departurePort"
    },
    "segments": [
      {
        "segmentNumber": 1,
        "mode": "road",
        "distanceKm": 82.86,
        "from": "Leeds",
        "to": "MAN"
      }
    ],
    "totalDistanceKm": 82.86
  },
  "emissions": {
    "roadSegments": {
      "totalDistanceKm": 82.86,
      "totalEmissionsKgCO2e": 75.61,
      "segments": [
        {
          "segmentNumber": 1,
          "distanceKm": 82.86,
          "emissionsKgCO2e": 75.61,
          "from": "Leeds",
          "to": "MAN"
        }
      ]
    },
    "airSegments": {
      "totalDistanceKm": 0,
      "note": "Air freight emissions calculation not yet supported. DEFRA 2024 factors required."
    },
    "seaSegments": {
      "totalDistanceKm": 0,
      "note": "Sea freight emissions calculation not yet supported. DEFRA 2024 factors required."
    },
    "totalEmissionsKgCO2e": 75.61
  }
}
```

---

## cURL Examples

### Simple Town-to-Town
```bash
curl -X POST https://greenhouse.vixsoft.systems/api/journey-emissions.php \
  -H "Content-Type: application/json" \
  -d '{
    "startTown": "Leeds",
    "destinationTown": "Birmingham",
    "vehicle": {
      "category": "artic_over_33t",
      "fuelType": "diesel"
    }
  }'
```

### Multi-Segment with Air Freight
```bash
curl -X POST https://greenhouse.vixsoft.systems/api/journey-emissions.php \
  -H "Content-Type: application/json" \
  -d '{
    "journeyId": "JRN-2024-005",
    "startTown": "Leeds",
    "departurePort": "MAN",
    "departurePortType": "airport",
    "arrivalPort": "JFK",
    "arrivalPortType": "airport",
    "vehicle": {
      "category": "artic_over_33t",
      "fuelType": "diesel"
    },
    "activity": {
      "payloadTonnes": 20.0
    }
  }'
```

---

## Port/Airport Code Formats

### Airport Codes (IATA)
- **Format:** 3 uppercase letters
- **Examples:** `MAN`, `JFK`, `LHR`, `BHX`, `EDI`, `LAX`, `CDG`
- **Auto-detected:** Yes (if 3 letters, assumed airport)

### Port Codes (UN/LOCODE)
- **Format:** 5 characters (2-letter country + 3-letter location)
- **Examples:** `GBLON` (London), `USNYC` (New York), `DEHAM` (Hamburg), `NLRTM` (Rotterdam)
- **Auto-detected:** Yes (if 5 characters matching pattern)

---

## Notes

### Emissions Calculation
- **Road Segments:** Fully supported using DEFRA 2024 factors. Emissions calculated per vehicle-km.
- **Air Segments:** Distance and emissions supported when DEFRA 2024 air freight factors are loaded and `activity.payloadTonnes` is provided.
- **Sea Segments:** Distance and emissions supported when DEFRA 2024 sea freight factors are loaded and `activity.payloadTonnes` is provided.

### Distance Calculation
- Uses Haversine formula (great circle distance) for all segments
- Road segments include a 1.2x multiplier to approximate actual road distance
- Port-to-port segments use direct great circle distance (accurate for air/sea routes)

### Journey End Detection
The API automatically detects where the journey ends based on provided fields:
- If `destinationTown` is provided → journey ends at destination town
- If only `departurePort` → journey ends at departure port
- If `departurePort` + `arrivalPort` (no `destinationTown`) → journey ends at arrival port
- If only `arrivalPort` → journey ends at arrival port

This is indicated in the response field `distance.route.journeyEndsAt`.

### Postcode/ZIP Code Support
- **Purpose:** Helps disambiguate cities with the same name (e.g., multiple "Pasadena" cities in the US)
- **Supported Formats:** US ZIP codes (e.g., `91101`, `77501`) and UK postcodes (e.g., `LS1 4DY`, `SW1A 1AA`)
- **Usage:** Include `startTownPostcode` and/or `destinationTownPostcode` when you have postcode data available
- **Geocoding:** Postcodes are appended to the location name in Nominatim queries to improve accuracy
- **Optional:** If omitted, the API will attempt to resolve the city name without postcode (may return incorrect city if multiple exist)

---

## Future Enhancements

- Per-segment consignment allocation
- Support for return journeys
- Integration with routing APIs for more accurate road distances
