# 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": "", "highlights": [ { "type": "positive", "message": "営業利益率が改善" }, { "type": "negative", "message": "リード獲得が目標を下回る" } ], "generated_at": "2025-12-07T10:00:00Z", "model": "gemini-2.0-flash" } } ``` --- ## 4. 인증 및 권한 ### 4.1 인증 방식 ```yaml # JWT Bearer Token Authorization: Bearer eyJhbGciOiJIUzI1NiIs... # Token 구조 { "sub": "user_001", "org": "org_001", "role": "editor", "exp": 1702000000 } ``` ### 4.2 권한 매트릭스 ``` ┌─────────────────────────────────────────────────────────────┐ │ Permission Matrix │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Resource │ Owner │ Admin │ Editor │ Viewer │ │ ──────────────┼────────┼────────┼────────┼──────── │ │ Trees - Read │ ✓ │ ✓ │ ✓ │ ✓ │ │ Trees - Create│ ✓ │ ✓ │ ✓ │ ✗ │ │ Trees - Update│ ✓ │ ✓ │ ✓ │ ✗ │ │ Trees - Delete│ ✓ │ ✓ │ ✗ │ ✗ │ │ ──────────────┼────────┼────────┼────────┼──────── │ │ Nodes - Read │ ✓ │ ✓ │ ✓ │ ✓ │ │ Nodes - Create│ ✓ │ ✓ │ ✓ │ ✗ │ │ Nodes - Update│ ✓ │ ✓ │ ✓ │ ✗ │ │ Nodes - Delete│ ✓ │ ✓ │ ✗ │ ✗ │ │ ──────────────┼────────┼────────┼────────┼──────── │ │ Data - Read │ ✓ │ ✓ │ ✓ │ ✓ │ │ Data - Update │ ✓ │ ✓ │ ✓ │ ✗ │ │ ──────────────┼────────┼────────┼────────┼──────── │ │ Settings │ ✓ │ ✓ │ ✗ │ ✗ │ │ Users │ ✓ │ ✓ │ ✗ │ ✗ │ │ Billing │ ✓ │ ✗ │ ✗ │ ✗ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 5. 에러 처리 ### 5.1 에러 응답 형식 ```json { "error": { "code": "ERROR_CODE", "message": "Human readable message", "details": [ { "field": "name", "message": "Name is required" } ], "request_id": "req_abc123" } } ``` ### 5.2 에러 코드 | HTTP | Code | Description | |------|------|-------------| | 400 | VALIDATION_ERROR | 입력값 검증 실패 | | 400 | CIRCULAR_REFERENCE | 순환 참조 감지 | | 400 | INVALID_FORMULA | 수식 오류 | | 401 | UNAUTHORIZED | 인증 필요 | | 403 | FORBIDDEN | 권한 없음 | | 404 | NOT_FOUND | 리소스 없음 | | 409 | CONFLICT | 충돌 (중복 등) | | 429 | RATE_LIMITED | 요청 한도 초과 | | 500 | INTERNAL_ERROR | 서버 오류 | --- ## 6. OpenAPI 스펙 (발췌) ```yaml openapi: 3.0.3 info: title: PlanitAI KPI API version: 1.0.0 description: AI-powered KPI Management Platform API servers: - url: https://api.planitai-kpi.com/api/v1 description: Production - url: http://localhost:8000/api/v1 description: Development components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT schemas: KPITree: type: object properties: id: type: string example: "tree_001" name: type: string example: "2025年度 営業KPI" fiscal_year: type: string example: "2025" status: type: string enum: [active, archived] root_node_id: type: string nullable: true KPINode: type: object required: - name - node_type - category - unit - unit_label properties: id: type: string name: type: string node_type: type: string enum: [kgi, kpi, metric, input] category: type: string enum: [finance, sales, marketing, hr, product, customer, operation, custom] formula: type: string nullable: true paths: /kpi-trees: get: summary: List KPI trees tags: [KPI Trees] security: - bearerAuth: [] responses: '200': description: Success content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/KPITree' ``` --- ## 7. 결론 ### API 설계 요약 | 리소스 | 엔드포인트 수 | 주요 기능 | |--------|---------------|-----------| | Auth | 4 | 로그인, 토큰 갱신 | | Trees | 7 | CRUD, 복제, 검증 | | Nodes | 6 | CRUD, 이동 | | Data | 4 | 조회, 일괄 업데이트 | | Analysis | 4 | 차이, 병목, 시뮬레이션, AI | | Reports | 4 | 생성, 다운로드 | ### 설계 특징 1. **일관성**: 모든 응답 형식 통일 2. **확장성**: 버전 관리, 페이지네이션 3. **문서화**: OpenAPI 3.0 스펙 4. **보안**: JWT + RBAC --- **다음 편: [v8] 프론트엔드 설계** *React/Next.js 기반 UI 컴포넌트와 KPI 트리 시각화 설계를 다룹니다.*