# Patient Data API — External Specification

## Purpose

The Go mkiso server needs patient data (name, medical record ID, registration ID) to generate ISO filenames. Instead of connecting directly to the HIS/PACS database, it calls this API.

**This API must be implemented by the HIS/PACS team** (or provided as a thin proxy in front of the existing DB).

---

## Authentication

Use one of:
- `Authorization: Bearer <jwt-token>` — if the HIS exposes standard JWT
- `X-API-Key: <key>` — simpler for internal services
- Configurable in Go server's `config.yaml`:`patient_api.auth_header`, `patient_api.auth_token`

---

## Endpoint: Get Patient by Accession Number

### `GET /patient/by-accession`

**Query Parameters:**

| Parameter | Required | Type | Description |
|-----------|----------|------|-------------|
| `accession_number` | Yes | string | DICOM Accession Number (e.g., `MR.2024.001`) |

**Request:**
```
GET /patient/by-accession?accession_number=MR.2024.001
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

**Response 200 — OK:**

```json
{
  "accession_number": "MR.2024.001",
  "medrec_id": "00012345",
  "reg_id": "REG-2024-00123",
  "patient_name": "JOHN DOE",
  "modality": "MR",
  "study_description": "MRI Brain"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `accession_number` | string | Echoed back for verification |
| `medrec_id` | string | Medical Record ID |
| `reg_id` | string | Registration ID |
| `patient_name` | string | Patient full name (for ISO filename) |
| `modality` | string | DICOM Modality code (MR, CT, CR, etc.) |
| `study_description` | string | DICOM Study Description |

**Response 404 — Not Found:**

```json
{
  "error": "accession_number not found",
  "accession_number": "MR.9999.XXX"
}
```

**Response 502 — Upstream Failure:**

```json
{
  "error": "upstream database unavailable"
}
```

---

## Implementation Guide (for the HIS/PACS team)

### Data Source

The existing `mkiso_multiple.php` queries these tables directly:

```sql
-- Step 1: MEDRECID + RegID from pacs_result_series
SELECT MEDRECID, RegID 
FROM pacs_result_series 
WHERE AccessionNumber = ? 
LIMIT 1;

-- Step 2: Patient name from medrec
SELECT Nama 
FROM medrec 
WHERE MEDRECID = ?;
```

**The API should wrap these queries** but the Go server never touches the DB directly.

### Minimal Reference Implementation (PHP)

```php
<?php
// patient-api.php
header('Content-Type: application/json');

// Auth check (simple API key)
$valid_key = getenv('PATIENT_API_KEY');
if ($_SERVER['HTTP_X_API_KEY'] !== $valid_key) {
    http_response_code(401);
    echo json_encode(['error' => 'unauthorized']);
    exit;
}

$acc = $_GET['accession_number'] ?? '';
if (empty($acc)) {
    http_response_code(400);
    echo json_encode(['error' => 'missing accession_number']);
    exit;
}

// Connect to DB (PACS DB: 192.168.2.7, pacsdb_his)
$db = new PDO('mysql:host=192.168.2.7;dbname=pacsdb_his', 'remote', '12Digit');

$stmt = $db->prepare(
    "SELECT prs.MEDRECID, prs.RegID, m.Nama
     FROM pacs_result_series prs
     LEFT JOIN medrec m ON m.MEDRECID = prs.MEDRECID
     WHERE prs.AccessionNumber = ?
     LIMIT 1"
);
$stmt->execute([$acc]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$row) {
    http_response_code(404);
    echo json_encode(['error' => 'accession_number not found', 'accession_number' => $acc]);
    exit;
}

echo json_encode([
    'accession_number' => $acc,
    'medrec_id' => $row['MEDRECID'],
    'reg_id' => $row['RegID'],
    'patient_name' => $row['Nama'],
    'modality' => '',       // Optional, can be added from another query
    'study_description' => '' // Optional
]);
```

---

## Go Server Configuration for Patient API

```yaml
# config.yaml
patient_api:
  base_url: "http://192.168.2.7:8090"        # Patient API server
  endpoint: "/patient/by-accession"
  auth_type: "api_key"                       # "jwt" or "api_key" or "none"
  auth_header: "X-API-Key"                   # Header name for auth
  auth_token: "changeme-secret-key"          # The API key or JWT
  timeout: 10s
  retry: 3                                   # Retry on network failure
  retry_backoff: 500ms
```

---

## Go Client (internal/repo/patient.go)

```go
package repo

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "time"
)

type PatientData struct {
    AccessionNumber  string `json:"accession_number"`
    MedrecID         string `json:"medrec_id"`
    RegID            string `json:"reg_id"`
    PatientName      string `json:"patient_name"`
    Modality         string `json:"modality"`
    StudyDescription string `json:"study_description"`
}

type PatientRepo struct {
    baseURL    string
    httpClient *http.Client
    authHeader string
    authToken  string
}

func NewPatientRepo(baseURL, authHeader, authToken string, timeout time.Duration) *PatientRepo {
    return &PatientRepo{
        baseURL:    baseURL,
        httpClient: &http.Client{Timeout: timeout},
        authHeader: authHeader,
        authToken:  authToken,
    }
}

func (r *PatientRepo) ByAccessionNumber(ctx context.Context, acc string) (*PatientData, error) {
    u, _ := url.Parse(r.baseURL + "/patient/by-accession")
    u.RawQuery = url.Values{"accession_number": {acc}}.Encode()

    req, _ := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
    if r.authHeader != "" {
        req.Header.Set(r.authHeader, r.authToken)
    }

    resp, err := r.httpClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("patient API request failed: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode == 404 {
        return nil, fmt.Errorf("accession_number %q not found", acc)
    }
    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("patient API returned %d", resp.StatusCode)
    }

    var data PatientData
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
        return nil, fmt.Errorf("patient API response decode: %w", err)
    }
    return &data, nil
}
```
