Skip to content

Latest commit

 

History

History
1407 lines (1164 loc) · 28.6 KB

File metadata and controls

1407 lines (1164 loc) · 28.6 KB

Merck AOA System API 사용 가이드

버전: 2.0 최종 업데이트: 2025-12-11 시스템: Merck Korea AOA 빌딩 입출입 관리 시스템

📋 버전 2.0 주요 변경사항

새로운 기능

  • 앵커 방향(Orientation) 지원: 문 밖에서 안을 바라보는 앵커 지원 (INWARD/OUTWARD)
  • 앵커 기울기(Tilt Angle) 자동 보정: 45도 기울어진 앵커의 정확한 위치 판단
  • 상태 기반 이벤트 보정: 누락된 입출입 이벤트 자동 생성
  • 재실 현황 정확도 향상: 시스템 재시작 시에도 정확한 재실 현황 유지

API 변경사항

  • 모든 기존 API 엔드포인트 호환성 유지
  • 응답 데이터에 confidence_score 추가 (이벤트 신뢰도)
  • Anchor 관리 API에 orientation, tilt_angle 필드 추가

목차

  1. 개요
  2. 인증 및 기본 설정
  3. 실시간 모니터링 API
  4. 건물 관리 API
  5. 게이트 관리 API
  6. 앵커 관리 API
  7. 태그 관리 API
  8. 긴급상황 API
  9. 통계 API
  10. 에러 처리
  11. 실시간 WebSocket

개요

Merck AOA System API는 AOA(Angle of Arrival) 블루투스 기술 기반 실시간 입출입 추적 시스템을 위한 RESTful API입니다.

기본 정보

  • Base URL: http://localhost:8001/api/v1
  • API 문서: http://localhost:8001/docs (Swagger UI)
  • Content-Type: application/json
  • 응답 형식: JSON

주요 기능

  • 실시간 재실 현황 모니터링
  • 건물/게이트/앵커/태그 관리
  • 입출입 이력 조회
  • 긴급상황 대응
  • 통계 및 분석

인증 및 기본 설정

현재 버전에서는 인증이 구현되어 있지 않습니다. 추후 JWT 기반 인증이 추가될 예정입니다.

공통 헤더

Content-Type: application/json
Accept: application/json

응답 형식

모든 API는 다음 형식의 응답을 반환합니다:

{
  "success": true,
  "data": { ... }
}

또는 에러 시:

{
  "detail": "에러 메시지"
}

실시간 모니터링 API

1. 헬스 체크

시스템 상태를 확인합니다.

엔드포인트: GET /health

응답 예시:

{
  "status": "healthy",
  "redis": "connected",
  "mysql": "connected",
  "timestamp": "2025-12-03T17:00:00"
}

cURL 예시:

curl http://localhost:8001/api/v1/health

2. 전체 건물 재실 현황

모든 건물의 현재 재실자 수를 조회합니다.

엔드포인트: GET /realtime/occupancy

응답 예시:

{
  "success": true,
  "data": [
    {
      "building_id": "BLDG01",
      "building_name": "본관",
      "occupant_count": 45,
      "rooftop_count": 2,
      "last_updated": "2025-12-03T17:00:00"
    }
  ]
}

cURL 예시:

curl http://localhost:8001/api/v1/realtime/occupancy

3. 건물별 상세 재실 현황

특정 건물의 재실자 상세 정보를 조회합니다.

엔드포인트: GET /realtime/occupancy/detailed?building_id={building_id}

파라미터:

  • building_id (required): 건물 ID

응답 예시:

{
  "success": true,
  "data": {
    "building_id": "BLDG01",
    "building_name": "본관",
    "occupant_count": 45,
    "occupants": [
      {
        "tag_id": "TAG001",
        "entry_time": "2025-12-03T09:30:00",
        "last_gate": "GATE01",
        "last_seen": "2025-12-03T16:55:00",
        "duration_minutes": 445,
        "battery_level": 85
      }
    ],
    "last_updated": "2025-12-03T17:00:00"
  }
}

cURL 예시:

curl "http://localhost:8001/api/v1/realtime/occupancy/detailed?building_id=BLDG01"

4. 원본 데이터 스트림 조회 (디버깅)

Redis Stream에서 원본 AOA 데이터를 JSON 형식으로 조회합니다. 디버깅 및 시스템 모니터링용입니다.

엔드포인트: GET /realtime/raw-stream?count={count}

파라미터:

  • count (optional, default=100, max=1000): 가져올 데이터 개수

Rate Limiting: 100 requests/minute

응답 예시:

{
  "success": true,
  "message": null,
  "data": {
    "count": 100,
    "items": [
      {
        "stream_id": "1734534123456-0",
        "timestamp": "1734534123456",
        "data": {
          "anchorMac": "AA:BB:CC:DD:EE:01",
          "tagId": "C483727A0001",
          "distance": 2.5,
          "angle1": 45,
          "angle2": -30,
          "rssi": -75,
          "battery": 85,
          "timestamp": 1734534123456
        }
      }
    ]
  }
}

특징:

  • Redis Stream에서 최신 데이터부터 역순으로 반환
  • 자동 타입 변환 (int, float, string)
  • 태그별 게이트별 필터링 가능 (프론트엔드에서)
  • Stream ID에서 밀리초 타임스탬프 추출

cURL 예시:

# 최근 100개 데이터 조회
curl "http://localhost:8001/api/v1/realtime/raw-stream"

# 최근 500개 데이터 조회
curl "http://localhost:8001/api/v1/realtime/raw-stream?count=500"

# 최대 1000개 제한
curl "http://localhost:8001/api/v1/realtime/raw-stream?count=1500"

Python 예시:

import requests

# 원본 데이터 조회
response = requests.get("http://localhost:8001/api/v1/realtime/raw-stream?count=50")
data = response.json()

if data["success"]:
    print(f"총 {data['data']['count']}개 데이터:")
    for item in data["data"]["items"]:
        print(f"Stream ID: {item['stream_id']}")
        print(f"태그: {item['data']['tagId']}")
        print(f"거리: {item['data']['distance']}m")
        print(f"앵커: {item['data']['anchorMac']}")
        print("-" * 40)

건물 관리 API

1. 건물 목록 조회

엔드포인트: GET /buildings

응답 예시:

{
  "success": true,
  "total": 6,
  "buildings": [
    {
      "id": "BLDG01",
      "name": "본관",
      "description": "메인 오피스 빌딩",
      "address": "서울시 강남구",
      "floor_count": 10,
      "is_active": true,
      "gate_count": 6,
      "current_occupants": 45,
      "created_at": "2025-01-01T00:00:00",
      "updated_at": "2025-01-01T00:00:00"
    }
  ]
}

cURL 예시:

curl http://localhost:8001/api/v1/buildings

2. 건물 상세 조회

엔드포인트: GET /buildings/{building_id}

응답 예시:

{
  "success": true,
  "data": {
    "id": "BLDG01",
    "name": "본관",
    "description": "메인 오피스 빌딩",
    "address": "서울시 강남구",
    "floor_count": 10,
    "is_active": true,
    "gate_count": 6,
    "current_occupants": 45,
    "gates": [
      {
        "gate_id": "GATE01",
        "name": "1층 정문",
        "location": "1F 메인 로비",
        "floor": 1,
        "gate_type": "NORMAL",
        "is_active": true,
        "anchor_id": "ANC001",
        "anchor_mac": "AA:BB:CC:DD:EE:01",
        "last_heartbeat": "2025-12-03T16:59:00"
      }
    ],
    "today_entries": 123,
    "today_exits": 78,
    "created_at": "2025-01-01T00:00:00",
    "updated_at": "2025-01-01T00:00:00"
  }
}

cURL 예시:

curl http://localhost:8001/api/v1/buildings/BLDG01

3. 건물 생성

엔드포인트: POST /buildings

요청 본문:

{
  "id": "BLDG07",
  "name": "신관",
  "description": "새로 지은 건물",
  "address": "서울시 강남구",
  "floor_count": 15
}

응답 예시 (201 Created):

{
  "success": true,
  "message": "건물이 생성되었습니다",
  "data": {
    "id": "BLDG07",
    "name": "신관",
    "description": "새로 지은 건물",
    "address": "서울시 강남구",
    "floor_count": 15,
    "is_active": true,
    "gate_count": 0,
    "current_occupants": 0,
    "created_at": "2025-12-03T17:00:00",
    "updated_at": "2025-12-03T17:00:00"
  }
}

cURL 예시:

curl -X POST http://localhost:8001/api/v1/buildings \
  -H "Content-Type: application/json" \
  -d '{
    "id": "BLDG07",
    "name": "신관",
    "description": "새로 지은 건물",
    "address": "서울시 강남구",
    "floor_count": 15
  }'

4. 건물 수정

엔드포인트: PUT /buildings/{building_id}

요청 본문 (모든 필드 선택적):

{
  "name": "신관 A동",
  "description": "업데이트된 설명",
  "floor_count": 20
}

응답 예시:

{
  "success": true,
  "message": "건물 정보가 수정되었습니다",
  "building_id": "BLDG07"
}

cURL 예시:

curl -X PUT http://localhost:8001/api/v1/buildings/BLDG07 \
  -H "Content-Type: application/json" \
  -d '{
    "name": "신관 A동",
    "floor_count": 20
  }'

5. 건물 비활성화

엔드포인트: DELETE /buildings/{building_id}

응답 예시:

{
  "success": true,
  "message": "건물이 비활성화되었습니다",
  "building_id": "BLDG07"
}

cURL 예시:

curl -X DELETE http://localhost:8001/api/v1/buildings/BLDG07

참고: Soft delete이므로 데이터는 보존되며 is_active만 FALSE로 변경됩니다.


게이트 관리 API

1. 게이트 목록 조회

엔드포인트: GET /gates

쿼리 파라미터:

  • building_id (optional): 건물 ID로 필터링
  • zone_type (optional): 구역 타입 필터 (INDOOR/ROOFTOP)
  • is_active (optional): 활성화 상태 필터

응답 예시:

{
  "success": true,
  "total": 36,
  "gates": [
    {
      "id": "GATE01",
      "name": "1층 정문",
      "building_id": "BLDG01",
      "building_name": "본관",
      "anchor_id": "ANC001",
      "anchor_mac": "AA:BB:CC:DD:EE:01",
      "floor": 1,
      "location_description": "1F 메인 로비",
      "gate_type": "NORMAL",
      "zone_type": "INDOOR",
      "is_active": true,
      "created_at": "2025-01-01T00:00:00",
      "updated_at": "2025-01-01T00:00:00"
    }
  ]
}

cURL 예시:

# 전체 게이트 조회
curl http://localhost:8001/api/v1/gates

# 특정 건물의 게이트만 조회
curl "http://localhost:8001/api/v1/gates?building_id=BLDG01"

# 옥상 게이트만 조회
curl "http://localhost:8001/api/v1/gates?zone_type=ROOFTOP"

2. 게이트 상세 조회

엔드포인트: GET /gates/{gate_id}

응답 예시:

{
  "success": true,
  "data": {
    "id": "GATE01",
    "name": "1층 정문",
    "building_id": "BLDG01",
    "building_name": "본관",
    "anchor_id": "ANC001",
    "anchor_mac": "AA:BB:CC:DD:EE:01",
    "floor": 1,
    "location_description": "1F 메인 로비",
    "gate_type": "NORMAL",
    "zone_type": "INDOOR",
    "is_active": true,
    "anchor_status": "online",
    "last_heartbeat": "2025-12-03T16:59:00",
    "recent_events_count": 15,
    "today_entries": 45,
    "today_exits": 32,
    "created_at": "2025-01-01T00:00:00",
    "updated_at": "2025-01-01T00:00:00"
  }
}

cURL 예시:

curl http://localhost:8001/api/v1/gates/GATE01

3. 게이트 생성

엔드포인트: POST /gates

요청 본문:

{
  "id": "GATE37",
  "name": "지하주차장 입구",
  "building_id": "BLDG01",
  "anchor_id": "ANC030",
  "floor": -1,
  "location_description": "B1 주차장",
  "gate_type": "NORMAL",
  "zone_type": "INDOOR"
}

응답 예시 (201 Created):

{
  "success": true,
  "message": "게이트가 생성되었습니다",
  "data": {
    "id": "GATE37",
    "name": "지하주차장 입구",
    "building_id": "BLDG01",
    "building_name": "본관",
    "anchor_id": "ANC030",
    "anchor_mac": "AA:BB:CC:DD:EE:30",
    "floor": -1,
    "location_description": "B1 주차장",
    "gate_type": "NORMAL",
    "zone_type": "INDOOR",
    "is_active": true,
    "created_at": "2025-12-03T17:00:00",
    "updated_at": "2025-12-03T17:00:00"
  }
}

cURL 예시:

curl -X POST http://localhost:8001/api/v1/gates \
  -H "Content-Type: application/json" \
  -d '{
    "id": "GATE37",
    "name": "지하주차장 입구",
    "building_id": "BLDG01",
    "anchor_id": "ANC030",
    "floor": -1,
    "location_description": "B1 주차장",
    "gate_type": "NORMAL",
    "zone_type": "INDOOR"
  }'

4. 게이트 수정

엔드포인트: PUT /gates/{gate_id}

요청 본문 (모든 필드 선택적):

{
  "name": "지하주차장 메인 입구",
  "zone_type": "INDOOR",
  "is_active": true
}

cURL 예시:

curl -X PUT http://localhost:8001/api/v1/gates/GATE37 \
  -H "Content-Type: application/json" \
  -d '{
    "name": "지하주차장 메인 입구"
  }'

5. 게이트 비활성화

엔드포인트: DELETE /gates/{gate_id}

cURL 예시:

curl -X DELETE http://localhost:8001/api/v1/gates/GATE37

앵커 관리 API

1. 앵커 목록 조회

엔드포인트: GET /anchors

쿼리 파라미터:

  • building_id (optional): 건물 ID로 필터링
  • status (optional): 상태 필터 (online/offline)

응답 예시:

{
  "success": true,
  "total": 36,
  "online_count": 34,
  "offline_count": 2,
  "anchors": [
    {
      "id": "ANC001",
      "gate_id": "GATE01",
      "gate_name": "1층 정문",
      "building_id": "BLDG01",
      "building_name": "본관",
      "mac_address": "AA:BB:CC:DD:EE:01",
      "is_active": true,
      "is_online": true,
      "last_heartbeat": "2025-12-03T16:59:30",
      "seconds_since_heartbeat": 30,
      "calibration": {
        "installation_height": 2.74,
        "door_distance": 2.74,
        "tilt_angle": 45.0,
        "tag_height": 1.2,
        "rssi_at_anchor": -57,
        "rssi_at_door": -60,
        "rssi_reference": -48,
        "path_loss_exponent": 2.0
      },
      "firmware_version": "1.0.0",
      "notes": null
    }
  ]
}

cURL 예시:

# 전체 앵커 조회
curl http://localhost:8001/api/v1/anchors

# 온라인 앵커만 조회
curl "http://localhost:8001/api/v1/anchors?status=online"

# 특정 건물의 앵커만 조회
curl "http://localhost:8001/api/v1/anchors?building_id=BLDG01"

2. 앵커 상세 조회

엔드포인트: GET /anchors/{anchor_id}

응답 예시:

{
  "success": true,
  "data": {
    "id": "ANC001",
    "gate_id": "GATE01",
    "gate_name": "1층 정문",
    "building_id": "BLDG01",
    "building_name": "본관",
    "mac_address": "AA:BB:CC:DD:EE:01",
    "is_active": true,
    "is_online": true,
    "last_heartbeat": "2025-12-03T16:59:30",
    "seconds_since_heartbeat": 30,
    "calibration": {
      "installation_height": 2.74,
      "door_distance": 2.74,
      "tilt_angle": 45.0,
      "tag_height": 1.2,
      "rssi_at_anchor": -57,
      "rssi_at_door": -60,
      "rssi_reference": -48,
      "path_loss_exponent": 2.0
    },
    "firmware_version": "1.0.0",
    "notes": "정상 작동 중"
  }
}

cURL 예시:

curl http://localhost:8001/api/v1/anchors/ANC001

3. 앵커 실시간 상태 조회

엔드포인트: GET /anchors/{anchor_id}/status

응답 예시:

{
  "anchor_id": "ANC001",
  "mac_address": "AA:BB:CC:DD:EE:01",
  "status": "online",
  "is_online": true,
  "last_heartbeat": "2025-12-03T16:59:30",
  "seconds_since_heartbeat": 30,
  "checked_at": "2025-12-03T17:00:00"
}

cURL 예시:

curl http://localhost:8001/api/v1/anchors/ANC001/status

4. 앵커 생성

엔드포인트: POST /anchors

요청 본문:

{
  "id": "ANC037",
  "gate_id": "GATE37",
  "mac_address": "AA:BB:CC:DD:EE:37",
  "firmware_version": "1.0.0",
  "notes": "신규 설치",
  "ip_address": "172.23.104.137",
  "subnet_mask": "255.255.255.0",
  "gateway": "172.23.104.1",
  "installation_height": 2.74,
  "door_distance": 2.74,
  "tilt_angle": 45.0,
  "tag_height": 1.2,
  "rssi_at_anchor": -57,
  "rssi_at_door": -60,
  "rssi_reference": -48,
  "path_loss_exponent": 2.0
}

응답 예시 (201 Created):

{
  "success": true,
  "message": "앵커가 생성되었습니다",
  "data": {
    "id": "ANC037",
    "gate_id": "GATE37",
    "gate_name": "지하주차장 입구",
    "building_id": "BLDG01",
    "building_name": "본관",
    "mac_address": "AA:BB:CC:DD:EE:37",
    "is_active": true,
    "is_online": false,
    "last_heartbeat": null,
    "seconds_since_heartbeat": null,
    "calibration": {
      "installation_height": 2.74,
      "door_distance": 2.74,
      "tilt_angle": 45.0,
      "tag_height": 1.2,
      "rssi_at_anchor": -57,
      "rssi_at_door": -60,
      "rssi_reference": -48,
      "path_loss_exponent": 2.0
    },
    "firmware_version": "1.0.0",
    "notes": "신규 설치"
  }
}

cURL 예시:

curl -X POST http://localhost:8001/api/v1/anchors \
  -H "Content-Type: application/json" \
  -d '{
    "id": "ANC037",
    "gate_id": "GATE37",
    "mac_address": "AA:BB:CC:DD:EE:37",
    "ip_address": "172.23.104.137",
    "subnet_mask": "255.255.255.0",
    "gateway": "172.23.104.1",
    "installation_height": 2.74,
    "door_distance": 2.74,
    "tilt_angle": 45.0
  }'

5. 앵커 수정

엔드포인트: PUT /anchors/{anchor_id}

요청 본문 (모든 필드 선택적):

{
  "firmware_version": "1.1.0",
  "notes": "펌웨어 업데이트 완료",
  "door_distance": 3.0,
  "tilt_angle": 50.0
}

cURL 예시:

curl -X PUT http://localhost:8001/api/v1/anchors/ANC037 \
  -H "Content-Type: application/json" \
  -d '{
    "firmware_version": "1.1.0",
    "door_distance": 3.0
  }'

6. 앵커 비활성화

엔드포인트: DELETE /anchors/{anchor_id}

cURL 예시:

curl -X DELETE http://localhost:8001/api/v1/anchors/ANC037

태그 관리 API

1. 태그 목록 조회

엔드포인트: GET /tags

쿼리 파라미터:

  • is_active (optional): 활성 상태 필터
  • is_inside (optional): 재실 상태 필터
  • tag_type (optional): 태그 타입 필터 (EMPLOYEE/VISITOR/VEHICLE)
  • building_id (optional): 건물 ID 필터
  • limit (optional, default=100): 결과 수 제한
  • offset (optional, default=0): 오프셋

응답 예시:

{
  "success": true,
  "total": 530,
  "active_count": 520,
  "inside_count": 234,
  "tags": [
    {
      "id": "P0001",
      "mac_address": "C483727A0001",
      "rfid_id": "550e8400e29b41d4a716446655440000",
      "tag_type": "EMPLOYEE",
      "is_active": true,
      "battery_threshold": 20,
      "last_battery_level": 85,
      "last_seen": "2025-12-03T16:55:00",
      "current_building": "BLDG01",
      "current_building_name": "본관",
      "is_inside": true,
      "activation_date": "2025-01-01",
      "deactivation_date": null,
      "notes": null
    }
  ]
}

cURL 예시:

# 전체 태그 조회
curl http://localhost:8001/api/v1/tags

# 현재 재실 중인 태그만 조회
curl "http://localhost:8001/api/v1/tags?is_inside=true"

# 특정 건물에 있는 태그만 조회
curl "http://localhost:8001/api/v1/tags?building_id=BLDG01&is_inside=true"

# 페이지네이션
curl "http://localhost:8001/api/v1/tags?limit=50&offset=100"

2. 태그 상태 조회

엔드포인트: GET /tags/{tag_id}/status

응답 예시:

{
  "success": true,
  "data": {
    "tag_id": "P0001",
    "current_building": "BLDG01",
    "current_building_name": "본관",
    "current_gate": "GATE01",
    "last_event": "ENTRY",
    "last_event_time": "2025-12-03T09:30:00",
    "entry_time": "2025-12-03T09:30:00",
    "battery": 85,
    "last_seen": "2025-12-03T16:55:00",
    "is_inside": true
  }
}

cURL 예시:

curl http://localhost:8001/api/v1/tags/P0001/status

3. 배터리 부족 태그 목록

엔드포인트: GET /tags/low-battery

응답 예시:

{
  "success": true,
  "data": {
    "total_count": 5,
    "critical_count": 2,
    "tags": [
      {
        "tag_id": "P0045",
        "battery_level": 8,
        "last_seen": "2025-12-03T16:30:00",
        "is_critical": true
      },
      {
        "tag_id": "P0123",
        "battery_level": 15,
        "last_seen": "2025-12-03T16:45:00",
        "is_critical": false
      }
    ]
  }
}

cURL 예시:

curl http://localhost:8001/api/v1/tags/low-battery

4. 태그 생성

엔드포인트: POST /tags

요청 본문:

{
  "id": "P0531",
  "mac_address": "C483727A0531",
  "rfid_id": "550e8400e29b41d4a716446655440531",
  "tag_type": "EMPLOYEE",
  "battery_threshold": 20,
  "notes": "신규 직원"
}

응답 예시 (200 OK):

{
  "success": true,
  "message": "태그가 등록되었습니다",
  "tag_id": "P0531"
}

cURL 예시:

curl -X POST http://localhost:8001/api/v1/tags \
  -H "Content-Type: application/json" \
  -d '{
    "id": "P0531",
    "mac_address": "C483727A0531",
    "tag_type": "EMPLOYEE"
  }'

5. 태그 수정

엔드포인트: PUT /tags/{tag_id}

요청 본문 (모든 필드 선택적):

{
  "tag_type": "VISITOR",
  "battery_threshold": 15,
  "notes": "업데이트된 정보"
}

cURL 예시:

curl -X PUT http://localhost:8001/api/v1/tags/P0531 \
  -H "Content-Type: application/json" \
  -d '{
    "battery_threshold": 15,
    "notes": "업데이트된 정보"
  }'

6. 태그 비활성화

엔드포인트: DELETE /tags/{tag_id}

응답 예시:

{
  "success": true,
  "message": "태그가 비활성화되었습니다",
  "tag_id": "P0531"
}

cURL 예시:

curl -X DELETE http://localhost:8001/api/v1/tags/P0531

긴급상황 API

1. 긴급 재실자 조회

긴급상황 시 1초 이내 응답을 보장하는 최우선 엔드포인트입니다.

엔드포인트: GET /emergency/building/{building_id}/occupants

응답 예시:

{
  "success": true,
  "building_id": "BLDG01",
  "building_name": "본관",
  "indoor_count": 45,
  "rooftop_count": 2,
  "total_count": 47,
  "occupants": [
    {
      "tag_id": "P0001",
      "entry_time": "2025-12-03T09:30:00",
      "duration_minutes": 445,
      "last_gate": "GATE01",
      "zone_type": "INDOOR"
    },
    {
      "tag_id": "P0034",
      "entry_time": "2025-12-03T15:00:00",
      "duration_minutes": 120,
      "last_gate": "GATE14",
      "zone_type": "ROOFTOP"
    }
  ],
  "timestamp": "2025-12-03T17:00:00"
}

cURL 예시:

curl http://localhost:8001/api/v1/emergency/building/BLDG01/occupants

2. 긴급 전체 건물 요약

엔드포인트: GET /emergency/summary

응답 예시:

{
  "success": true,
  "total_buildings": 6,
  "total_occupants": 234,
  "buildings": [
    {
      "building_id": "BLDG01",
      "building_name": "본관",
      "indoor_count": 45,
      "rooftop_count": 2,
      "total_count": 47
    }
  ],
  "timestamp": "2025-12-03T17:00:00"
}

cURL 예시:

curl http://localhost:8001/api/v1/emergency/summary

통계 API

1. 출입 이력 조회

엔드포인트: GET /events/history

쿼리 파라미터:

  • tag_id (optional): 태그 ID 필터
  • building_id (optional): 건물 ID 필터
  • gate_id (optional): 게이트 ID 필터
  • event_type (optional): 이벤트 타입 필터 (ENTRY/EXIT)
  • start_date (optional): 시작일 (YYYY-MM-DD)
  • end_date (optional): 종료일 (YYYY-MM-DD)
  • limit (optional, default=100): 결과 수
  • offset (optional, default=0): 오프셋

응답 예시:

{
  "success": true,
  "total": 1523,
  "events": [
    {
      "event_id": "evt_12345",
      "tag_id": "P0001",
      "building_id": "BLDG01",
      "building_name": "본관",
      "gate_id": "GATE01",
      "gate_name": "1층 정문",
      "event_type": "ENTRY",
      "event_time": "2025-12-03T09:30:15.123",
      "confidence_score": 95.5,
      "distance": 1.85,
      "angle": 12.5,
      "battery_level": 85
    }
  ]
}

cURL 예시:

# 특정 태그의 이력
curl "http://localhost:8001/api/v1/events/history?tag_id=P0001"

# 오늘 출입 이력
curl "http://localhost:8001/api/v1/events/history?start_date=2025-12-03&end_date=2025-12-03"

# 특정 건물의 입실 이벤트만
curl "http://localhost:8001/api/v1/events/history?building_id=BLDG01&event_type=ENTRY"

2. 일일 통계

엔드포인트: GET /stats/daily

쿼리 파라미터:

  • date (optional, default=today): 날짜 (YYYY-MM-DD)
  • building_id (optional): 건물 ID 필터

응답 예시:

{
  "success": true,
  "date": "2025-12-03",
  "total_events": 856,
  "total_entries": 456,
  "total_exits": 400,
  "unique_tags": 234,
  "by_building": [
    {
      "building_id": "BLDG01",
      "building_name": "본관",
      "entries": 123,
      "exits": 98,
      "unique_tags": 67
    }
  ],
  "peak_hour": {
    "hour": 9,
    "entry_count": 89,
    "exit_count": 12
  }
}

cURL 예시:

# 오늘 통계
curl http://localhost:8001/api/v1/stats/daily

# 특정 날짜 통계
curl "http://localhost:8001/api/v1/stats/daily?date=2025-12-01"

에러 처리

HTTP 상태 코드

  • 200 OK: 성공
  • 201 Created: 리소스 생성 성공
  • 400 Bad Request: 잘못된 요청
  • 404 Not Found: 리소스를 찾을 수 없음
  • 409 Conflict: 중복된 리소스
  • 500 Internal Server Error: 서버 내부 오류

에러 응답 형식

{
  "detail": "에러 메시지"
}

일반적인 에러 예시

1. 존재하지 않는 리소스 (404):

{
  "detail": "건물을 찾을 수 없습니다"
}

2. 중복된 리소스 (409):

{
  "detail": "이미 존재하는 건물 ID입니다"
}

3. 잘못된 요청 (400):

{
  "detail": "수정할 필드가 없습니다"
}

4. 이미 비활성화된 리소스 (400):

{
  "detail": "이미 비활성화된 건물입니다"
}

실시간 WebSocket

실시간 위치 추적 (전체)

엔드포인트: ws://localhost:8001/api/v1/ws/positions

메시지 형식:

{
  "tag_id": "P0001",
  "gate_id": "GATE01",
  "building_id": "BLDG01",
  "distance": 1.85,
  "angle1": 12.5,
  "angle2": -35.0,
  "rssi": -65,
  "battery": 85,
  "timestamp": 1701612345678,
  "position": {
    "x": -0.45,
    "y": 1.75,
    "z": 0.1
  }
}

JavaScript 예시:

const ws = new WebSocket('ws://localhost:8001/api/v1/ws/positions');

ws.onopen = () => {
  console.log('WebSocket 연결됨');
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('위치 업데이트:', data);
};

ws.onerror = (error) => {
  console.error('WebSocket 에러:', error);
};

ws.onclose = () => {
  console.log('WebSocket 연결 종료');
};

실시간 위치 추적 (게이트별)

엔드포인트: ws://localhost:8001/api/v1/ws/gate/{gate_id}

게이트별로 필터링된 위치 데이터를 수신합니다.

JavaScript 예시:

const gateId = 'GATE01';
const ws = new WebSocket(`ws://localhost:8001/api/v1/ws/gate/${gateId}`);

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(`${gateId} 위치 업데이트:`, data);
};

부록: Python 예제 코드

기본 API 호출

import requests

BASE_URL = "http://localhost:8001/api/v1"

# 건물 목록 조회
response = requests.get(f"{BASE_URL}/buildings")
buildings = response.json()
print(f"총 건물 수: {buildings['total']}")

# 재실 현황 조회
response = requests.get(f"{BASE_URL}/realtime/occupancy")
occupancy = response.json()
for building in occupancy['data']:
    print(f"{building['building_name']}: {building['occupant_count']}명")

# 건물 생성
new_building = {
    "id": "BLDG07",
    "name": "신관",
    "floor_count": 15
}
response = requests.post(
    f"{BASE_URL}/buildings",
    json=new_building
)
if response.status_code == 201:
    print("건물 생성 성공")
else:
    print(f"에러: {response.json()['detail']}")

WebSocket 실시간 모니터링

import asyncio
import websockets
import json

async def monitor_positions():
    uri = "ws://localhost:8001/api/v1/ws/positions"

    async with websockets.connect(uri) as websocket:
        print("WebSocket 연결됨")

        while True:
            try:
                message = await websocket.recv()
                data = json.loads(message)

                print(f"태그: {data['tag_id']}")
                print(f"위치: x={data['position']['x']:.2f}, "
                      f"y={data['position']['y']:.2f}")
                print(f"배터리: {data['battery']}%")
                print("-" * 40)

            except websockets.exceptions.ConnectionClosed:
                print("연결 종료")
                break

# 실행
asyncio.run(monitor_positions())

지원 및 문의

문서 버전: 1.0 API 버전: 1.0.0 최종 업데이트: 2025-12-03

Swagger UI: http://localhost:8001/docs ReDoc: http://localhost:8001/redoc

개발팀: Merck Korea IT Team


이 문서는 Merck Korea AOA System API의 공식 사용 가이드입니다.