# 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 ` — if the HIS exposes standard JWT - `X-API-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 '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 } ```