Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 275 additions & 1 deletion submit-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,278 @@ Open [http://localhost:5000/api](http://localhost:5000/api) to view it in the br

Ensure the latest version of [VS Code](https://code.visualstudio.com) is installed.

The [`launch.json`](.vscode/launch.json) is already configured with a launch task (SUBMIT-API Launch) that allows you to launch chrome in a debugging capacity and debug through code within the editor.
The [`launch.json`](.vscode/launch.json) is already configured with a launch task (SUBMIT-API Launch) that allows you to launch chrome in a debugging capacity and debug through code within the editor.

## Documentation

- [Database Design: Works Integration](../docs/database-design-works-integration.md) - Database schema for integrating Works, Phases, and work-specific packages from EPIC.track

## API Endpoints

### Staff API: Package Type Management

#### Create or Update Package Type

**Endpoint:** `POST /api/staff/package-types`

**Description:** Creates or updates a package type with phase association. This endpoint is **idempotent** - it will create a new package type if it doesn't exist, or update the existing one if it does.

**Authentication:** Staff users only

**How it Works:**
1. The endpoint identifies the phase using three parameters:
- `ea_act_name`: Environmental Assessment Act name
- `work_type_name`: Work Type name
- `phase_name`: Phase name (matches either `display_name` or `name` in the database)
2. Once the phase is found, it creates or updates a package type and associates it with the specified item types
3. Item types are associated in the order provided, with `sort_order` automatically assigned

**Request Body:**
```json
{
"ea_act_name": "EA Act (2018)",
"work_type_name": "Assessment",
"phase_name": "Early Engagement",
"package_type_name": "IPD",
"item_types": [
{"id": 1},
{"id": 2},
{"name": "Custom Document", "submission_method": "DOCUMENT_UPLOAD"}
]
}
```

**Request Parameters:**

| Parameter | Type | Required | Description | Example |
|-----------|------|----------|-------------|---------|
| `ea_act_name` | string | Yes | Environmental Assessment Act name | `"EA Act (2018)"` |
| `work_type_name` | string | Yes | Work type name from EPIC.track | `"Assessment"` |
| `phase_name` | string | Yes | Phase name (display name or actual name) | `"Early Engagement"` |
| `package_type_name` | string | Yes | Name for the package type to create/update | `"IPD"` |
| `item_types` | array[object] | Yes | List of item types (existing IDs or new definitions) | See below |

**Valid EA Act Names:**
- `"EA Act (1996)"` (ID: 1)
- `"EA Act (2002)"` (ID: 2)
- `"EA Act (2018)"` (ID: 3)
- `"Other"` (ID: 4)

**Valid Work Type Names:**
- `"Project Notification"` (ID: 1)
- `"Minister's Designation"` (ID: 2)
- `"CEAO's Designation"` (ID: 3)
- `"Intake (Pre-EA)"` (ID: 4)
- `"Exemption Order"` (ID: 5)
- `"Assessment"` (ID: 6)
- `"Amendment"` (ID: 7)
- `"Post-EAC Document Review"` (ID: 8)
- `"EAC Extension"` (ID: 9)
- `"Substantial Start Determination"` (ID: 10)
- `"EAC/Order Transfer"` (ID: 11)
- `"EAC/Order Suspension"` (ID: 12)
- `"EAC/Order Cancellation"` (ID: 13)
- `"Other"` (ID: 14)
- `"Material Alteration"` (ID: 15)

**Item Types Format:**

Each item in the `item_types` array can be specified in two ways:

1. **Existing Item Type (by ID):**
```json
{"id": 1}
```

2. **New Item Type (by name and submission method):**
```json
{
"name": "Custom Document Type",
"submission_method": "DOCUMENT_UPLOAD" // or "FORM_SUBMISSION"
}
```

**Valid Submission Methods:**
- `"DOCUMENT_UPLOAD"` - Item requires document upload
- `"FORM_SUBMISSION"` - Item uses form-based submission

**Example Phase Names (for Assessment work type):**
- `"Pre-EA (EAC Assessment)"`
- `"Early Engagement"`
- `"DPD Development (Proponent Time)"`
- `"Process Planning"`
- `"EAC Application Development (Proponent Time)"`
- `"EAC Application Review"`
- `"Effects Assessment & Recommendation"`
- `"EAC Decision"`

**Success Response (200 OK):**
```json
{
"id": 5,
"name": "IPD",
"phase_id": 10,
"phase_name": "Early Engagement",
"ea_act_name": "EA Act (2018)",
"work_type_name": "Assessment",
"item_type_ids": [1, 2, 5],
"created_item_types": [
{
"id": 5,
"name": "Custom Document",
"submission_method": "DOCUMENT_UPLOAD"
}
],
"created": true
}
```

**Response Fields:**

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Package type ID (database primary key) |
| `name` | string | Package type name |
| `phase_id` | integer | Associated phase ID from `track_phases` table |
| `phase_name` | string | Display name of the phase |
| `ea_act_name` | string | Environmental Assessment Act name |
| `work_type_name` | string | Work type name |
| `item_type_ids` | array[int] | List of all associated item type IDs |
| `created_item_types` | array[object] | List of newly created item types (empty if all existed) |
| `created` | boolean | `true` if package type newly created, `false` if updated |

**Error Responses:**

**400 Bad Request** - Validation error
```json
{
"message": "Validation error",
"errors": {
"item_types": ["item_types must contain at least one item type"]
}
}
```

Or for invalid item type definition:
```json
{
"message": "Validation error",
"errors": {
"item_types": {
"0": {
"_schema": ["Must provide either id OR both name and submission_method"]
}
}
}
}
```

**404 Not Found** - Phase or item types not found
```json
{
"message": "Phase not found for EA Act: 'EA Act (2018)', Work Type: 'Assessment', Phase: 'Invalid Phase'"
}
```

Or for invalid item type ID:
```json
{
"message": "An error occurred while creating/updating the package type",
"error": "Item type with ID 999 not found"
}
```

**500 Internal Server Error** - Server error
```json
{
"message": "An error occurred while creating/updating the package type",
"error": "Error details..."
}
```

**Usage Examples:**

**Example 1: Create a new package type using existing item types**
```bash
curl -X POST http://localhost:5000/api/staff/package-types \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"ea_act_name": "EA Act (2018)",
"work_type_name": "Assessment",
"phase_name": "Early Engagement",
"package_type_name": "IPD",
"item_types": [{"id": 1}, {"id": 2}, {"id": 3}]
}'
```

**Example 2: Create package type with mixed item types (existing + new)**
```bash
curl -X POST http://localhost:5000/api/staff/package-types \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"ea_act_name": "EA Act (2018)",
"work_type_name": "Assessment",
"phase_name": "Early Engagement",
"package_type_name": "IPD",
"item_types": [
{"id": 1},
{"id": 2},
{"name": "Environmental Impact Assessment", "submission_method": "DOCUMENT_UPLOAD"}
]
}'
```

**Example 3: Create IPD package type for Early Engagement (real-world example)**
```bash
curl -X POST http://localhost:5000/api/staff/package-types \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"ea_act_name": "EA Act (2018)",
"work_type_name": "Assessment",
"phase_name": "Early Engagement",
"package_type_name": "IPD",
"item_types": [
{
"name": "Initial Project Description",
"submission_method": "DOCUMENT_UPLOAD"
},
{
"name": "Engagement Plan",
"submission_method": "DOCUMENT_UPLOAD"
},
{
"name": "Geospatial Information",
"submission_method": "DOCUMENT_UPLOAD"
},
{
"name": "Early Engagement Proponent Checklist",
"submission_method": "FORM_SUBMISSION"
}
]
}'
```

**Important Notes:**

1. **Idempotency**: Running the same request multiple times will update the existing package type rather than creating duplicates
2. **Phase Matching**: The phase is matched by `ea_act_name`, `work_type_name`, and `phase_name`. The `phase_name` can match either the `display_name` or `name` field in the database
3. **Item Type Creation**: New item types are created automatically if they don't exist. If an item type with the same name already exists, the existing one will be reused
4. **Item Type Order**: Item types are associated in the order provided in the `item_types` array, with `sort_order` starting from 1
5. **Existing Associations**: When updating, all existing item type associations are removed and replaced with the new ones
6. **Phase Data**: Phase data must exist in the `track_phases` table before creating package types. See the [Database Design documentation](../docs/database-design-works-integration.md) for phase data structure

**Related Enums:**

The following enums are available in the codebase for reference:

- `EAActName` (`src/submit_api/enums/ea_act.py`) - Environmental Assessment Act names
- `WorkTypeName` (`src/submit_api/enums/work_type.py`) - Work type names

**Swagger Documentation:**

Interactive API documentation is available at:
- Development: `http://localhost:5000/api/staff/`
- The Swagger UI provides a complete list of available phases and item types
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""adding display_name for phase name

Revision ID: f1d8ab8c2d30
Revises: e27054af162b
Create Date: 2026-02-26 15:12:55.957537

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'f1d8ab8c2d30'
down_revision = 'e27054af162b'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
# Drop unnecessary indexes from small tables
with op.batch_alter_table('package_types', schema=None) as batch_op:
batch_op.drop_index('idx_package_types_name')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't feel like this index would be useful considering the size possible data in this table.

batch_op.drop_index('idx_package_types_phase_id')

# Drop old index and create optimized indexes for packages table
with op.batch_alter_table('packages', schema=None) as batch_op:
batch_op.drop_index('idx_packages_account_project_work_id')

# Create new indexes for packages
op.create_index('idx_packages_account_project_id', 'packages', ['account_project_id'])
op.create_index(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created this as partial index as there is a chance that this field can contain NULL values in case of Management Plan, which will break the index.

'idx_packages_account_project_work_id',
'packages',
['account_project_work_id'],
postgresql_where=sa.text('account_project_work_id IS NOT NULL')
)

# Add display_name and ea_act_name columns to track_phases
with op.batch_alter_table('track_phases', schema=None) as batch_op:
batch_op.add_column(sa.Column('display_name', sa.String(length=255), nullable=True, comment='Submit-specific phase name override; defaults to name field if not set'))
batch_op.add_column(sa.Column('ea_act_name', sa.String(length=255), nullable=True, comment='Environmental Assessment Act name'))

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
# Drop display_name and ea_act_name columns from track_phases
with op.batch_alter_table('track_phases', schema=None) as batch_op:
batch_op.drop_column('ea_act_name')
batch_op.drop_column('display_name')

# Drop new optimized indexes
op.drop_index('idx_packages_account_project_work_id', table_name='packages')
op.drop_index('idx_packages_account_project_id', table_name='packages')

# Restore old indexes
with op.batch_alter_table('packages', schema=None) as batch_op:
batch_op.create_index('idx_packages_account_project_work_id', ['account_project_work_id'], unique=False)

with op.batch_alter_table('package_types', schema=None) as batch_op:
batch_op.create_index('idx_package_types_phase_id', ['phase_id'], unique=False)
batch_op.create_index('idx_package_types_name', ['name'], unique=False)

# ### end Alembic commands ###
33 changes: 33 additions & 0 deletions submit-api/src/submit_api/enums/ea_act.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Environmental Assessment Act enum."""
from enum import Enum


class EAActName(str, Enum):
"""Environmental Assessment Act names."""

EA_ACT_1996 = "EA Act (1996)"
EA_ACT_2002 = "EA Act (2002)"
EA_ACT_2018 = "EA Act (2018)"
OTHER = "Other"

@classmethod
def get_by_id(cls, ea_act_id: int):
"""Get EA Act name by ID."""
mapping = {
1: cls.EA_ACT_1996,
2: cls.EA_ACT_2002,
3: cls.EA_ACT_2018,
4: cls.OTHER,
}
return mapping.get(ea_act_id)

@classmethod
def get_id_by_name(cls, name: str):
"""Get EA Act ID by name."""
mapping = {
cls.EA_ACT_1996.value: 1,
cls.EA_ACT_2002.value: 2,
cls.EA_ACT_2018.value: 3,
cls.OTHER.value: 4,
}
return mapping.get(name)
Loading