# PlanitAI KPI - API 설계 **시리즈**: PlanitAI KPI 개발기 v7/16 **작성일**: 2025-12-07 **작성자**: GemEgg Dev Team --- ## 1. API 설계 원칙 ### 1.1 RESTful API 원칙 ``` ┌─────────────────────────────────────────────────────────────┐ │ API Design Principles │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 리소스 중심 (Resource-Oriented) │ │ └── /kpi-trees, /kpi-nodes, /kpi-data │ │ │ │ 2. HTTP 메서드 활용 │ │ ├── GET : 조회 │ │ ├── POST : 생성 │ │ ├── PUT : 전체 수정 │ │ ├── PATCH : 부분 수정 │ │ └── DELETE : 삭제 │ │ │ │ 3. 일관된 응답 형식 │ │ └── { data, meta, error } │ │ │ │ 4. 버전 관리 │ │ └── /api/v1/... │ │ │ │ 5. HATEOAS (선택적) │ │ └── 관련 리소스 링크 포함 │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 2. API 엔드포인트 목록 ### 2.1 전체 엔드포인트 ``` ┌─────────────────────────────────────────────────────────────┐ │ PlanitAI KPI API v1 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 인증 (Auth) │ │ ──────────────────────────────────────────── │ │ POST /api/v1/auth/login │ │ POST /api/v1/auth/logout │ │ POST /api/v1/auth/refresh │ │ GET /api/v1/auth/me │ │ │ │ 조직 (Organizations) │ │ ──────────────────────────────────────────── │ │ GET /api/v1/organizations │ │ GET /api/v1/organizations/:id │ │ PATCH /api/v1/organizations/:id │ │ │ │ KPI 트리 (KPI Trees) │ │ ──────────────────────────────────────────── │ │ GET /api/v1/kpi-trees │ │ POST /api/v1/kpi-trees │ │ GET /api/v1/kpi-trees/:id │ │ PUT /api/v1/kpi-trees/:id │ │ DELETE /api/v1/kpi-trees/:id │ │ POST /api/v1/kpi-trees/:id/clone │ │ POST /api/v1/kpi-trees/:id/validate │ │ │ │ KPI 노드 (KPI Nodes) │ │ ──────────────────────────────────────────── │ │ GET /api/v1/kpi-trees/:treeId/nodes │ │ POST /api/v1/kpi-trees/:treeId/nodes │ │ GET /api/v1/kpi-trees/:treeId/nodes/:id │ │ PUT /api/v1/kpi-trees/:treeId/nodes/:id │ │ DELETE /api/v1/kpi-trees/:treeId/nodes/:id │ │ PATCH /api/v1/kpi-trees/:treeId/nodes/:id/move │ │ │ │ KPI 데이터 (KPI Data) │ │ ──────────────────────────────────────────── │ │ GET /api/v1/kpi-data │ │ POST /api/v1/kpi-data/bulk │ │ GET /api/v1/kpi-data/:nodeId │ │ PUT /api/v1/kpi-data/:nodeId/:period │ │ │ │ 분석 (Analysis) │ │ ──────────────────────────────────────────── │ │ GET /api/v1/analysis/variance │ │ GET /api/v1/analysis/bottlenecks │ │ POST /api/v1/analysis/simulate │ │ GET /api/v1/analysis/ai-summary │ │ │ │ 리포트 (Reports) │ │ ──────────────────────────────────────────── │ │ GET /api/v1/reports │ │ POST /api/v1/reports/generate │ │ GET /api/v1/reports/:id │ │ GET /api/v1/reports/:id/download │ │ │ │ 데이터 소스 (Data Sources) │ │ ──────────────────────────────────────────── │ │ GET /api/v1/data-sources │ │ POST /api/v1/data-sources/google-sheets/connect │ │ POST /api/v1/data-sources/google-sheets/sync │ │ POST /api/v1/data-sources/freee/connect │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 3. 상세 API 스펙 ### 3.1 KPI 트리 API #### GET /api/v1/kpi-trees ```yaml # 트리 목록 조회 Request: Query Parameters: - fiscal_year: string (optional) # "2025" - status: string (optional) # "active", "archived" - page: integer (default: 1) - limit: integer (default: 20) Response 200: { "data": [ { "id": "tree_001", "name": "2025年度 営業KPI", "fiscal_year": "2025", "status": "active", "node_count": 15, "root_node": { "id": "kpi_001", "name": "売上高" }, "created_at": "2025-01-01T00:00:00Z", "updated_at": "2025-12-07T10:00:00Z" } ], "meta": { "page": 1, "limit": 20, "total": 3, "total_pages": 1 } } ``` #### POST /api/v1/kpi-trees ```yaml # 트리 생성 Request: Body: { "name": "2025年度 営業KPI", "fiscal_year": "2025", "description": "営業部門のKPI管理", "template": "sales_basic" # optional: 템플릿 사용 } Response 201: { "data": { "id": "tree_002", "name": "2025年度 営業KPI", "fiscal_year": "2025", "status": "active", "root_node_id": null, "created_at": "2025-12-07T10:00:00Z" } } Response 400: { "error": { "code": "VALIDATION_ERROR", "message": "Invalid fiscal year format", "details": [ {"field": "fiscal_year", "message": "Must be YYYY format"} ] } } ``` #### GET /api/v1/kpi-trees/:id ```yaml # 트리 상세 조회 (노드 포함) Request: Path Parameters: - id: string Query Parameters: - include_nodes: boolean (default: true) - include_data: boolean (default: false) - period: string (optional) # "2025-04" for monthly data Response 200: { "data": { "id": "tree_001", "name": "2025年度 営業KPI", "fiscal_year": "2025", "status": "active", "root_node_id": "kpi_001", "nodes": [ { "id": "kpi_001", "name": "売上高", "node_type": "kgi", "category": "finance", "unit": "currency", "unit_label": "円", "formula": "kpi_002 * kpi_003", "parent_id": null, "children_ids": ["kpi_002", "kpi_003"], "level": 0, "data": { # include_data=true 시 "plan": 2000000, "actual": 1800000, "variance": -200000, "achievement_rate": 90.0 } }, // ... more nodes ], "statistics": { "total_nodes": 15, "max_depth": 4, "categories": { "finance": 2, "sales": 5, "marketing": 8 } } } } ``` ### 3.2 KPI 노드 API #### POST /api/v1/kpi-trees/:treeId/nodes ```yaml # 노드 생성 Request: Body: { "name": "新規リード数", "name_en": "New Leads", "node_type": "input", "category": "marketing", "unit": "count", "unit_label": "件", "parent_id": "kpi_007", "department": "マーケティング部", "target_direction": "higher_is_better" } Response 201: { "data": { "id": "kpi_020", "tree_id": "tree_001", "name": "新規リード数", // ... full node object } } ``` #### PATCH /api/v1/kpi-trees/:treeId/nodes/:id/move ```yaml # 노드 이동 (부모 변경) Request: Body: { "new_parent_id": "kpi_005", "order": 2 # 형제 노드 중 순서 } Response 200: { "data": { "id": "kpi_020", "parent_id": "kpi_005", "level": 3, "order": 2 } } Response 400: { "error": { "code": "CIRCULAR_REFERENCE", "message": "Moving this node would create a circular reference" } } ``` ### 3.3 KPI 데이터 API #### POST /api/v1/kpi-data/bulk ```yaml # 데이터 일괄 업데이트 Request: Body: { "tree_id": "tree_001", "period": "2025-04", "data_type": "actual", "values": [ {"node_id": "kpi_001", "value": 1800000}, {"node_id": "kpi_002", "value": 36}, {"node_id": "kpi_003", "value": 50000}, {"node_id": "kpi_007", "value": 380} ] } Response 200: { "data": { "updated": 4, "calculated": 11, # 자동 계산된 노드 "errors": [] } } ``` #### GET /api/v1/kpi-data ```yaml # 데이터 조회 (기간별) Request: Query Parameters: - tree_id: string (required) - start_period: string # "2025-04" - end_period: string # "2025-12" - data_types: string[] # ["plan", "actual"] - node_ids: string[] # 특정 노드만 Response 200: { "data": { "tree_id": "tree_001", "periods": ["2025-04", "2025-05", "2025-06"], "nodes": { "kpi_001": { "name": "売上高", "values": { "2025-04": {"plan": 2000000, "actual": 1800000}, "2025-05": {"plan": 2100000, "actual": 1950000}, "2025-06": {"plan": 2200000, "actual": null} } }, // ... more nodes } } } ``` ### 3.4 분석 API #### GET /api/v1/analysis/variance ```yaml # 예실 차이 분석 Request: Query Parameters: - tree_id: string (required) - period: string (required) # "2025-04" Response 200: { "data": { "period": "2025-04", "summary": { "kgi_achievement": 90.0, "below_target_count": 5, "on_target_count": 8, "above_target_count": 2 }, "nodes": [ { "node_id": "kpi_001", "name": "売上高", "plan": 2000000, "actual": 1800000, "variance": -200000, "variance_rate": -10.0, "achievement_rate": 90.0, "status": "below_target" }, // ... more nodes ] } } ``` #### GET /api/v1/analysis/bottlenecks ```yaml # 병목 분석 Request: Query Parameters: - tree_id: string (required) - period: string (required) - threshold: number (default: -10) # 달성률 기준 Response 200: { "data": { "bottlenecks": [ { "node_id": "kpi_006", "name": "商談化率", "category": "marketing", "achievement_rate": 80.0, "variance_rate": -20.0, "impact_level": 33.3, "suggested_actions": [ "インサイドセールスのトーク改善", "ナーチャリング施策の強化" ], "estimated_impact": { "if_improved_to_target": { "kgi_increase": 250000, "kgi_increase_rate": 12.5 } } } ], "ai_analysis": "商談化率が20%下落しており..." } } ``` #### POST /api/v1/analysis/simulate ```yaml # What-If 시뮬레이션 Request: Body: { "tree_id": "tree_001", "base_period": "2025-04", "changes": [ {"node_id": "kpi_007", "value": 450} ] } Response 200: { "data": { "simulation_id": "sim_001", "changes_applied": [ { "node_id": "kpi_007", "name": "リード数", "before": 380, "after": 450, "change_rate": 18.4 } ], "impact": { "kpi_001": { "name": "売上高", "before": 1800000, "after": 2131579, "absolute_change": 331579, "percentage_change": 18.4 } }, "summary": "リード数を450件に増やすと、売上高が約33万円増加する見込みです。" } } ``` #### GET /api/v1/analysis/ai-summary ```yaml # AI 분석 요약 Request: Query Parameters: - tree_id: string (required) - period: string (required) - type: string # "summary", "variance", "forecast" Response 200: { "data": { "type": "summary", "period": "2025-04", "content": "