# PX360 External API Documentation

Base URL: `/api/v1/external/`

## Authentication

All requests require an `X-API-Key` header.

```
X-API-Key: <your-api-key>
```

API keys are managed through the Django admin panel. Each key can be scoped to:

- A specific hospital (restricts data access to that hospital)
- Specific entities (`complaints`, `inquiries`, `observations`, `appreciations`, `suggestions`, `doctor_ratings`)
- Empty `allowed_entities` = access to all entities

### Creating an API Key

Via Django admin or programmatically:

```python
from apps.integrations.models import ExternalAPIKey

api_key, raw_key = ExternalAPIKey.create_key(
    name="Partner Integration",
    hospital=hospital_object,  # Optional: scope to a hospital
    allowed_entities=["complaints", "inquiries"],  # Optional: empty = all
    rate_limit=60,  # Requests per minute
    description="Integration with partner system",
)
# raw_key is shown ONCE - store it securely
```

### Error Responses

| HTTP Status | Meaning |
|---|---|
| `401` | Missing or invalid API key |
| `403` | API key does not have access to the requested entity, or hospital scope mismatch |
| `404` | Requested resource not found |
| `400` | Validation error (missing/invalid fields) |

---

## Lookup Endpoints

These endpoints provide dropdown/reference data for building forms.

### List Hospitals

```
GET /api/v1/external/hospitals/
```

**Response:**

```json
{
  "count": 3,
  "results": [
    { "id": "uuid", "name": "Al Nuzha", "code": "HH-N" },
    { "id": "uuid", "name": "Al Olya", "code": "HH-A" },
    { "id": "uuid", "name": "Al Suwaidi", "code": "HH-S" }
  ]
}
```

### List Location Types

```
GET /api/v1/external/location-types/
```

**Response:**

```json
{
  "count": 4,
  "results": [
    { "value": "OP", "label": "Outpatient", "label_ar": "خارجي" },
    { "value": "IP", "label": "Inpatient", "label_ar": "تنويم" },
    { "value": "ER", "label": "Emergency", "label_ar": "طوارئ" },
    { "value": "GENERAL", "label": "General", "label_ar": "عام" }
  ]
}
```

### List Areas

```
GET /api/v1/external/areas/?hospital=<hospital_name>
```

| Parameter | Required | Description |
|---|---|---|
| `hospital` | Yes | Hospital name (case-insensitive) |
| `location_type` | No | Filter by location type (`OP`, `IP`, `ER`, `GENERAL`) |

**Response:**

```json
{
  "count": 27,
  "results": [
    { "id": "uuid", "name": "Emergency", "name_ar": "", "code": "emergency" }
  ]
}
```

### List Departments

```
GET /api/v1/external/departments/?hospital=<hospital_name>
```

| Parameter | Required | Description |
|---|---|---|
| `hospital` | Yes | Hospital name (case-insensitive) |

**Response:**

```json
{
  "count": 37,
  "results": [
    { "id": "uuid", "name": "Critical Care Department", "name_en": "Critical Care Department", "name_ar": "", "code": "hh_n_critical_care_department" }
  ]
}
```

### List Sections

```
GET /api/v1/external/sections/?hospital=<hospital_name>&department=<department_name>
```

| Parameter | Required | Description |
|---|---|---|
| `hospital` | Yes | Hospital name (case-insensitive) |
| `department` | Yes | Department name (case-insensitive) |

**Response:**

```json
{
  "count": 3,
  "results": [
    { "id": "uuid", "name": "ICU", "name_ar": "", "code": "hh_n_critical_care_department__icu" }
  ]
}
```

---

## Complaints

### Create a Complaint

```
POST /api/v1/external/complaints/
```

**Request Body:**

| Field | Required | Type | Description |
|---|---|---|---|
| `hospital` | Yes | string | Hospital name (case-insensitive) |
| `title` | Yes | string | Complaint title (max 500 chars) |
| `description` | Yes | string | Detailed complaint description |
| `contact_name` | Yes | string | Reporter's name (max 200 chars) |
| `contact_phone` | Yes | string | Reporter's phone (max 20 chars) |
| `contact_email` | No | string | Reporter's email |
| `relation_to_patient` | No | string | One of: `patient`, `relative`, `friend`, `other` |
| `patient_name` | No | string | Patient name |
| `national_id` | No | string | National ID number |
| `incident_date` | No | date | Date of incident (`YYYY-MM-DD`) |
| `expected_result` | No | string | Expected resolution |
| `location_type` | No | string | One of: `OP`, `IP`, `ER`, `GENERAL` |
| `area` | No | string | Area name (resolved by hospital) |
| `department` | No | string | Department name (resolved by hospital) |
| `section` | No | string | Section name (requires department) |

**Example:**

```json
{
  "hospital": "Al Nuzha",
  "title": "Long wait time in ER",
  "description": "I waited 3 hours in the emergency room without being seen.",
  "contact_name": "John Doe",
  "contact_phone": "+966501234567",
  "contact_email": "john@example.com",
  "location_type": "ER",
  "area": "Emergency",
  "department": "Critical Care Department",
  "section": "ICU"
}
```

**Response (201):**

```json
{
  "success": true,
  "reference_number": "CMP-20260609-167152",
  "status": "open"
}
```

### List Complaints

```
GET /api/v1/external/complaints/list/
```

| Parameter | Required | Description |
|---|---|---|
| `page` | No | Page number (default: 1) |
| `page_size` | No | Items per page (default: 20, max: 100) |
| `created_from` | No | Filter by created date from (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`) |
| `created_to` | No | Filter by created date to (date-only values auto-extend to end of day) |
| `updated_from` | No | Filter by updated date from |
| `updated_to` | No | Filter by updated date to |
| `status` | No | Filter by status value |

### Retrieve a Complaint

```
GET /api/v1/external/complaints/<reference_number>/
```

**Response:**

```json
{
  "id": "uuid",
  "reference_number": "CMP-20260609-167152",
  "title": "Long wait time in ER",
  "description": "I waited 3 hours...",
  "status": "open",
  "severity": "medium",
  "priority": "medium",
  "contact_name": "John Doe",
  "contact_phone": "+966501234567",
  "contact_email": "john@example.com",
  "relation_to_patient": "",
  "patient_name": "",
  "incident_date": null,
  "expected_result": "",
  "resolution": "",
  "satisfaction": "",
  "hospital_name": "Al Nuzha",
  "created_at": "2026-06-09T13:38:03.808618+03:00",
  "updated_at": "2026-06-09T13:38:03.808683+03:00"
}
```

#### Resolution & Satisfaction Fields

| Field | Description |
|---|---|
| `resolution` | Free-text description of the resolution taken (empty if unresolved) |
| `satisfaction` | Patient satisfaction: `satisfied`, `neutral`, `dissatisfied`, `no_response`, or empty |

### Set Patient Satisfaction

Sets the patient satisfaction level for a complaint. **A resolution must be recorded first** — returns `400` if the complaint has no resolution.

```
PATCH /api/v1/external/complaints/<reference_number>/satisfaction/
```

**Request Body:**

| Field | Required | Type | Description |
|---|---|---|---|
| `satisfaction` | Yes | string | One of: `satisfied`, `neutral`, `dissatisfied`, `no_response` |

**Example:**

```json
{
  "satisfaction": "satisfied"
}
```

**Response (200):**

```json
{
  "success": true,
  "reference_number": "CMP-20260609-167152",
  "satisfaction": "satisfied"
}
```

---

## Inquiries

### Create an Inquiry

```
POST /api/v1/external/inquiries/
```

**Request Body:**

| Field | Required | Type | Description |
|---|---|---|---|
| `hospital` | Yes | string | Hospital name |
| `subject` | Yes | string | Inquiry subject (max 500 chars) |
| `message` | Yes | string | Inquiry message |
| `contact_name` | Yes | string | Contact name |
| `contact_phone` | Yes | string | Contact phone |
| `contact_email` | No | string | Contact email |
| `category` | No | string | One of: `appointment`, `billing`, `medical_records`, `general`, `other` |
| `location_type` | No | string | One of: `OP`, `IP`, `ER`, `GENERAL` |
| `area` | No | string | Area name |
| `department` | No | string | Department name |
| `section` | No | string | Section name (requires department) |

**Response (201):**

```json
{
  "success": true,
  "reference_number": "INQ-20260609-124293",
  "status": "open"
}
```

### List / Retrieve Inquiries

```
GET /api/v1/external/inquiries/list/
GET /api/v1/external/inquiries/<reference_number>/
```

Same pagination and date/status filters as complaints.

**Retrieve Response:**

```json
{
  "id": "uuid",
  "reference_number": "INQ-20260609-124293",
  "subject": "Appointment rescheduling",
  "message": "I need to reschedule my cardiology appointment.",
  "category": "appointment",
  "status": "open",
  "contact_name": "Jane Smith",
  "contact_phone": "+966509876543",
  "contact_email": "jane@example.com",
  "hospital_name": "Al Nuzha",
  "created_at": "2026-06-09T13:36:22.730962+03:00",
  "updated_at": "2026-06-09T13:36:22.731020+03:00"
}
```

---

## Observations

### Create an Observation

```
POST /api/v1/external/observations/
```

**Request Body:**

| Field | Required | Type | Description |
|---|---|---|---|
| `hospital` | Yes | string | Hospital name |
| `description` | Yes | string | Observation description |
| `title` | No | string | Title (max 300 chars) |
| `severity` | No | string | One of: `low`, `medium`, `high`, `critical` (default: `medium`) |
| `category` | No | UUID | Observation category UUID |
| `location_text` | No | string | Free-text location description |
| `incident_datetime` | No | datetime | When the incident occurred |
| `contact_name` | No | string | Reporter name (anonymous supported) |
| `contact_phone` | No | string | Reporter phone |
| `contact_email` | No | string | Reporter email |
| `reporter_staff_id` | No | string | Staff ID of reporter |
| `patient_file_number` | No | string | Patient file number |

**Response (201):**

```json
{
  "success": true,
  "reference_number": "OBS-O8TCIC",
  "status": "new"
}
```

### List / Retrieve Observations

```
GET /api/v1/external/observations/list/
GET /api/v1/external/observations/<tracking_code>/
```

**Retrieve Response:**

```json
{
  "id": "uuid",
  "reference_number": "OBS-O8TCIC",
  "title": "",
  "description": "Wet floor near the main entrance, no warning sign posted.",
  "severity": "medium",
  "status": "new",
  "location_text": "Main entrance lobby",
  "incident_datetime": "2026-06-09T13:36:23.542046+03:00",
  "contact_name": "Anonymous Reporter",
  "contact_phone": "",
  "contact_email": "",
  "reporter_staff_id": "",
  "patient_file_number": "",
  "hospital_name": "Al Nuzha",
  "category_name": null,
  "created_at": "2026-06-09T13:36:23.540024+03:00",
  "updated_at": "2026-06-09T13:36:23.540079+03:00"
}
```

---

## Appreciations

### Create an Appreciation

```
POST /api/v1/external/appreciations/
```

**Request Body:**

| Field | Required | Type | Description |
|---|---|---|---|
| `hospital` | Yes | string | Hospital name |
| `message` | Yes | string | Appreciation message |
| `contact_name` | Yes | string | Submitter name |
| `contact_phone` | Yes | string | Submitter phone |

**Response (201):**

```json
{
  "success": true,
  "reference_number": "APR-20260609-239270",
  "status": "draft"
}
```

### List / Retrieve Appreciations

```
GET /api/v1/external/appreciations/list/
GET /api/v1/external/appreciations/<uuid>/
```

**Retrieve Response:**

```json
{
  "id": "uuid",
  "reference_number": "APR-20260609-239270",
  "message_en": "Dr. Ahmed was incredibly kind and thorough.",
  "status": "draft",
  "hospital_name": "Al Nuzha",
  "is_anonymous": false,
  "created_at": "2026-06-09T13:36:23.788028+03:00",
  "updated_at": "2026-06-09T13:36:23.788077+03:00"
}
```

---

## Suggestions

### Create a Suggestion

```
POST /api/v1/external/suggestions/
```

**Request Body:**

| Field | Required | Type | Description |
|---|---|---|---|
| `hospital` | Yes | string | Hospital name |
| `message` | Yes | string | Suggestion message |
| `contact_name` | Yes | string | Submitter name |
| `contact_phone` | Yes | string | Submitter phone |
| `title` | No | string | Title (auto-generated from message if omitted) |
| `category` | No | string | One of: `clinical_care`, `staff_service`, `facility`, `communication`, `appointment`, `billing`, `food_service`, `cleanliness`, `technology`, `general`, `other` |
| `rating` | No | integer | Rating 1-5 |

**Response (201):**

```json
{
  "success": true,
  "reference_number": "SG-20260609-316316",
  "status": "submitted"
}
```

### List / Retrieve Suggestions

```
GET /api/v1/external/suggestions/list/
GET /api/v1/external/suggestions/<uuid>/
```

**Retrieve Response:**

```json
{
  "id": "uuid",
  "reference_number": "SG-20260609-316316",
  "title": "Digital queue system",
  "message": "Please implement a digital queue management system.",
  "category": "technology",
  "rating": null,
  "status": "submitted",
  "feedback_type": "suggestion",
  "sentiment": "neutral",
  "contact_name": "Suggestor",
  "contact_phone": "+966504445566",
  "hospital_name": "Al Nuzha",
  "created_at": "2026-06-09T13:36:24.051998+03:00",
  "updated_at": "2026-06-09T13:36:24.052047+03:00"
}
```

---

## Doctor Ratings

### Submit a Doctor Rating

Submit a patient rating for a doctor. The `doctor_id` must be an existing employee ID in the Staff table for the given hospital.

```
POST /api/v1/external/doctor-ratings/
```

**Request Body:**

| Field | Required | Type | Description |
|---|---|---|---|
| `hospital_id` | Yes | integer | Hospital ID |
| `doctor_id` | Yes | string | Doctor's employee ID (must exist in the hospital's Staff records) |
| `rating` | Yes | integer | Rating from 1 to 5 |
| `doctor_name` | No | string | Doctor name (stored for reference) |
| `feedback` | No | string | Patient feedback text |
| `rating_date` | No | date | Date of rating (`YYYY-MM-DD`, defaults to current date) |
| `patient_uhid` | No | string | Patient unique health ID |
| `patient_name` | No | string | Patient name |
| `patient_type` | No | string | One of: `IP`, `OP`, `ER`, `DC` |
| `department_name` | No | string | Department name |
| `admit_date` | No | date | Admission date (`YYYY-MM-DD`) |
| `discharge_date` | No | date | Discharge date (`YYYY-MM-DD`) |

**Example:**

```json
{
  "hospital_id": 1,
  "doctor_id": "10738",
  "doctor_name": "Dr. Omaymah Yaqoub",
  "rating": 4,
  "feedback": "Great doctor, very caring and attentive.",
  "rating_date": "2026-06-10",
  "patient_uhid": "UHID12345",
  "patient_name": "Ahmed Ali",
  "patient_type": "OP",
  "department_name": "Internal Medicine"
}
```

**Response (201):**

```json
{
  "success": true,
  "rating_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "staff_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
  "doctor_name": "OMAYMAH YAQOUB ELAMEIAN",
  "rating": 4
}
```

**Error Responses:**

| HTTP Status | Condition |
|---|---|
| `400` | `doctor_id` not found in the hospital's Staff records |
| `400` | `rating` outside 1-5 range |
| `400` | `hospital_id` not found |
| `403` | Hospital does not match API key scope |

**Error Example (doctor not found):**

```json
{
  "doctor_id": [
    "Doctor with employee ID '99999' not found at this hospital."
  ]
}
```

---

## Common Patterns

### Location Hierarchy

Locations follow a cascading hierarchy. Use lookup endpoints to populate dependent dropdowns:

```
1. GET /hospitals/           → pick hospital
2. GET /location-types/      → pick location type
3. GET /areas/?hospital=X    → pick area (filtered by hospital)
4. GET /departments/?hospital=X  → pick department
5. GET /sections/?hospital=X&department=Y  → pick section
```

### Date Filtering

All list endpoints support date range filtering:

```
GET /complaints/list/?created_from=2026-01-01&created_to=2026-06-30
GET /complaints/list/?updated_from=2026-06-01T08:00:00&updated_to=2026-06-09T17:00:00
```

- Date-only values (`YYYY-MM-DD`): `created_from` starts at 00:00, `created_to` extends to 23:59:59
- Full datetime values (`YYYY-MM-DDTHH:MM:SS`) are also accepted

### Pagination

All list endpoints return paginated results:

```json
{
  "count": 150,
  "page": 1,
  "page_size": 20,
  "results": [...]
}
```

| Parameter | Default | Max |
|---|---|---|
| `page` | 1 | - |
| `page_size` | 20 | 100 |

### Reference Number Formats

| Entity | Prefix | Format |
|---|---|---|
| Complaint | `CMP-` | `CMP-YYYYMMDD-XXXXXX` |
| Inquiry | `INQ-` | `INQ-YYYYMMDD-XXXXXX` |
| Observation | `OBS-` | `OBS-XXXXXX` (auto-generated by model) |
| Appreciation | `APR-` | `APR-YYYYMMDD-XXXXXX` |
| Suggestion | `SG-` | `SG-YYYYMMDD-XXXXXX` |
