Task 545: .PHR File Format

Task 545: .PHR File Format

File Format Specifications for the .PHR File Format

The .PHR file format is defined in the HL7 FHIR Implementation Guide for Personal Health Records (PHR). It is a text-based format for storing patient health data using FHIR resources. The format uses new-line delimited JSON (NDJSON) for larger files or a single JSON Bundle for smaller files (<16MB). The file contains a heterogeneous mix of FHIR resources, with at least one Patient resource required. It supports optional components for metadata, provenance, signatures, and supporting media references. The format emphasizes security with optional encryption, compression, and digital signatures. Files may be bundled in a .sphr container (a zipped folder) for additional materials.

  1. List of all the properties of this file format intrinsic to its file system:
  • Patient Resource (required: contains patient demographics, identifiers, and details)
  • Composition Resource (optional: acts as a cover page, recording ownership, versioning, and parsing data)
  • DocumentManifest Resource (optional: serves as a manifest and table of contents for critical documents)
  • Provenance Resources (optional: provides attestation and history of data sources)
  • FHIR Signatures (optional: detached signatures on resources for authenticity)
  • International Patient Summary (optional: standardized summary of patient data)
  • Problem-Oriented Health Record Components (optional: structured problem lists and related data)
  • DocumentReference Resources (optional: pointers to supporting media files like images or PDFs)
  • Bundle Structure (for small files <16MB: wraps resources in a single JSON Bundle)
  • NDJSON Lines (for large files >16MB: each line is a separate FHIR resource in JSON)
  • JWS Signature (optional: separate file for verifying authenticity and integrity using SHA-256 hash)
  • Encryption (optional: using ES256, A256GCM, or PGP for data protection)
  • Compression (optional: using DEFLATE algorithm for zipped .sphr containers)
  1. Two direct download links for files of format .PHR:
  1. Ghost blog embedded HTML JavaScript for drag and drop .PHR file dump:
PHR File Dumper
Drag and drop .PHR file here
  1. Python class for .PHR file:
import json
import os

class PHRFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.resources = []

    def read(self):
        with open(self.filepath, 'r') as f:
            for line in f:
                line = line.strip()
                if line:
                    try:
                        resource = json.loads(line)
                        self.resources.append(resource)
                    except json.JSONDecodeError as e:
                        print(f"Error decoding line: {e}")

    def decode(self):
        # Decoding is parsing JSON, already done in read
        pass

    def print_properties(self):
        for idx, resource in enumerate(self.resources, 1):
            print(f"Resource {idx}:")
            print(f"  Type: {resource.get('resourceType', 'N/A')}")
            print(f"  ID: {resource.get('id', 'N/A')}")
            print(f"  Patient Name: {resource.get('name', [{}])[0].get('text', 'N/A')}")
            print(f"  Version: {resource.get('meta', {}).get('versionId', 'N/A')}")
            print(f"  Provenance: {'Present' if 'contained' in resource else 'Absent'}")
            print(f"  Signature: {'Present' if 'signature' in resource else 'Absent'}")
            print(f"  Full Resource: {json.dumps(resource, indent=2)}\n")

    def write(self, new_resources, new_filepath=None):
        filepath = new_filepath or self.filepath
        with open(filepath, 'w') as f:
            for res in new_resources:
                f.write(json.dumps(res) + '\n')

# Example usage:
# phr = PHRFile('example.phr')
# phr.read()
# phr.print_properties()
# phr.write([{'resourceType': 'Patient', 'id': 'new'}], 'new.phr')
  1. Java class for .PHR file:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class PHRFile {
    private String filepath;
    private List<Map<String, Object>> resources = new ArrayList<>();

    public PHRFile(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
            String line;
            ObjectMapper mapper = new ObjectMapper();
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (!line.isEmpty()) {
                    try {
                        Map<String, Object> resource = mapper.readValue(line, Map.class);
                        resources.add(resource);
                    } catch (IOException e) {
                        System.out.println("Error decoding line: " + e.getMessage());
                    }
                }
            }
        }
    }

    public void decode() {
        // Decoding is parsing JSON, done in read
    }

    public void printProperties() {
        for (int idx = 0; idx < resources.size(); idx++) {
            Map<String, Object> resource = resources.get(idx);
            System.out.println("Resource " + (idx + 1) + ":");
            System.out.println("  Type: " + resource.getOrDefault("resourceType", "N/A"));
            System.out.println("  ID: " + resource.getOrDefault("id", "N/A"));
            Map<String, Object> name = (Map<String, Object>) ((List<?>) resource.getOrDefault("name", new ArrayList<>())).stream().findFirst().orElse(Map.of());
            System.out.println("  Patient Name: " + name.getOrDefault("text", "N/A"));
            Map<String, Object> meta = (Map<String, Object>) resource.getOrDefault("meta", Map.of());
            System.out.println("  Version: " + meta.getOrDefault("versionId", "N/A"));
            System.out.println("  Provenance: " + (resource.containsKey("contained") ? "Present" : "Absent"));
            System.out.println("  Signature: " + (resource.containsKey("signature") ? "Present" : "Absent"));
            System.out.println("  Full Resource: " + resource + "\n");
        }
    }

    public void write(List<Map<String, Object>> newResources, String newFilepath) throws IOException {
        String path = (newFilepath != null) ? newFilepath : filepath;
        try (FileWriter fw = new FileWriter(path)) {
            ObjectMapper mapper = new ObjectMapper();
            for (Map<String, Object> res : newResources) {
                fw.write(mapper.writeValueAsString(res) + "\n");
            }
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     PHRFile phr = new PHRFile("example.phr");
    //     phr.read();
    //     phr.printProperties();
    //     List<Map<String, Object>> newRes = new ArrayList<>();
    //     newRes.add(Map.of("resourceType", "Patient", "id", "new"));
    //     phr.write(newRes, "new.phr");
    // }
}
  1. JavaScript class for .PHR file:
class PHRFile {
  constructor(filepath) {
    this.filepath = filepath;
    this.resources = [];
  }

  async read() {
    // Assuming Node.js with fs
    const fs = require('fs');
    const content = fs.readFileSync(this.filepath, 'utf8');
    const lines = content.split('\n').filter(line => line.trim());
    lines.forEach(line => {
      try {
        const resource = JSON.parse(line);
        this.resources.push(resource);
      } catch (err) {
        console.log(`Error decoding line: ${err.message}`);
      }
    });
  }

  decode() {
    // Decoding is parsing JSON, done in read
  }

  printProperties() {
    this.resources.forEach((resource, idx) => {
      console.log(`Resource ${idx + 1}:`);
      console.log(`  Type: ${resource.resourceType || 'N/A'}`);
      console.log(`  ID: ${resource.id || 'N/A'}`);
      const name = resource.name ? resource.name[0].text : 'N/A';
      console.log(`  Patient Name: ${name}`);
      const version = resource.meta ? resource.meta.versionId : 'N/A';
      console.log(`  Version: ${version}`);
      console.log(`  Provenance: ${resource.contained ? 'Present' : 'Absent'}`);
      console.log(`  Signature: ${resource.signature ? 'Present' : 'Absent'}`);
      console.log(`  Full Resource: ${JSON.stringify(resource, null, 2)}\n`);
    });
  }

  write(newResources, newFilepath = this.filepath) {
    const fs = require('fs');
    const content = newResources.map(res => JSON.stringify(res)).join('\n');
    fs.writeFileSync(newFilepath, content + '\n');
  }
}

// Example usage:
// const phr = new PHRFile('example.phr');
// await phr.read();
// phr.printProperties();
// phr.write([{ resourceType: 'Patient', id: 'new' }], 'new.phr');
  1. C class for .PHR file (using C++ for class support and JSON parsing with nlohmann/json library assumption):
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <nlohmann/json.hpp> // Assume nlohmann/json library for JSON parsing

using json = nlohmann::json;

class PHRFile {
private:
    std::string filepath;
    std::vector<json> resources;

public:
    PHRFile(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath);
        std::string line;
        while (std::getline(file, line)) {
            if (!line.empty()) {
                try {
                    json resource = json::parse(line);
                    resources.push_back(resource);
                } catch (const json::exception& e) {
                    std::cout << "Error decoding line: " << e.what() << std::endl;
                }
            }
        }
    }

    void decode() {
        // Decoding is parsing JSON, done in read
    }

    void printProperties() {
        for (size_t idx = 0; idx < resources.size(); ++idx) {
            const json& resource = resources[idx];
            std::cout << "Resource " << (idx + 1) << ":" << std::endl;
            std::cout << "  Type: " << (resource.contains("resourceType") ? resource["resourceType"].get<std::string>() : "N/A") << std::endl;
            std::cout << "  ID: " << (resource.contains("id") ? resource["id"].get<std::string>() : "N/A") << std::endl;
            std::string name = "N/A";
            if (resource.contains("name") && !resource["name"].empty() && resource["name"].is_array() && resource["name"][0].contains("text")) {
                name = resource["name"][0]["text"].get<std::string>();
            }
            std::cout << "  Patient Name: " << name << std::endl;
            std::string version = "N/A";
            if (resource.contains("meta") && resource["meta"].contains("versionId")) {
                version = resource["meta"]["versionId"].get<std::string>();
            }
            std::cout << "  Version: " << version << std::endl;
            std::cout << "  Provenance: " << (resource.contains("contained") ? "Present" : "Absent") << std::endl;
            std::cout << "  Signature: " << (resource.contains("signature") ? "Present" : "Absent") << std::endl;
            std::cout << "  Full Resource: " << resource.dump(2) << std::endl << std::endl;
        }
    }

    void write(const std::vector<json>& newResources, const std::string& newFilepath = "") {
        std::string path = newFilepath.empty() ? filepath : newFilepath;
        std::ofstream file(path);
        for (const auto& res : newResources) {
            file << res.dump() << std::endl;
        }
    }
};

// Example usage:
// int main() {
//     PHRFile phr("example.phr");
//     phr.read();
//     phr.printProperties();
//     std::vector<json> newRes = {{{"resourceType", "Patient"}, {"id", "new"}}};
//     phr.write(newRes, "new.phr");
//     return 0;
// }