docs: [planning] 주일기업 기획 메뉴 기술문서 추가
- README.md: 전체 개요, 5개 하위 메뉴 구조, 아키텍처 - construction-photos.md: 공사현장 사진대지 (GCS, 행 구조, 음성입력) - meeting-minutes.md: 회의록 (STT 화자분리, Gemini AI 요약, 오디오 녹음) - planning-views.md: 견적/프로젝트/워크플로우 화면 명세 - INDEX.md: 문서 인덱스에 planning 등록
This commit is contained in:
@@ -145,6 +145,7 @@ docs/
|
||||
| [ai/README.md](features/ai/README.md) | AI 분석 리포트 (Gemini 연동) |
|
||||
| [equipment/README.md](features/equipment/README.md) | 설비관리 (MNG 전용) |
|
||||
| [approvals/README.md](features/approvals/README.md) | 결재관리 시스템 (순차결재, 보류/전결/참조/복사재기안) |
|
||||
| [planning/README.md](features/planning/README.md) | 주일기업 기획 (견적/프로젝트/사진대지/회의록/AI) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
129
sam/docs/features/planning/README.md
Normal file
129
sam/docs/features/planning/README.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 주일기업 기획 메뉴
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 운영 중
|
||||
> **프로젝트**: SAM MNG (관리자 웹)
|
||||
> **라우트 접두사**: `/juil`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
블라인드/스크린 제조업체의 현장 관리를 위한 기획 도구 모음. 견적부터 공사, 준공까지의 업무 흐름과 현장 기록(사진대지), 회의 기록(STT/AI 요약)을 제공한다.
|
||||
|
||||
### 1.2 문서 구조
|
||||
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| **README.md** (이 문서) | 전체 개요, 메뉴 구조, 아키텍처 |
|
||||
| [construction-photos.md](construction-photos.md) | 공사현장 사진대지 기술 명세 |
|
||||
| [meeting-minutes.md](meeting-minutes.md) | 회의록 작성 기술 명세 (STT/AI 통합) |
|
||||
| [planning-views.md](planning-views.md) | 견적/프로젝트/워크플로우 화면 명세 |
|
||||
|
||||
### 1.3 하위 메뉴 구조
|
||||
|
||||
```
|
||||
주일기업 기획
|
||||
├── 견적/입찰/공사관리 /juil/estimate
|
||||
├── 프로젝트관리/기성청구 /juil/project
|
||||
├── 업무 Workflow /juil/workflow
|
||||
├── 공사현장 사진대지 /juil/construction-photos
|
||||
└── 회의록 작성 /juil/meeting-minutes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 아키텍처
|
||||
|
||||
### 2.1 기술 스택
|
||||
|
||||
| 계층 | 기술 | 설명 |
|
||||
|------|------|------|
|
||||
| 뷰 | Blade + React (인라인) + Babel | 브라우저 트랜스파일 React 컴포넌트 |
|
||||
| API | Laravel Controller + Service | JSON API (AJAX) |
|
||||
| 모델 | Eloquent ORM | Multi-tenant (BelongsToTenant) |
|
||||
| 파일 저장 | Google Cloud Storage | 사진, 오디오 파일 |
|
||||
| AI | Gemini API (Vertex AI) | 요약, 화자 분리 |
|
||||
| STT | Google Speech-to-Text V1/V2 + Web Speech API | 음성 인식 |
|
||||
|
||||
### 2.2 프로젝트 파일 구조
|
||||
|
||||
```
|
||||
mng/
|
||||
├── app/Http/Controllers/
|
||||
│ ├── PlanningController.php ← 견적/프로젝트/워크플로우
|
||||
│ ├── ConstructionSitePhotoController.php ← 사진대지 CRUD + 파일 관리
|
||||
│ └── MeetingMinuteController.php ← 회의록 CRUD + AI 기능
|
||||
├── app/Services/
|
||||
│ ├── ConstructionSitePhotoService.php ← 사진대지 비즈니스 로직
|
||||
│ └── MeetingMinuteService.php ← 회의록 + AI 통합 로직
|
||||
├── app/Models/
|
||||
│ ├── ConstructionSitePhoto.php ← 사진대지 모델
|
||||
│ ├── ConstructionSitePhotoRow.php ← 사진 행 모델
|
||||
│ ├── MeetingMinute.php ← 회의록 모델
|
||||
│ └── MeetingMinuteSegment.php ← 회의 세그먼트 모델
|
||||
└── resources/views/juil/
|
||||
├── estimate.blade.php ← 견적/입찰/공사관리
|
||||
├── project.blade.php ← 프로젝트관리/기성청구
|
||||
├── workflow.blade.php ← 업무 Workflow
|
||||
├── construction-photos.blade.php ← 사진대지 SPA
|
||||
└── meeting-minutes.blade.php ← 회의록 SPA
|
||||
```
|
||||
|
||||
### 2.3 기능별 구현 현황
|
||||
|
||||
| 기능 | 구현 방식 | 백엔드 | DB |
|
||||
|------|----------|--------|-----|
|
||||
| 견적/입찰/공사관리 | React 뷰 (목데이터) | PlanningController (뷰 반환만) | 없음 |
|
||||
| 프로젝트관리/기성청구 | React 뷰 (목데이터) | PlanningController (뷰 반환만) | 없음 |
|
||||
| 업무 Workflow | React 뷰 (정적 데이터) | PlanningController (뷰 반환만) | 없음 |
|
||||
| 공사현장 사진대지 | React SPA + API | Controller + Service | 2 테이블 |
|
||||
| 회의록 작성 | React SPA + API | Controller + Service + AI | 2 테이블 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 외부 서비스 의존성
|
||||
|
||||
| 서비스 | 용도 | 추적 |
|
||||
|--------|------|------|
|
||||
| **Google Cloud Storage** | 사진/오디오 파일 저장 | `AiTokenHelper::saveGcsStorageUsage()` |
|
||||
| **Google Speech-to-Text V2 (Chirp2)** | 자동 화자 분리 (최우선) | `AiTokenHelper::saveSttUsage()` |
|
||||
| **Google Speech-to-Text V1** | 화자 분리 (V2 실패 시 폴백) | `AiTokenHelper::saveSttUsage()` |
|
||||
| **Gemini API (Vertex AI)** | 요약 생성 + 화자 재분배 | `AiTokenHelper::saveGeminiUsage()` |
|
||||
| **Web Speech API** | 브라우저 음성 입력 (현장명/설명) | `logSttUsage()` |
|
||||
|
||||
### 3.1 도메인 용어 힌트 (STT 정확도 향상)
|
||||
|
||||
```
|
||||
블라인드, 스크린, 롤스크린, 허니콤, 버티컬,
|
||||
원단, 바텀레일, 헤드레일, 브라켓,
|
||||
주일, 경동, 주일블라인드, 경동블라인드,
|
||||
수주, 발주, 납기, 출하, 재고, 원가, 단가,
|
||||
SAM, ERP, MES
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. HTMX 전체 페이지 로드 규칙
|
||||
|
||||
모든 `/juil/*` 페이지는 React 인라인 컴포넌트를 사용하므로, HTMX 부분 로드 시 스크립트가 실행되지 않는다. 각 컨트롤러 메서드에서 HTMX 요청 감지 시 **HX-Redirect로 전체 페이지 리로드를 강제**한다.
|
||||
|
||||
```php
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('juil.estimate'));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 관련 문서
|
||||
|
||||
- [공사현장 사진대지](construction-photos.md) — GCS 파일 관리, 행 구조, 음성 입력
|
||||
- [회의록 작성](meeting-minutes.md) — STT/화자분리/AI 요약, 오디오 녹음
|
||||
- [견적/프로젝트/워크플로우](planning-views.md) — React 뷰 구성, 업무 프로세스 정의
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
275
sam/docs/features/planning/construction-photos.md
Normal file
275
sam/docs/features/planning/construction-photos.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# 공사현장 사진대지
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 운영 중
|
||||
> **라우트**: `/juil/construction-photos`
|
||||
> **관련**: [README.md](README.md) | [회의록](meeting-minutes.md) | [뷰 화면](planning-views.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
건설/시공 현장의 작업 과정을 **작업전/작업중/작업후** 3단계 사진으로 기록하고 관리하는 기능. Google Cloud Storage에 사진을 저장하며, 음성 입력(Web Speech API)으로 현장명과 설명을 입력할 수 있다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 라우트
|
||||
|
||||
```
|
||||
/juil/construction-photos
|
||||
├── GET / → index (목록 페이지)
|
||||
├── GET /list → list (JSON 목록)
|
||||
├── POST / → store (새 사진대지 등록)
|
||||
├── POST /log-stt-usage → logSttUsage (STT 시간 기록)
|
||||
├── GET /{id} → show (상세 조회)
|
||||
├── PUT /{id} → update (메타데이터 수정)
|
||||
├── DELETE /{id} → destroy (삭제)
|
||||
├── POST /{id}/rows → addRow (행 추가)
|
||||
├── DELETE /{id}/rows/{rowId} → deleteRow (행 삭제)
|
||||
├── POST /{id}/rows/{rowId}/upload → uploadPhoto (사진 업로드)
|
||||
├── DELETE /{id}/rows/{rowId}/photo/{type} → deletePhoto (사진 삭제)
|
||||
└── GET /{id}/rows/{rowId}/download/{type} → downloadPhoto (다운로드)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터베이스
|
||||
|
||||
### 3.1 construction_site_photos (사진대지)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT FK | 테넌트 격리 |
|
||||
| `user_id` | BIGINT FK | 등록자 |
|
||||
| `site_name` | VARCHAR(200) | 현장명 (필수) |
|
||||
| `work_date` | DATE | 작업일자 (필수) |
|
||||
| `description` | TEXT NULL | 설명 |
|
||||
| `deleted_at` | TIMESTAMP NULL | 소프트 삭제 |
|
||||
|
||||
**인덱스**: `tenant_id`, `user_id`, `(tenant_id, work_date)`
|
||||
|
||||
### 3.2 construction_site_photo_rows (사진 행)
|
||||
|
||||
각 사진대지는 1개 이상의 행을 가지며, 각 행에 3개 타입(before/during/after) 사진 저장.
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `construction_site_photo_id` | BIGINT FK | 부모 (cascade delete) |
|
||||
| `sort_order` | INT | 정렬 순서 (0부터) |
|
||||
| `before_photo_path` | VARCHAR(500) NULL | 작업전 GCS 경로 |
|
||||
| `before_photo_gcs_uri` | VARCHAR(500) NULL | 작업전 GCS URI |
|
||||
| `before_photo_size` | INT UNSIGNED NULL | 작업전 파일크기 (bytes) |
|
||||
| `during_photo_path` | VARCHAR(500) NULL | 작업중 GCS 경로 |
|
||||
| `during_photo_gcs_uri` | VARCHAR(500) NULL | 작업중 GCS URI |
|
||||
| `during_photo_size` | INT UNSIGNED NULL | 작업중 파일크기 (bytes) |
|
||||
| `after_photo_path` | VARCHAR(500) NULL | 작업후 GCS 경로 |
|
||||
| `after_photo_gcs_uri` | VARCHAR(500) NULL | 작업후 GCS URI |
|
||||
| `after_photo_size` | INT UNSIGNED NULL | 작업후 파일크기 (bytes) |
|
||||
|
||||
### 3.3 테이블 관계
|
||||
|
||||
```
|
||||
construction_site_photos
|
||||
│ 1:N
|
||||
▼
|
||||
construction_site_photo_rows (sort_order ASC)
|
||||
├── before_photo_* (작업전)
|
||||
├── during_photo_* (작업중)
|
||||
└── after_photo_* (작업후)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API 명세
|
||||
|
||||
### 4.1 목록 조회
|
||||
|
||||
```
|
||||
GET /juil/construction-photos/list
|
||||
```
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 현장명 검색 |
|
||||
| `date_from` | date | 시작일 |
|
||||
| `date_to` | date | 종료일 |
|
||||
| `per_page` | int | 페이지당 건수 |
|
||||
|
||||
### 4.2 생성
|
||||
|
||||
```
|
||||
POST /juil/construction-photos
|
||||
```
|
||||
|
||||
| 필드 | 규칙 | 설명 |
|
||||
|------|------|------|
|
||||
| `site_name` | required, max:200 | 현장명 |
|
||||
| `work_date` | required, date | 작업일자 |
|
||||
| `description` | nullable, max:2000 | 설명 |
|
||||
|
||||
> 생성 시 빈 행 1개 자동 추가
|
||||
|
||||
### 4.3 사진 업로드
|
||||
|
||||
```
|
||||
POST /juil/construction-photos/{id}/rows/{rowId}/upload
|
||||
```
|
||||
|
||||
| 필드 | 규칙 | 설명 |
|
||||
|------|------|------|
|
||||
| `type` | required, in:before,during,after | 사진 타입 |
|
||||
| `photo` | required, image, mimes:jpeg,jpg,png,webp, max:10240 | 최대 10MB |
|
||||
|
||||
### 4.4 사진 다운로드
|
||||
|
||||
```
|
||||
GET /juil/construction-photos/{id}/rows/{rowId}/download/{type}?inline=1
|
||||
```
|
||||
|
||||
| 파라미터 | 설명 |
|
||||
|---------|------|
|
||||
| `inline=1` | 브라우저 표시 (미지정 시 다운로드) |
|
||||
|
||||
---
|
||||
|
||||
## 5. GCS 저장 구조
|
||||
|
||||
### 5.1 경로 패턴
|
||||
|
||||
```
|
||||
construction-site-photos/{tenant_id}/{photo_id}/{row_id}_{timestamp}_{type}.{ext}
|
||||
```
|
||||
|
||||
**예시:**
|
||||
|
||||
```
|
||||
construction-site-photos/1/42/15_1709723456_before.jpg
|
||||
construction-site-photos/1/42/15_1709723456_during.jpg
|
||||
construction-site-photos/1/42/15_1709723456_after.png
|
||||
```
|
||||
|
||||
### 5.2 업로드 흐름
|
||||
|
||||
```
|
||||
클라이언트 (Canvas 이미지 압축: 1920px, quality 80%)
|
||||
↓
|
||||
FormData (multipart) 전송
|
||||
↓
|
||||
컨트롤러: uploadPhoto()
|
||||
↓
|
||||
서비스: uploadPhoto()
|
||||
├── 기존 사진 있으면 GCS에서 삭제
|
||||
├── GCS에 업로드
|
||||
├── DB에 path + uri + size 저장
|
||||
└── AiTokenHelper::saveGcsStorageUsage() 호출
|
||||
↓
|
||||
응답: { success, data: Photo with rows }
|
||||
```
|
||||
|
||||
### 5.3 삭제 흐름
|
||||
|
||||
```
|
||||
사진 삭제: GCS 파일 삭제 → DB 필드 null
|
||||
행 삭제: 행 내 모든 사진 GCS 삭제 → 행 삭제 → sort_order 재정렬
|
||||
사진대지 삭제: 모든 행의 모든 사진 GCS 삭제 → soft delete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 음성 입력 (Web Speech API)
|
||||
|
||||
### 6.1 VoiceInputButton 컴포넌트
|
||||
|
||||
현장명, 설명 필드에 음성으로 텍스트 입력 가능.
|
||||
|
||||
```javascript
|
||||
// Web Speech Recognition 설정
|
||||
recognition.lang = 'ko-KR';
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = true;
|
||||
recognition.maxAlternatives = 1;
|
||||
```
|
||||
|
||||
### 6.2 인식 상태
|
||||
|
||||
| 상태 | 표시 | 설명 |
|
||||
|------|------|------|
|
||||
| interim (미확정) | 이탤릭 + 회색 | 인식 중간 결과, 2초 후 소실 |
|
||||
| final (확정) | 일반체 + 진한색 | 확정 텍스트, 영구 저장 |
|
||||
|
||||
### 6.3 사용량 추적
|
||||
|
||||
```
|
||||
STT 사용 종료 시:
|
||||
duration = Math.max(1, (Date.now() - startTime) / 1000)
|
||||
↓
|
||||
POST /juil/construction-photos/log-stt-usage
|
||||
body: { duration_seconds }
|
||||
↓
|
||||
AiTokenHelper::saveSttUsage('공사현장사진대지-음성입력', seconds)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. UI 구성 (React)
|
||||
|
||||
### 7.1 사진 타입별 색상
|
||||
|
||||
| 타입 | 라벨 | 배경색 | 뱃지색 |
|
||||
|------|------|--------|--------|
|
||||
| `before` | 작업전 | `bg-blue-50` | `bg-blue-100 text-blue-800` |
|
||||
| `during` | 작업중 | `bg-yellow-50` | `bg-yellow-100 text-yellow-800` |
|
||||
| `after` | 작업후 | `bg-green-50` | `bg-green-100 text-green-800` |
|
||||
|
||||
### 7.2 행 관리
|
||||
|
||||
- **행 추가**: sort_order 자동 계산 (마지막 + 1)
|
||||
- **행 삭제**: 최소 1개 행 유지 필수
|
||||
- **행별 사진**: 각 행에 3개 타입 사진 독립 업로드/삭제
|
||||
|
||||
---
|
||||
|
||||
## 8. 모델 메서드
|
||||
|
||||
### 8.1 ConstructionSitePhoto
|
||||
|
||||
```php
|
||||
user() # BelongsTo User (등록자)
|
||||
rows() # HasMany Row (sort_order ASC)
|
||||
getPhotoCount(): int # 전체 사진 개수 (모든 행의 사진 합계)
|
||||
```
|
||||
|
||||
### 8.2 ConstructionSitePhotoRow
|
||||
|
||||
```php
|
||||
constructionSitePhoto() # BelongsTo 부모
|
||||
hasPhoto(string $type): bool # 특정 타입 사진 존재 여부
|
||||
getPhotoCount(): int # 이 행의 사진 개수 (0~3)
|
||||
```
|
||||
|
||||
### 8.3 ConstructionSitePhotoService
|
||||
|
||||
```php
|
||||
getList(array $filters) # 검색/필터 목록 (페이지네이션)
|
||||
create(array $data) # 생성 + 빈 행 1개 자동 추가
|
||||
update(ConstructionSitePhoto, array $data) # 메타데이터만 수정
|
||||
delete(ConstructionSitePhoto) # GCS 전체 삭제 → soft delete
|
||||
uploadPhoto(Row, UploadedFile, string $type) # GCS 업로드 + DB 기록
|
||||
deletePhotoByType(Row, string $type) # 특정 타입 GCS 삭제
|
||||
addRow(ConstructionSitePhoto) # 행 추가 (sort_order 자동)
|
||||
deleteRow(Row) # 행 내 GCS 삭제 → 행 삭제 → 재정렬
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 기획 메뉴 전체 개요
|
||||
- [회의록 작성](meeting-minutes.md) — STT/AI 통합 회의 기록
|
||||
- [견적/프로젝트/워크플로우](planning-views.md) — 화면 명세
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
456
sam/docs/features/planning/meeting-minutes.md
Normal file
456
sam/docs/features/planning/meeting-minutes.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# 회의록 작성
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 운영 중
|
||||
> **라우트**: `/juil/meeting-minutes`
|
||||
> **관련**: [README.md](README.md) | [사진대지](construction-photos.md) | [뷰 화면](planning-views.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
음성으로 회의 내용을 기록하고, **Google STT(화자 분리)** + **Gemini AI(요약/결정사항/액션아이템)** 로 자동 정리하는 회의록 시스템. 브라우저 MediaRecorder로 녹음하고, GCS에 오디오를 저장하며, 세그먼트(화자별 발화)를 관리한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 라우트
|
||||
|
||||
```
|
||||
/juil/meeting-minutes
|
||||
├── GET / → index (목록 페이지)
|
||||
├── GET /list → list (JSON 목록)
|
||||
├── POST / → store (새 회의록 생성)
|
||||
├── POST /log-stt-usage → logSttUsage (STT 시간 기록)
|
||||
├── GET /{id} → show (상세 조회 + segments)
|
||||
├── PUT /{id} → update (메타데이터 수정)
|
||||
├── DELETE /{id} → destroy (삭제)
|
||||
├── POST /{id}/segments → saveSegments (세그먼트 저장)
|
||||
├── POST /{id}/upload-audio → uploadAudio (오디오 업로드)
|
||||
├── POST /{id}/summarize → summarize (AI 요약 생성)
|
||||
├── POST /{id}/diarize → diarize (자동 화자 분리)
|
||||
└── GET /{id}/download-audio → downloadAudio (오디오 다운로드)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터베이스
|
||||
|
||||
### 3.1 meeting_minutes (회의록)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT FK | 테넌트 격리 |
|
||||
| `user_id` | BIGINT FK | 작성자 |
|
||||
| `title` | VARCHAR(300) | 제목 (기본: "무제 회의록") |
|
||||
| `folder` | VARCHAR(100) NULL | 폴더 분류 |
|
||||
| `participants` | JSON NULL | 참여자 목록 배열 |
|
||||
| `meeting_date` | DATE | 회의 날짜 |
|
||||
| `meeting_time` | TIME NULL | 회의 시작 시간 |
|
||||
| `duration_seconds` | INT UNSIGNED | 녹음 총 시간(초) |
|
||||
| `audio_file_path` | VARCHAR(500) NULL | 오디오 GCS 경로 |
|
||||
| `audio_gcs_uri` | VARCHAR(500) NULL | 오디오 GCS URI |
|
||||
| `audio_file_size` | BIGINT UNSIGNED NULL | 오디오 파일 크기 (bytes) |
|
||||
| `full_transcript` | LONGTEXT NULL | 전체 트랜스크립트 |
|
||||
| `summary` | LONGTEXT NULL | AI 요약 |
|
||||
| `decisions` | JSON NULL | 결정사항 배열 |
|
||||
| `action_items` | JSON NULL | 액션아이템 배열 |
|
||||
| `status` | VARCHAR(20) | 상태 (5가지) |
|
||||
| `stt_language` | VARCHAR(10) | STT 언어 (기본: ko-KR) |
|
||||
| `deleted_at` | TIMESTAMP NULL | 소프트 삭제 |
|
||||
|
||||
**인덱스**: `tenant_id`, `user_id`, `(tenant_id, meeting_date)`, `status`
|
||||
|
||||
### 3.2 meeting_minute_segments (세그먼트)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `meeting_minute_id` | BIGINT FK | 회의록 (cascade delete) |
|
||||
| `segment_order` | INT UNSIGNED | 순서 |
|
||||
| `speaker_name` | VARCHAR(100) | 화자 이름 (기본: "화자 1") |
|
||||
| `speaker_label` | VARCHAR(20) NULL | 화자 라벨/번호 |
|
||||
| `text` | TEXT | 발화 텍스트 |
|
||||
| `start_time_ms` | INT UNSIGNED | 시작 시간 (ms, 기본: 0) |
|
||||
| `end_time_ms` | INT UNSIGNED NULL | 종료 시간 (ms) |
|
||||
| `is_manual_speaker` | BOOLEAN | 수동 화자 전환 여부 (기본: true) |
|
||||
|
||||
**인덱스**: `meeting_minute_id`, `(meeting_minute_id, segment_order)`
|
||||
|
||||
### 3.3 테이블 관계
|
||||
|
||||
```
|
||||
meeting_minutes
|
||||
│ 1:N
|
||||
▼
|
||||
meeting_minute_segments (segment_order ASC)
|
||||
├── speaker_name (화자명)
|
||||
├── text (발화 내용)
|
||||
└── start_time_ms / end_time_ms (타임스탬프)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 상태 관리
|
||||
|
||||
### 4.1 상태값
|
||||
|
||||
| 상태 | 코드 | 색상 | 설명 |
|
||||
|------|------|------|------|
|
||||
| 초안 | `DRAFT` | 회색 | 생성 직후, 편집 가능 |
|
||||
| 녹음중 | `RECORDING` | 빨강 | (클라이언트 상태) |
|
||||
| 처리중 | `PROCESSING` | 노랑 | AI 요약/화자분리 처리 중 |
|
||||
| 완료 | `COMPLETED` | 초록 | AI 처리 완료 |
|
||||
| 실패 | `FAILED` | 빨강 | AI 처리 실패 |
|
||||
|
||||
### 4.2 상태 전이
|
||||
|
||||
```
|
||||
DRAFT
|
||||
↓ [오디오 업로드, 세그먼트 추가]
|
||||
DRAFT (계속 편집)
|
||||
↓ [summarize() 호출]
|
||||
PROCESSING
|
||||
↓
|
||||
COMPLETED (성공) 또는 FAILED (실패)
|
||||
|
||||
DRAFT
|
||||
↓ [diarize() 호출 → 화자 분리]
|
||||
DRAFT (세그먼트 갱신, 상태 유지)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 명세
|
||||
|
||||
### 5.1 목록 조회
|
||||
|
||||
```
|
||||
GET /juil/meeting-minutes/list
|
||||
```
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 제목 검색 |
|
||||
| `date_from` | date | 시작일 |
|
||||
| `date_to` | date | 종료일 |
|
||||
| `status` | string | 상태 필터 |
|
||||
| `per_page` | int | 페이지당 건수 |
|
||||
|
||||
### 5.2 생성
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes
|
||||
```
|
||||
|
||||
| 필드 | 규칙 | 설명 |
|
||||
|------|------|------|
|
||||
| `title` | nullable, max:300 | 제목 (미입력 시 "무제 회의록") |
|
||||
| `folder` | nullable, max:100 | 폴더 분류 |
|
||||
| `participants` | nullable, array | 참여자 목록 |
|
||||
| `meeting_date` | required, date | 회의 날짜 |
|
||||
| `meeting_time` | nullable | 회의 시간 |
|
||||
| `stt_language` | nullable, max:10 | STT 언어 (기본: ko-KR) |
|
||||
|
||||
### 5.3 세그먼트 저장
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes/{id}/segments
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"segments": [
|
||||
{
|
||||
"speaker_name": "김과장",
|
||||
"speaker_label": "1",
|
||||
"text": "블라인드 납기일 확인 필요합니다.",
|
||||
"start_time_ms": 0,
|
||||
"end_time_ms": 5000,
|
||||
"is_manual_speaker": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **전처리**: 빈 텍스트 필터링, 언더스코어 노이즈 제거, 다중 공백 정규화
|
||||
> **자동 생성**: `full_transcript` = `[화자명] 발화텍스트\n...` 형식
|
||||
|
||||
### 5.4 오디오 업로드
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes/{id}/upload-audio
|
||||
```
|
||||
|
||||
| 필드 | 규칙 | 설명 |
|
||||
|------|------|------|
|
||||
| `audio` | required, file | webm/mp3 등 |
|
||||
| `duration_seconds` | required, integer, min:1 | 녹음 시간(초) |
|
||||
|
||||
### 5.5 AI 요약 생성
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes/{id}/summarize
|
||||
```
|
||||
|
||||
**요청**: 없음 (서버에서 `full_transcript` 사용)
|
||||
|
||||
**응답 예시:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "AI 요약이 완료되었습니다.",
|
||||
"data": {
|
||||
"summary": "블라인드 납품 일정과 현장 설치 계획을 논의했습니다...",
|
||||
"decisions": [
|
||||
"납품일을 3월 15일로 확정",
|
||||
"현장 실측은 3월 10일 진행"
|
||||
],
|
||||
"action_items": [
|
||||
{
|
||||
"assignee": "김과장",
|
||||
"task": "거래처에 납기 확인 연락",
|
||||
"deadline": "2026-03-08"
|
||||
}
|
||||
],
|
||||
"status": "COMPLETED"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.6 자동 화자 분리
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes/{id}/diarize
|
||||
```
|
||||
|
||||
| 필드 | 설명 | 기본값 |
|
||||
|------|------|--------|
|
||||
| `min_speakers` | 최소 화자 수 | 2 |
|
||||
| `max_speakers` | 최대 화자 수 | 6 |
|
||||
|
||||
**응답:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "자동 화자 분리가 완료되었습니다. (3명 감지)",
|
||||
"data": { /* Meeting with segments */ },
|
||||
"speaker_count": 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. AI 통합 상세
|
||||
|
||||
### 6.1 화자 분리 (Diarization) 3단계 폴백
|
||||
|
||||
```
|
||||
[1단계] Google STT V2 (Chirp2) ← 최우선
|
||||
│ speechToTextWithDiarizationAuto()
|
||||
│ 최신 모델, 높은 정확도
|
||||
│ 도메인 용어 힌트 포함
|
||||
│
|
||||
↓ (실패 시)
|
||||
[2단계] Google STT V1 (latest_long) ← 폴백
|
||||
│ 안정적이지만 약간 덜 정확
|
||||
│
|
||||
↓ (1명만 인식 시)
|
||||
[3단계] Gemini AI 화자 재분배
|
||||
splitSpeakersWithGemini()
|
||||
대화 맥락/호칭/질답 패턴/어투 변화 분석
|
||||
2명 이상으로 재분배
|
||||
```
|
||||
|
||||
### 6.2 요약 생성 (Gemini API)
|
||||
|
||||
```
|
||||
입력: full_transcript (전체 트랜스크립트)
|
||||
↓
|
||||
Gemini API 호출
|
||||
├── 모드 1: Vertex AI (projectId, region, JWT)
|
||||
└── 모드 2: Google AI Studio (API key) ← 폴백
|
||||
│
|
||||
│ Temperature: 0.3 (결정적)
|
||||
│ Max tokens: 4096
|
||||
↓
|
||||
출력 JSON:
|
||||
{
|
||||
"summary": "3-5문장 요약",
|
||||
"decisions": ["결정사항 1", "..."],
|
||||
"action_items": [
|
||||
{ "assignee": "담당자", "task": "할일", "deadline": "기한" }
|
||||
],
|
||||
"keywords": ["키워드1", "..."]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Gemini 화자 재분배
|
||||
|
||||
Google STT가 1명만 인식할 때 Gemini로 대화 맥락 분석:
|
||||
|
||||
```
|
||||
입력: 단일 화자 트랜스크립트 + 예상 화자 수
|
||||
↓
|
||||
Gemini 프롬프트:
|
||||
- 대화 맥락 분석 (호칭, 질답, 어투 변화)
|
||||
- 지정된 수의 화자로 분리
|
||||
↓
|
||||
출력: 화자별 세그먼트 배열
|
||||
→ DB 세그먼트 교체
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 오디오 관리 (GCS)
|
||||
|
||||
### 7.1 GCS 경로 패턴
|
||||
|
||||
```
|
||||
meeting-minutes/{tenant_id}/{meeting_id}/{timestamp}.webm
|
||||
```
|
||||
|
||||
### 7.2 녹음 흐름
|
||||
|
||||
```
|
||||
브라우저 MediaRecorder API
|
||||
├── navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
├── new MediaRecorder(stream)
|
||||
├── recorder.ondataavailable → webm 블롭 수집
|
||||
└── 녹음 종료 → FormData로 업로드
|
||||
↓
|
||||
POST /{id}/upload-audio
|
||||
├── GCS 업로드
|
||||
├── DB: audio_file_path, audio_gcs_uri, audio_file_size, duration_seconds
|
||||
└── AiTokenHelper::saveGcsStorageUsage()
|
||||
```
|
||||
|
||||
### 7.3 다운로드
|
||||
|
||||
```
|
||||
GET /{id}/download-audio
|
||||
→ GCS에서 파일 콘텐츠 다운로드
|
||||
→ Content-Disposition: attachment; filename="{title}.webm"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 세그먼트 처리 로직
|
||||
|
||||
### 8.1 저장 시 전처리
|
||||
|
||||
```php
|
||||
// 1. 빈 텍스트 필터링
|
||||
trim($segment['text']) !== ''
|
||||
|
||||
// 2. 언더스코어 노이즈 제거
|
||||
str_replace('_', '', $text)
|
||||
|
||||
// 3. 다중 공백 정규화
|
||||
preg_replace('/\s{2,}/', ' ', $text)
|
||||
```
|
||||
|
||||
### 8.2 전체 트랜스크립트 자동 생성
|
||||
|
||||
```
|
||||
[김과장] 블라인드 납기일 확인 필요합니다.
|
||||
[박부장] 3월 15일로 확정합시다.
|
||||
[김과장] 네, 거래처에 연락하겠습니다.
|
||||
```
|
||||
|
||||
### 8.3 화자 분리 결과 세그먼트 변환
|
||||
|
||||
```
|
||||
Google STT 결과 → MeetingMinuteSegment 변환:
|
||||
{
|
||||
segment_order: 순서,
|
||||
speaker_name: "화자 N",
|
||||
speaker_label: "N",
|
||||
text: 발화 텍스트,
|
||||
start_time_ms: 시작시간,
|
||||
end_time_ms: 종료시간,
|
||||
is_manual_speaker: false // 자동 분리
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. UI 구성 (React)
|
||||
|
||||
### 9.1 화자 색상
|
||||
|
||||
| 화자 | 배경색 | 뱃지색 |
|
||||
|------|--------|--------|
|
||||
| 화자 1 | `bg-blue-50` | `bg-blue-100 text-blue-800` |
|
||||
| 화자 2 | `bg-green-50` | `bg-green-100 text-green-800` |
|
||||
| 화자 3 | `bg-purple-50` | `bg-purple-100 text-purple-800` |
|
||||
| 화자 4 | `bg-orange-50` | `bg-orange-100 text-orange-800` |
|
||||
|
||||
### 9.2 지원 언어
|
||||
|
||||
| 코드 | 라벨 |
|
||||
|------|------|
|
||||
| `ko-KR` | 한국어 |
|
||||
| `en-US` | English |
|
||||
| `ja-JP` | 日本語 |
|
||||
| `zh-CN` | 中文 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 사용량 추적
|
||||
|
||||
| 추적 항목 | 레이블 | Helper |
|
||||
|----------|--------|--------|
|
||||
| Web Speech API 사용 | `회의록-음성인식` | `AiTokenHelper::saveSttUsage()` |
|
||||
| Google STT V1 화자 분리 | `회의록-화자분리` | `AiTokenHelper::saveSttUsage()` |
|
||||
| Google STT V2 화자 분리 | `회의록-화자분리(Chirp2)` | `AiTokenHelper::saveSttUsage()` |
|
||||
| GCS 오디오 저장 | `회의록-GCS저장` | `AiTokenHelper::saveGcsStorageUsage()` |
|
||||
| Gemini 요약/분리 | `회의록-AI요약` | `AiTokenHelper::saveGeminiUsage()` |
|
||||
|
||||
---
|
||||
|
||||
## 11. 모델 메서드
|
||||
|
||||
### 11.1 MeetingMinute
|
||||
|
||||
```php
|
||||
user() # BelongsTo User
|
||||
segments() # HasMany Segment (segment_order ASC)
|
||||
getFormattedDurationAttribute() # "H:MM:SS" 또는 "MM:SS"
|
||||
```
|
||||
|
||||
**Cast**: `participants`, `decisions`, `action_items` → array, `meeting_date` → date
|
||||
|
||||
### 11.2 MeetingMinuteService
|
||||
|
||||
```php
|
||||
# CRUD
|
||||
getList(array $filters) # 검색/필터 목록
|
||||
create(array $data) # 생성 (DRAFT)
|
||||
update(MeetingMinute, array $data) # 수정
|
||||
delete(MeetingMinute) # GCS 삭제 → soft delete
|
||||
|
||||
# 세그먼트
|
||||
saveSegments(MeetingMinute, array $segments) # 전처리 + 저장 + 트랜스크립트 생성
|
||||
uploadAudio(MeetingMinute, UploadedFile, int $seconds) # GCS 업로드
|
||||
logSttUsage(int $seconds) # STT 사용량 기록
|
||||
|
||||
# AI
|
||||
generateSummary(MeetingMinute) # Gemini 요약 생성
|
||||
processDiarization(MeetingMinute, int $min, int $max) # 3단계 화자 분리
|
||||
splitSpeakersWithGemini(string $text, int $expected) # Gemini 화자 재분배
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 기획 메뉴 전체 개요
|
||||
- [공사현장 사진대지](construction-photos.md) — GCS 파일 관리, 음성 입력
|
||||
- [견적/프로젝트/워크플로우](planning-views.md) — 화면 명세
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
222
sam/docs/features/planning/planning-views.md
Normal file
222
sam/docs/features/planning/planning-views.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# 견적/프로젝트/워크플로우 화면 명세
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 뷰 구현 완료 (목데이터 기반, API 미연동)
|
||||
> **라우트**: `/juil/estimate`, `/juil/project`, `/juil/workflow`
|
||||
> **관련**: [README.md](README.md) | [사진대지](construction-photos.md) | [회의록](meeting-minutes.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
3개 화면 모두 **React 인라인 컴포넌트**(Babel 브라우저 트랜스파일)로 구현. 현재는 정적/목데이터 기반이며, 향후 API 연동 예정. PlanningController에서 뷰만 반환한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 견적/입찰/공사관리 (/juil/estimate)
|
||||
|
||||
### 2.1 개요
|
||||
|
||||
블라인드/스크린 설치 프로젝트의 견적서 작성, 입찰 관리, 공사 진행 현황을 한 화면에서 관리.
|
||||
|
||||
### 2.2 데이터 구조 (initialEstimates)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "string",
|
||||
name: "프로젝트명",
|
||||
client: "고객사명",
|
||||
status: "견적중|입찰|계약|공사중|준공",
|
||||
amount: number, // 금액
|
||||
startDate: "YYYY-MM-DD",
|
||||
endDate: "YYYY-MM-DD",
|
||||
manager: "담당자명",
|
||||
items: [ // 품목 내역
|
||||
{ name: "품목명", quantity: number, unitPrice: number }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 공사관리 정보 (initialConstructionData)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "string",
|
||||
estimateId: "string", // 연결 견적
|
||||
siteName: "현장명",
|
||||
address: "현장 주소",
|
||||
progress: number, // 진행률 (0~100)
|
||||
workers: number, // 투입 인원
|
||||
safetyChecks: [ // 안전점검
|
||||
{ date: "YYYY-MM-DD", result: "합격|불합격", inspector: "점검자" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 상태별 배지 색상
|
||||
|
||||
| 상태 | 색상 |
|
||||
|------|------|
|
||||
| 견적중 | 파랑 |
|
||||
| 입찰 | 보라 |
|
||||
| 계약 | 초록 |
|
||||
| 공사중 | 주황 |
|
||||
| 준공 | 회색 |
|
||||
|
||||
### 2.5 SAM 연계
|
||||
|
||||
- 견적서 작성 시 SAM 견적 시스템(`features/quotes/`) 데이터 활용 가능
|
||||
- 향후 `/juil/estimate` ↔ SAM 견적 API 연동 계획
|
||||
|
||||
---
|
||||
|
||||
## 3. 프로젝트관리/기성청구 (/juil/project)
|
||||
|
||||
### 3.1 개요
|
||||
|
||||
계약된 프로젝트의 현장 관리, 발주/청구/인건비 상태 추적, 기성 청구 관리.
|
||||
|
||||
### 3.2 데이터 구조 (initialProjects)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "string",
|
||||
name: "프로젝트명",
|
||||
client: "발주처",
|
||||
contractAmount: number, // 계약금액
|
||||
status: "진행중|완료|보류",
|
||||
sites: [ // 현장 목록
|
||||
{
|
||||
name: "현장명",
|
||||
address: "주소",
|
||||
progress: number // 진행률
|
||||
}
|
||||
],
|
||||
orders: [ // 발주 내역
|
||||
{
|
||||
vendor: "거래처",
|
||||
amount: number,
|
||||
status: "발주|납품|정산"
|
||||
}
|
||||
],
|
||||
claims: [ // 기성 청구
|
||||
{
|
||||
round: number, // 차수
|
||||
amount: number, // 청구금액
|
||||
claimDate: "YYYY-MM-DD",
|
||||
status: "청구|승인|입금"
|
||||
}
|
||||
],
|
||||
laborCosts: [ // 인건비
|
||||
{
|
||||
month: "YYYY-MM",
|
||||
amount: number,
|
||||
workers: number
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 금액 포맷 함수
|
||||
|
||||
```javascript
|
||||
fmt(amount) // 1,234,567 (쉼표 포맷)
|
||||
fmtBillion(amount) // 12.3억 (억 단위 축약)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 업무 Workflow (/juil/workflow)
|
||||
|
||||
### 4.1 개요
|
||||
|
||||
블라인드/스크린 사업의 전체 업무 프로세스를 단계별로 시각화. 각 프로세스에 담당 부서, 산출물, 서브스텝을 정의.
|
||||
|
||||
### 4.2 프로세스 데이터 구조
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "S1-1", // 프로세스 ID
|
||||
phase: "영업", // Phase 명
|
||||
name: "정보 수집", // 프로세스 이름
|
||||
icon: "icon-name", // 아이콘
|
||||
dept: "영업팀", // 담당 부서
|
||||
color: "#3B82F6", // 테마 색상
|
||||
description: "프로세스 설명",
|
||||
documents: [ // 산출물 목록
|
||||
"현장조사서", "고객요구사항서"
|
||||
],
|
||||
subSteps: [ // 상세 서브스텝
|
||||
{
|
||||
name: "서브스텝명",
|
||||
description: "상세 설명",
|
||||
responsible: "담당자/팀",
|
||||
output: "산출물"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 업무 Phase 목록
|
||||
|
||||
| Phase | ID 범위 | 설명 |
|
||||
|-------|---------|------|
|
||||
| **영업** | S1-1 ~ S1-4 | 정보 수집 → 현장 실측 → 고객 미팅 → 프로젝트 검토 |
|
||||
| **견적서 작성** | S2-1 ~ S2-4 | 물량 산출 → 단가 산정 → 견적가 산출 → 견적서 작성/검토 |
|
||||
| **입찰** | S3-* | 입찰 준비 → 제출 → 결과 확인 |
|
||||
| **계약** | S4-* | 계약 협상 → 계약 체결 |
|
||||
| **공사** | S5-* | 자재 발주 → 시공 → 현장 관리 |
|
||||
| **준공** | S6-* | 검수 → 하자보수 → 준공 정산 |
|
||||
|
||||
### 4.4 SAM 연계 포인트
|
||||
|
||||
```javascript
|
||||
// 견적서 작성 Phase에서 SAM 견적 화면으로 연결
|
||||
{ samLink: '/juil/estimate', label: '견적서 작성 바로가기' }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 공통 특징
|
||||
|
||||
### 5.1 HTMX 전체 페이지 로드
|
||||
|
||||
3개 화면 모두 React 컴포넌트 사용하므로 HTMX 부분 로드 불가:
|
||||
|
||||
```php
|
||||
// PlanningController의 모든 메서드
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('juil.xxx'));
|
||||
}
|
||||
return view('juil.xxx');
|
||||
```
|
||||
|
||||
### 5.2 현재 구현 상태
|
||||
|
||||
| 항목 | 상태 |
|
||||
|------|------|
|
||||
| UI 화면 | 구현 완료 (React 인라인) |
|
||||
| 목데이터 | 블레이드에 하드코딩 |
|
||||
| API 연동 | 미연동 (향후 계획) |
|
||||
| DB 테이블 | 미생성 (향후 계획) |
|
||||
| CRUD 기능 | 뷰 조회만 (생성/수정/삭제 미구현) |
|
||||
|
||||
### 5.3 향후 개발 방향
|
||||
|
||||
1. 견적/프로젝트 DB 테이블 설계 (API 프로젝트)
|
||||
2. API 엔드포인트 구현
|
||||
3. React 컴포넌트 API 연동
|
||||
4. SAM 견적 시스템과 데이터 동기화
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 기획 메뉴 전체 개요
|
||||
- [공사현장 사진대지](construction-photos.md) — GCS 파일 관리, 음성 입력
|
||||
- [회의록 작성](meeting-minutes.md) — STT/AI 통합 회의 기록
|
||||
- [견적 시스템](../quotes/README.md) — SAM 견적 관리 (BOM, 10단계 로직)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
Reference in New Issue
Block a user