[FastAPI] Azure Service Bus와 Function App을 활용한 데이터 전송 (1)

2025. 12. 18. 19:43·FastAPI

아키텍쳐 흐름

 

응급환자 음성 분석 및 실시간 병원 공유 시스템 아키텍처

이 시스템은 구급대원의 음성 기록을 AI를 통해 구조화된 데이터(JSON)로 자동 변환하고, 이를 데이터베이스에 저장함과 동시에 실시간으로 관련 병원 프론트엔드에 전파(Broadcast)하는 전체 흐름을 다룹니다.

전체적인 데이터 흐름은 크게 1) 음성 데이터 분석 및 변환 단계와 2) 데이터 적재 및 실시간 전파 단계로 나누어집니다.

1. 음성 데이터 분석 및 변환 (Voice-to-Data Flow)

구급대원이 현장에서 환자 상태를 음성으로 기록하고 이를 구조화된 데이터로 만드는 과정입니다.

  1. 음성 녹음 및 전송: 구급대원이 사용하는 React Frontend 앱에서 환자의 상태를 음성으로 녹음합니다. 녹음된 오디오 파일(Blob)은 FastAPI 서버로 전송(POST)됩니다.
  2. STT 변환 (Speech-to-Text): FastAPI 서버는 받은 오디오 파일을 Azure OpenAI의 Whisper 모델로 전송하여 텍스트 데이터로 변환(Transcribe) 받아옵니다.
  3. AI 분석 및 구조화 (LLM Analysis): 변환된 텍스트 데이터는 다시 Azure OpenAI의 GPT-4o 모델로 전송됩니다. GPT 모델은 프롬프트에 따라 환자의 핵심 정보를 추출하여 사전에 정의된 표준 JSON 포맷으로 구조화된 데이터를 반환합니다.

2. 데이터 적재 및 실시간 전파 (Persistence & Real-time Broadcast Flow)

완성된 JSON 데이터를 영구 저장하고, 동시에 이벤트 기반으로 병원에 실시간 공유하는 핵심 프로세스입니다. 

A. 데이터 적재 (Persistence)

  • Oracle DB 저장: 완성된 환자 정보 JSON 데이터는 FastAPI 서버에 의해 Oracle Database의 PATIENT_LOGS 테이블에 영구적으로 저장(INSERT)되어 기록을 남깁니다.

B. 실시간 이벤트 브로드캐스팅 (Real-time Broadcast Flow)

실시간성이 중요한 환자 데이터를 병원 측에 즉각적으로 전달하기 위해 메시지 브로커와 웹소켓을 활용하는 단계입니다.

  1. 이벤트 발행 (Publisher - FastAPI):
    • FastAPI 서버는 환자 데이터 분석이 완료되면, 이를 하나의 '이벤트'로 간주하고 해당 JSON 데이터를 Azure Service Bus Topic으로 발행(Publish)합니다.
    • 이때, 환자별 데이터의 순서를 보장하기 위해 환자 ID를 Session ID로 설정하여 메시지를 전송합니다.
  2. 메시지 수신 및 트리거 (Subscriber - Azure Function):
    • Azure Service Bus는 전달받은 메시지를 Topic에 보관합니다.
    • 이 Topic을 구독(Subscription)하고 있던 Azure Function은 새로운 메시지가 도착하는 즉시 트리거(Trigger)되어 실행됩니다. 
  3. 브로드캐스트 요청 (Function to FastAPI):
    • Azure Function은 웹소켓 연결을 직접 관리하지 않고, 데이터를 전파하기 위해 다시 FastAPI 서버의 특정 브로드캐스트 엔드포인트(POST api/v1/emc/broadcast)로 데이터를 전송합니다.
  4. 웹소켓 브로드캐스팅 (FastAPI to Frontend):
    • 브로드캐스트 요청을 받은 FastAPI 서버는 현재 웹소켓으로 연결되어 있는 모든 병원 프론트엔드(WebSocket Client) 들에게 해당 환자 정보 JSON 데이터를 동시에 뿌려줍니다(Broadcast).
    • 각 병원의 상황판(UI)은 이 데이터를 실시간으로 수신하여 화면을 즉시 업데이트합니다.

 

1번 음성 데이터 분석 및 변환과 2번에서 데이터 적재는 이전 포스팅에서 다뤘기 때문에 이번 포스팅에서는 Azure Service Bus와 Azure Function App을 활용한 데이터 실시간 전파를 구현해보겠습니다. 


Azure Service Bus

 

Azure Service Bus는 서로 다른 어플리케이션이나 서비스들이 데이터를 주고받을 때, 직접 연결하지 않고 '중간 기착지(Message Broker)'를 거쳐서 소통하게 해주는 클라우드 메시징 서비스입니다.

 

Service Bus를 이해하려면 가장 먼저 메시지 전달 방식 두 가지를 알아야 합니다.

 

1. Queue (1:1 방식)

가장 단순한 형태의 보관함입니다.

  • 작동: 발송자(Sender)가 메시지를 넣으면, 수신자(Receiver) 중 단 한 명만 그 메시지를 가져갈 수 있습니다.
  • 특징: 메시지를 처리하고 나면 보관함에서 사라집니다. "누가 이 일을 할래?"라고 물었을 때 선착순 한 명이 가져가는 구조입니다.

2. Topic & Subscription (1:N 방식)

이번에 설계할 'Publish - Subscribe' 모델입니다.

  • Topic: 발송자가 메시지를 던지는 '게시판'입니다.
  • Subscription: 게시판을 지켜보는 '구독자'들입니다.
  • 특징: 하나의 메시지가 Topic에 들어오면, 그 Topic을 구독하고 있는 모든 Subscription에 복사본이 전달됩니다. (마치 신문사가 기사 하나를 쓰면 모든 구독자에게 배달되는 것과 같습니다.)

 

 

단순히 A 서버 -> B 서버로 HTTP 통신을 하면 편할 텐데, 왜 굳이 중간에 Service Bus를 넣을까요?

 

① 비동기 처리 및 디커플링 (Decoupling)

  • 상황: 환자 데이터가 발생했을 때, FastAPI 서버가 잠시 점검 중이거나 네트워크 오류가 났다고 가정해 봅시다.
  • 효과: 직접 연결되어 있다면 데이터는 유실됩니다. 하지만 Service Bus가 있으면 메시지를 안전하게 보관하고 있다가, FastAPI(또는 Azure Function)가 다시 살아났을 때 처리할 수 있게 해줍니다. 즉, 두 시스템이 서로의 상태에 의존하지 않게 됩니다.

② 부하 분산 (Load Leveling)

  • 상황: 대형 사고가 발생해 갑자기 수천 명의 환자 데이터가 한꺼번에 몰려옵니다.
  • 효과: 서버가 감당할 수 없는 데이터가 들어와도 서버가 터지지 않습니다. Service Bus가 거대한 '댐' 역할을 하여 메시지를 쌓아두고, 서버는 자신이 처리할 수 있는 속도로 차근차근 메시지를 가져갑니다.

③ 순서 보장 (Sessions)

  • 문제: 환자 A의 상태가 위급 -> 안정으로 변했는데, 네트워크 문제로 안정 메시지가 먼저 도착하고 위급 메시지가 나중에 도착하면 큰 혼선이 생깁니다.
  • 해결: Session ID(환자 ID)를 지정하면, Service Bus는 해당 환자에 대한 메시지를 반드시 들어온 순서대로 한 놈씩 줄 세워 처리하도록 보장합니다.

Azure Service Bus에서 사용하는 주요 용어들입니다.

 

1. Namespace (네임스페이스)

Service Bus를 사용하기 위한 가장 큰 컨테이너입니다. Queue나 Topic을 만들기 위해서는 이 공간이 먼저 있어야 합니다.

 

2. Queue (큐)

보내는 사람(Sender)이 메시지를 넣으면, 받는 사람(Receiver) 중 단 한 명만 그 메시지를 가져갈 수 있습니다.

 

3. Topic (토픽)

메시지를 여러 곳에 동시에 뿌리고 싶을 때 사용하는 보내는 곳입니다.

 

4. Subscription (구독)

Topic에서 쏜 메시지를 실제로 받는 곳입니다. Topic 하나에 여러 개의 Subscription을 달 수 있습니다.


Azure Service Bus 생성

 

직접 Azure Service Bus룰 생성하겠습니다. Azure 포탈에 접속해 리소스 그룹을 생성한 뒤 Marketplace에서 Service Bus를 Create 합니다. 

 

 

 

이렇게 만들어진 Service Bus Namespace 에서 az-patient-event-hub 이름으로 Topic을 만들어줍니다. 

 

 

Topic을 만들었다면 그 안에 sub-azfunc-emergency-relay 이름으로 subscription도 만들어줍니다.

 

 


VScode에서 Azure Function App 생성

 

Azure Function App은 인프라(서버)를 직접 구축하거나 관리할 필요 없이, "이벤트가 발생했을 때만 코드를 실행"**해주는 서버리스(Serverless) 컴퓨팅 서비스입니다.

 

 

 

일반적인 서버(Web Server)는 사용자가 오든 안 오든 24시간 켜져 있어야 합니다. 하지만 Function App은 다릅니다.

  • Trigger (트리거): 함수를 깨우는 '알람'입니다. "Service Bus에 새 글이 올라오면 실행해!", "특정 시간이 되면 실행해!"처럼 실행 조건을 정합니다.
  • Action (실행): 트리거가 발동되면 미리 작성해 둔 코드(Python, Node.js 등)가 실행됩니다.
  • Binding (바인딩): 데이터의 입출력을 담당합니다. 복잡한 연결 코드 없이 "결과를 바로 DB에 넣어" 혹은 "결과를 다른 API로 쏴줘"라고 설정만 하면 됩니다.

 

Azure 포털에 접속해 Function App을 Create 합니다. 

 

Function App을 만들었다면 작업하고 있는 VScode로 돌아옵니다. 

Extension에서 Azure와 관련된 소스들을 다운로드하고 좌측에 보이는 Azure Icon을 클릭합니다.

클릭하면 나타나는 WORKSPACE 항목에 Create Function Project를 클릭합니다. 

 

 

화면 상단 중앙에 뜨는 메뉴에서 순서대로 선택합니다. 

  • Folder: siren-azfunc-relay (현재 폴더) 선택
  • Language: Python 선택
  • Interpreter: 컴퓨터에 설치된 파이썬 3.11.9
  • Template: Service Bus Topic Trigger 선택
  • Name: relay_worker (함수 이름) 입력
  • Topic: az-patient-event-hub 입력
  • Subscription: sub-azfunc-emergency-relay 입력

 

해당 작업이 마치면 siren-azfunc-relay 안에 requirements.txt / local.settings.json / function_app.py 파일이 생성됩니다. 

파일명 비유 핵심 역할
function_app.py 작업 지시서 실제 작동하는 파이썬 코드가 있는 곳
local.settings.json 비밀 금고 내 컴퓨터에서만 쓰는 암호/연결 문자열 (Git 업로드 금지 🚫)
requirements.txt 장비 목록 필요한 파이썬 라이브러리 리스트
host.json 사규(Rule) 로그 레벨 등 앱 전체의 공통 설정
.funcignore 검문소 배포할 때 빼고 보낼 파일 목록
 
먼저 requirements.txt 파일을 수정합니다. 함수가 FastAPI로 데이터를 쏘려면 requests 라이브러리가 필요합니다.
azure-functions
requests

 

다음으로 로컬에서 테스트할 때 필요한 정보들을 적어주는 곳입니다. 

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "SERVICEBUS_CONNECTION": "<Azure Portal에서 복사한 Service Bus 연결 문자열>",
    "FASTAPI_BROADCAST_URL": "http://localhost:8000/api/v1/emc/broadcast"
  }
}

 

아래의 창에서 연결 문자열을 확인할 수 있습니다. 

 

마지막으로 function.py 파일을 작성합니다.

import azure.functions as func
import logging
import requests
import os
import json

app = func.FunctionApp()

# FastAPI 브로드캐스트 주소 (local.settings.json에서 읽어옴)
FASTAPI_URL = os.getenv("FASTAPI_BROADCAST_URL")

@app.service_bus_topic_trigger(
    arg_name="msg",
    topic_name="az-patient-event-hub",
    subscription_name="sub-azfunc-emergency-relay",
    connection="SERVICEBUS_CONNECTION",
    is_sessions_enabled=True  # 환자별 순서 보장을 위해 필수!
)

def relay_worker(msg: func.ServiceBusMessage):
    try:
        # 1. 메시지 본문 가져오기
        body_str = msg.get_body().decode("utf-8").strip() # 공백 제거
        
        # [로그 추가] 실제로 어떤 데이터가 들어오는지 눈으로 확인합니다.
        logging.info(f"📥 [수신 데이터 원본]: '{body_str}'")

        if not body_str:
            logging.warning("⚠️ 메시지 본문이 비어 있습니다. 스킵합니다.")
            return

        # 2. JSON 파싱 시도
        try:
            event_data = json.loads(body_str)
        except json.JSONDecodeError:
            logging.error(f"❌ JSON 형식이 아닙니다: {body_str}")
            # JSON이 아니더라도 FastAPI로 보낼지, 버릴지 결정해야 합니다.
            # 여기서는 일단 텍스트 그대로 body에 담아 보내도록 처리해 보겠습니다.
            event_data = {"raw_text": body_str}

        # 2. 메타데이터(Application Properties) 추출
        # bytes 키를 처리하기 위한 유연한 로직
        props = msg.application_properties or {}
        def decode_meta(key):
            val = props.get(key.encode()) or props.get(key)
            return val.decode() if isinstance(val, bytes) else val

        # 3. FastAPI로 전달할 통합 페이로드 조립
        payload = {
            "patient_id": msg.session_id,      # 환자 식별자
            "message_id": msg.message_id,
            "region": decode_meta("Region") or "Unknown",
            "state": decode_meta("PatientState") or "STATUS_UPDATE",
            "body": event_data                 # 동료가 보낸 실제 데이터
        }

        logging.info(f"🚚 [AzFunc] 중계 시작: 환자 {payload['patient_id']}")

        # 4. FastAPI 서버로 전송
        response = requests.post(
            FASTAPI_URL,
            json=payload,
            timeout=5
        )

        # 5. 전송 결과 확인 및 에러 핸들링
        if response.status_code >= 400:
            logging.error(f"❌ FastAPI 오류 ({response.status_code}): {response.text}")
            raise Exception("FastAPI 전송 실패로 인한 재시도")

        logging.info(f"✅ 환자 {payload['patient_id']} 이벤트 방송 성공!")

    except Exception as e:
        logging.error(f"🔥 처리 중 치명적 오류: {str(e)}")
        # 여기서 raise를 해야 메시지가 버려지지 않고 Service Bus가 재시도합니다.
        raise

 


코드 분석

위의 코드는 Azure Service Bus(Topic)에서 메시지를 받아, 분석 및 가공한 뒤, FastAPI 서버로 전달(Relay)하는 '중계기' 역할을 하는 Azure Function입니다.

 

아래의 코드는 Azure Fuction과 기타 다른 핵심 기능을 사용하기 위한 필수 라이브러리들입니다. 

import azure.functions as func
import logging
import requests
import os
import json

 

Function App의 본체를 만드는 코드입니다. 이 app 객체에 트리거를 등록하게 됩니다.

app = func.FunctionApp()

 

Service Bus로 부터 받아온 JSON 파일을 보내줄 브로드캐스트 주소입니다. 

# FastAPI 브로드캐스트 주소 (local.settings.json에서 읽어옴)
FASTAPI_URL = os.getenv("FASTAPI_BROADCAST_URL")

 

 

아래의 코드는 Service Bus Trigeer를 불러오는 설정입니다. 

 

@app.service_bus_topic_trigger: "이 함수는 Service Bus Topic에 메시지가 들어오면 실행된다"라고 선언하는 데코레이터입니다.

arg_name="msg": 들어온 메시지를 아래 함수(relay_worker)에서 msg라는 변수명으로 쓰겠다고 정합니다.

topic_name / subscription_name: 어느 게시판(Topic)의 어느 구독자(Subscription)함인지 지정합니다.

connection: local.settings.json에 저장된 Service Bus 연결 문자열의 키(Key) 이름입니다.

is_sessions_enabled=True: 환자별 순서 보장을 위해 세션 기능을 켭니다. 이게 없으면 메시지가 뒤죽박죽 처리될 수 있습니다.

 

@app.service_bus_topic_trigger(
    arg_name="msg",
    topic_name="az-patient-event-hub",
    subscription_name="sub-azfunc-emergency-relay",
    connection="SERVICEBUS_CONNECTION",
    is_sessions_enabled=True  # 환자별 순서 보장을 위해 필수!
)

 

 

def relay_worker(...): 실제 로직이 시작되는 함수입니다.

msg.get_body().decode("utf-8"): Service Bus 메시지는 기본적으로 bytes(이진 데이터)로 옵니다. 이를 사람이 읽을 수 있는 문자열(UTF-8)로 변환합니다.

.strip(): 앞뒤 불필요한 공백을 제거합니다.

if not body_str:: 빈 메시지가 오면 에러를 내지 않고 조용히 함수를 종료합니다. (방어적 코딩)

 

def relay_worker(msg: func.ServiceBusMessage):
    try:
        # 1. 메시지 본문 가져오기
        body_str = msg.get_body().decode("utf-8").strip() # 공백 제거
        
        # [로그 추가] 실제로 어떤 데이터가 들어오는지 눈으로 확인합니다.
        logging.info(f"📥 [수신 데이터 원본]: '{body_str}'")

        if not body_str:
            logging.warning("⚠️ 메시지 본문이 비어 있습니다. 스킵합니다.")
            return

 

json.loads(body_str): 문자열을 파이썬 딕셔너리(Dict)로 변환합니다.

# 2. JSON 파싱 시도
        try:
            event_data = json.loads(body_str)
        except json.JSONDecodeError:
            logging.error(f"❌ JSON 형식이 아닙니다: {body_str}")
            event_data = {"raw_text": body_str}

 

 

msg.application_properties: 메시지 본문(Body) 외에 꼬리표처럼 붙어있는 사용자 정의 헤더들입니다. (예: Region, PatientState 등)

decode_meta 함수

  • 가끔 Service Bus 라이브러리 버전에 따라 키나 값이 bytes로 오기도 하고 str로 오기도 합니다.
  • 이 함수는 타입이 무엇이든 안전하게 문자열로 바꿔주는 헬퍼(도우미) 함수입니다.

해당 객체와 속성들은 모두 Azure Functions Python 라이브러리(azure-functions)에서 제공하는 기능입니다.

(https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-output?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&pivots=programming-language-python)

 

# 2. 메타데이터(Application Properties) 추출
        # bytes 키를 처리하기 위한 유연한 로직
        props = msg.application_properties or {}
        def decode_meta(key):
            val = props.get(key.encode()) or props.get(key)
            return val.decode() if isinstance(val, bytes) else val

 

 

FastAPI 서버가 받기 편하게 데이터를 하나의 JSON으로 예쁘게 포장하는 단계입니다.

 

# 3. FastAPI로 전달할 통합 페이로드 조립
        payload = {
            "patient_id": msg.session_id,      # 환자 식별자
            "message_id": msg.message_id,
            "region": decode_meta("Region") or "Unknown",
            "state": decode_meta("PatientState") or "STATUS_UPDATE",
            "body": event_data                 # 동료가 보낸 실제 데이터
        }
        
        logging.info(f"🚚 [AzFunc] 중계 시작: 환자 {payload['patient_id']}")

 

 

requests.post(...): 포장한 payload를 FastAPI 서버로 발사합니다.

 

다시 JSON으로 만드는 작업을 하는 이유는 Service Bus에서 온 메시지가 내용물(Body)과 **'겉봉투(Header/Properties)로 분리되어 있습니다.

  • 내용물 (body): 환자의 이름, 혈압, 증상 등 (순수 데이터)
  • 겉봉투 (msg 객체 속성): Session ID (환자 ID), Message ID, Region (어느 지역 병원인지 등)

만약 body만 그대로 FastAPI로 토스해버리면, FastAPI는 이게 도대체 누구(ID) 데이터야?를 알 수 없게 됩니다. (Session ID는 겉봉투에만 있으니까요!) 우리는 겉봉투에 있는 patient_id(session_id)와 region 정보를 꺼내서, 하나의 완벽한 서류(JSON Payload)로 합쳐서 보내야 합니다.

# 4. FastAPI 서버로 전송
        response = requests.post(
            FASTAPI_URL,
            json=payload,
            timeout=5
        )

        # 5. 전송 결과 확인 및 에러 핸들링
        if response.status_code >= 400:
            logging.error(f"❌ FastAPI 오류 ({response.status_code}): {response.text}")
            raise Exception("FastAPI 전송 실패로 인한 재시도")

        logging.info(f"✅ 환자 {payload['patient_id']} 이벤트 방송 성공!")

로컬 테스트

 

앞서 작업한 function_app.py 에서 JSON 파일을 보낼 URL을 http://localhost:8000/api/v1/emc/broadcast로 설정했습니다. 

 

해당 파일이 전송되려면 라우터를 설정해줘야 합니다. 

 
# routers/relay.py
from fastapi import APIRouter
from pydantic import BaseModel
from typing import Dict, Any
from services.alert_system import manager # 웹소켓 매니저

router = APIRouter(prefix="/api/v1/emc", tags=["Emergency"])

class EmergencyBroadcast(BaseModel):
    patient_id: str
    message_id: str
    region: str
    state: str
    body: Dict[str, Any]

@router.post("/broadcast")
async def receive_and_broadcast(event: EmergencyBroadcast):
    print(f"📢 [FastAPI] AzFunc로부터 수신: {event.patient_id}")
    
    # 여기서 웹소켓으로 연결된 병원들에게 뿌림
    await manager.broadcast({
        "type": "REALTIME_UPDATE",
        "patient_id": event.patient_id,
        "state": event.state,
        "data": event.body
    })
    return {"status": "broadcast_success"}

 

여기서 사용한 브로드캐스트는 "내 서버에 연결된 모든 병원 프론트엔드 화면에, 이 환자 정보를 동시에 띄워라!" 라는 명령입니다.

 

 

여기까지 완료했다면 FastAPI 서버를 먼저 실행합니다. 

 

그리고 VScode에서 F5 키를 눌러서 Azrue Fucntion을 실행합니다. 

정상적으로 실행했다면 Worker process started and initialized가 나오면 감시가 시작된 것입니다. 

 

두 가지가 잘 켜졌다면 다시 Azure 포털로 돌아가 Service Bus 메뉴에서 Service Bus Explorer를 이용해 직접 테스트 메시지를 az-patient-event-hub로 던져볼 수 있습니다.

{
        "patient_identity": {
            "name": "홍길동",
            "age": "50대",
            "gender": "남성"
        },
        "patient_condition": {
            "symptoms": "공사장 3m 높이 추락, 좌측 대퇴부 변형 및 극심한 통증",
            "vital_bp": "110/70",
            "vital_temp": "36.5",
            "vital_spo2": "95%",
            "consciousness": "명료"
        },
        "triage_info": {
            "dept_code": "TRAUMA"
        }
    }

 

 

해당 내용으로 Service Bus에 Send하면 아래와 같이 로그가 뜹니다. 

 

웹소켓을 확인할 수 있는 HTML 파일을 만들고 접속해봅니다. 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Siren - 실시간 응급 모니터링</title>
    <style>
        body { font-family: 'Pretendard', sans-serif; background-color: #0f172a; color: white; margin: 0; padding: 20px; }
        header { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #ef4444; padding-bottom: 10px; margin-bottom: 20px; }
        .status-tag { background: #ef4444; padding: 5px 10px; border-radius: 4px; font-weight: bold; animation: blink 1.5s infinite; }
        @keyframes blink { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
        
        #patient-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 20px; }
        .patient-card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 20px; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5); }
        .card-header { display: flex; justify-content: space-between; border-bottom: 1px solid #334155; padding-bottom: 10px; margin-bottom: 15px; }
        .dept-badge { background: #3b82f6; padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; }
        
        .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 0.9rem; }
        .label { color: #94a3b8; }
        .vital-box { background: #0f172a; padding: 10px; border-radius: 8px; margin-top: 15px; display: flex; justify-content: space-between; border-left: 4px solid #ef4444; }
        .vital-item { text-align: center; }
        .vital-value { display: block; font-size: 1.1rem; font-weight: bold; color: #ef4444; }
        .symptoms { margin-top: 15px; padding: 10px; background: #334155; border-radius: 6px; font-size: 0.95rem; line-height: 1.4; }
    </style>
</head>
<body>

<header>
    <h1>🚨 SIREN 실시간 응급 환자 이송 현황</h1>
    <div id="connection-status" class="status-tag">연결 대기 중...</div>
</header>

<div id="patient-container">
    </div>

<script>
    // 🔗 FastAPI 웹소켓 주소 (실제 서버 주소에 맞게 수정)
    const socket = new WebSocket("ws://localhost:8000/ws/monitor");

    const statusTag = document.getElementById('connection-status');
    const container = document.getElementById('patient-container');

    socket.onopen = () => {
        statusTag.innerText = "실시간 연결됨";
        statusTag.style.background = "#22c55e";
    };

    socket.onmessage = (event) => {
        const data = JSON.parse(event.data);
        // Azure Function에서 넘어온 데이터 구조가 event.data 혹은 data.data에 있을 수 있음
        const patient = data.data; 

        addPatientCard(patient);
    };

    socket.onclose = () => {
        statusTag.innerText = "연결 끊김";
        statusTag.style.background = "#64748b";
    };

    function addPatientCard(data) {
        const iden = data.patient_identity;
        const cond = data.patient_condition;
        const triage = data.triage_info;

        const card = document.createElement('div');
        card.className = 'patient-card';
        card.innerHTML = `
            <div class="card-header">
                <span style="font-size: 1.2rem; font-weight: bold;">${iden.name} (${iden.age}, ${iden.gender})</span>
                <span class="dept-badge">${triage.dept_code}</span>
            </div>
            <div class="info-grid">
                <div><span class="label">의식상태:</span> ${cond.consciousness}</div>
                <div><span class="label">발생시간:</span> ${new Date().toLocaleTimeString()}</div>
            </div>
            <div class="symptoms">
                <strong>주요 증상:</strong><br>
                ${cond.symptoms}
            </div>
            <div class="vital-box">
                <div class="vital-item"><span class="label">BP</span><span class="vital-value">${cond.vital_bp}</span></div>
                <div class="vital-item"><span class="label">Temp</span><span class="vital-value">${cond.vital_temp}°C</span></div>
                <div class="vital-item"><span class="label">SpO2</span><span class="vital-value">${cond.vital_spo2}</span></div>
            </div>
        `;
        
        // 새로운 환자가 가장 위에 보이게 추가
        container.prepend(card);
    }
</script>
</body>
</html>

 

 

아래와 같이 데이터가 잘 들어옴을 확인할 수 있습니다.

 

'FastAPI' 카테고리의 다른 글

[FastAPI] Nginx 사용하기  (0) 2025.12.18
[FastAPI] Azure Service Bus와 Function App을 활용한 데이터 전송 (2)  (0) 2025.12.18
[FastAPI] Naver API를 사용한 거리 및 최단경로 시간 탐색  (0) 2025.12.17
[FastAPI] websocket을 사용한 통신  (0) 2025.12.17
[FastAPI] Azure OpenAI를 활용한 오디오 분석  (0) 2025.12.16
'FastAPI' 카테고리의 다른 글
  • [FastAPI] Nginx 사용하기
  • [FastAPI] Azure Service Bus와 Function App을 활용한 데이터 전송 (2)
  • [FastAPI] Naver API를 사용한 거리 및 최단경로 시간 탐색
  • [FastAPI] websocket을 사용한 통신
David0903
David0903
  • David0903
    별별 코딩
    David0903
  • 전체
    오늘
    어제
    • 전체 (92)
      • Microsoft Azure (33)
      • FastAPI (8)
      • YOLO (4)
      • C++ (27)
      • Deep Learning (12)
      • Business Data Analysis (2)
      • Basic Data Analysis (5)
      • Statistics (1)
      • Data Analysis (0)
      • Computer Science (0)
      • InfoSec (0)
  • 블로그 메뉴

    • 홈
  • 링크

    • 블로그
  • 공지사항

  • 인기 글

  • 태그

    call by reference
    ROS
    c++
    테스트셋
    deep learning
    Friend
    모델
    call by value
    deep learing
    데이터 다루기
    모델 설계
    경사하강법
    생성자
    object
    상속
    딥러닝
    k겹 교차
    원-핫 인코딩
    operator overloading
    학습셋
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
David0903
[FastAPI] Azure Service Bus와 Function App을 활용한 데이터 전송 (1)
상단으로

티스토리툴바