Skip to content
Snippets Groups Projects
Commit 2c81e1fa authored by Lukas Stotz's avatar Lukas Stotz
Browse files

Add HL7 receiver functionality and update requirements

parent 5e03afbf
No related branches found
No related tags found
No related merge requests found
import socket
import datetime
import uuid
import os
import json
from pathlib import Path
import hl7
import logging
from typing import Dict, Any
from flask import Flask, request, jsonify
from flask_cors import CORS
# import threading # Not needed when TCP server is disabled
app = Flask(__name__)
CORS(app)
class HL7MessageReceiver:
def __init__(self, host: str = 'localhost', port: int = 6661):
"""Initialize the HL7 message receiver.
Args:
host (str): Host to listen on
port (int): Port to listen on
"""
self.host = host
self.port = port
self.setup_logging()
self.setup_directories()
def setup_logging(self):
"""Set up logging configuration."""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('hl7_receiver.log'),
logging.StreamHandler()
]
)
self.logger = logging.getLogger('HL7Receiver')
def setup_directories(self):
"""Create necessary directories for message storage."""
# Create base directories
Path('received_messages').mkdir(exist_ok=True)
Path('message_logs').mkdir(exist_ok=True)
def generate_filename(self, msg_type: str, timestamp: datetime.datetime) -> str:
"""Generate a filename based on message type and timestamp.
Args:
msg_type (str): The HL7 message type
timestamp (datetime.datetime): Message receipt timestamp
Returns:
str: Generated filename
"""
date_str = timestamp.strftime('%Y%m%d_%H%M%S')
return f"received_messages/{msg_type}_{date_str}.txt"
def process_message(self, raw_message: str) -> Dict[str, Any]:
"""Process received HL7 message.
Args:
raw_message (str): Raw HL7 message string
Returns:
Dict[str, Any]: Processed message info
"""
# Parse the HL7 message
try:
# Remove leading/trailing whitespace and ensure proper line endings
raw_message = '\r'.join(line.strip() for line in raw_message.splitlines())
if not raw_message.endswith('\r'):
raw_message += '\r'
self.logger.info(f"Attempting to parse message: {raw_message}")
parsed_msg = hl7.parse(raw_message)
# Extract message type more safely
try:
# Get MSH segment
msh = parsed_msg.segments('MSH')[0]
# MSH-9 contains the message type in format "ADT^A01"
message_type_field = str(msh[9])
# Split the message type field on '^' to get type and trigger
type_parts = message_type_field.split('^')
message_type = type_parts[0] if len(type_parts) > 0 else "UNKNOWN"
trigger_event = type_parts[1] if len(type_parts) > 1 else ""
# Combine message type and trigger
msg_type = f"{message_type}_{trigger_event}" if trigger_event else message_type
self.logger.info(f"Extracted message type: {msg_type} from field: {message_type_field}")
except Exception as e:
self.logger.error(f"Error extracting message type: {str(e)}")
msg_type = "UNKNOWN"
# Generate timestamp and ID
timestamp = datetime.datetime.now()
msg_id = str(uuid.uuid4())
# Create message info dictionary
msg_info = {
'id': msg_id,
'timestamp': timestamp.isoformat(),
'type': msg_type,
'raw_message': raw_message,
'parsed_segments': [str(seg) for seg in parsed_msg]
}
# Save message to file
filename = self.generate_filename(msg_type, timestamp)
with open(filename, 'w', encoding='utf-8') as f:
f.write(f"Message ID: {msg_id}\n")
f.write(f"Timestamp: {timestamp.isoformat()}\n")
f.write(f"Message Type: {msg_type}\n")
f.write("\nRaw Message:\n")
f.write(raw_message)
f.write("\n\nParsed Segments:\n")
for segment in parsed_msg:
f.write(f"{str(segment)}\n")
# Log message receipt
self.log_message(msg_info)
self.logger.info(f"Successfully processed message {msg_id} of type {msg_type}")
return msg_info
except Exception as e:
self.logger.error(f"Error processing message: {str(e)}")
self.logger.error(f"Raw message was: {raw_message}")
raise ValueError(f"Failed to process HL7 message: {str(e)}")
def log_message(self, msg_info: Dict[str, Any]):
"""Log message information to the message log file.
Args:
msg_info (Dict[str, Any]): Message information to log
"""
log_file = f"message_logs/messages_{datetime.date.today()}.log"
with open(log_file, 'a', encoding='utf-8') as f:
json.dump(msg_info, f)
f.write('\n')
# TCP Server functionality commented out
"""
def start_tcp_server(self):
#Start the HL7 message receiver TCP server.
self.logger.info(f"Starting HL7 TCP receiver on {self.host}:{self.port}")
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((self.host, self.port))
server_socket.listen(5)
try:
while True:
client_socket, address = server_socket.accept()
self.logger.info(f"Connection from {address}")
try:
# Receive message
message = ''
while True:
data = client_socket.recv(4096).decode('utf-8')
if not data:
break
message += data
if '\r' in data: # HL7 messages end with carriage return
break
if message:
# Process the message
msg_info = self.process_message(message)
# Send acknowledgment
ack = self.generate_ack(msg_info)
client_socket.send(ack.encode('utf-8'))
except Exception as e:
self.logger.error(f"Error handling client connection: {str(e)}")
finally:
client_socket.close()
except KeyboardInterrupt:
self.logger.info("Shutting down HL7 receiver")
server_socket.close()
except Exception as e:
self.logger.error(f"Server error: {str(e)}")
server_socket.close()
raise
"""
def generate_ack(self, msg_info: Dict[str, Any]) -> str:
"""Generate HL7 acknowledgment message.
Args:
msg_info (Dict[str, Any]): Information about the received message
Returns:
str: HL7 acknowledgment message
"""
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
return (
f"MSH|^~\\&|RECEIVER|FACILITY|SENDER|FACILITY|{timestamp}||ACK|{msg_info['id']}|P|2.5.1\r"
f"MSA|AA|{msg_info['id']}|Message received successfully\r"
)
# Create a global instance of the receiver
receiver = HL7MessageReceiver()
@app.route('/api/hl7/receive', methods=['POST'])
def receive_hl7():
"""HTTP endpoint to receive HL7 messages."""
try:
# Get the raw message from the request
raw_message = request.data.decode('utf-8')
if not raw_message:
raw_message = request.form.get('message', '')
if not raw_message:
return jsonify({
'error': 'No HL7 message provided'
}), 400
# Process the message
try:
msg_info = receiver.process_message(raw_message)
# Generate acknowledgment
ack = receiver.generate_ack(msg_info)
return jsonify({
'status': 'success',
'message': 'HL7 message received and processed successfully',
'message_info': msg_info,
'acknowledgment': ack
})
except ValueError as ve:
# Handle validation errors with 400 status
return jsonify({
'error': str(ve),
'status': 'error',
'message': 'Invalid HL7 message format'
}), 400
except Exception as e:
# Handle unexpected errors with 500 status
app.logger.error(f"Unexpected error processing request: {str(e)}")
return jsonify({
'error': str(e),
'status': 'error',
'message': 'Internal server error processing HL7 message'
}), 500
@app.route('/api/hl7/messages', methods=['GET'])
def list_messages():
"""List all received HL7 messages."""
try:
messages = []
received_dir = Path('received_messages')
if received_dir.exists():
for file in received_dir.glob('*.txt'):
with open(file, 'r', encoding='utf-8') as f:
messages.append({
'filename': file.name,
'content': f.read()
})
return jsonify({
'messages': messages
})
except Exception as e:
return jsonify({
'error': str(e)
}), 500
if __name__ == '__main__':
# Start Flask server
app.run(host='0.0.0.0', port=5000)
\ No newline at end of file
Message ID: 1b5e9da7-b3b6-418d-83ef-263b8d19f14d
Timestamp: 2025-04-12T12:40:05.947515
Message Type: ADT_A01
Raw Message:
MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20240214150000||ADT^A01|MSG00001|P|2.5.1 EVN|A01|20240214150000 PID|||12345||DOE^JOHN^^^^||19800101|M
Parsed Segments:
MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20240214150000||ADT^A01|MSG00001|P|2.5.1
EVN|A01|20240214150000
PID|||12345||DOE^JOHN^^^^||19800101|M
Message ID: 0ab13de4-616f-494e-b56b-2589bfab683d
Timestamp: 2025-04-12T12:30:02.323344
Message Type: UNKNOWN
Raw Message:
MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20240214150000||ADT^A01|MSG00001|P|2.5.1 EVN|A01|20240214150000 PID|||12345||DOE^JOHN^^^^||19800101|M
Parsed Segments:
MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20240214150000||ADT^A01|MSG00001|P|2.5.1
EVN|A01|20240214150000
PID|||12345||DOE^JOHN^^^^||19800101|M
flask==3.0.0
Flask==2.0.1
flask-cors==4.0.0
lxml==4.9.3
watchdog==3.0.0
\ No newline at end of file
watchdog==3.0.0
hl7==0.4.2
Werkzeug==2.0.1
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment