5.9 KiB
5.9 KiB
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 JWTX-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:
{
"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:
{
"error": "accession_number not found",
"accession_number": "MR.9999.XXX"
}
Response 502 — Upstream Failure:
{
"error": "upstream database unavailable"
}
Implementation Guide (for the HIS/PACS team)
Data Source
The existing mkiso_multiple.php queries these tables directly:
-- 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
// 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
# 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)
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
}