@dssp/dcsp 1.0.2 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist-client/bootstrap.js +13 -10
  2. package/dist-client/bootstrap.js.map +1 -1
  3. package/dist-client/components/kpi-2d-lookup-chart.d.ts +50 -0
  4. package/dist-client/components/kpi-2d-lookup-chart.js +263 -0
  5. package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
  6. package/dist-client/components/kpi-boxplot-chart.d.ts +31 -0
  7. package/dist-client/components/kpi-boxplot-chart.js +306 -0
  8. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  9. package/dist-client/components/kpi-lookup-chart.d.ts +58 -0
  10. package/dist-client/components/kpi-lookup-chart.js +464 -0
  11. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  12. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  13. package/dist-client/components/kpi-mini-trend-chart.js +180 -0
  14. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  15. package/dist-client/components/kpi-radar-chart.d.ts +17 -0
  16. package/dist-client/components/kpi-radar-chart.js +259 -0
  17. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  18. package/dist-client/components/kpi-single-boxplot-chart.d.ts +36 -0
  19. package/dist-client/components/kpi-single-boxplot-chart.js +417 -0
  20. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
  21. package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
  22. package/dist-client/components/kpi-step-lookup-chart.js +181 -0
  23. package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
  24. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  25. package/dist-client/components/kpi-trend-chart.js +241 -0
  26. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  27. package/dist-client/google-map/common-google-map.d.ts +35 -0
  28. package/dist-client/google-map/common-google-map.js +349 -0
  29. package/dist-client/google-map/common-google-map.js.map +1 -0
  30. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  31. package/dist-client/google-map/google-map-loader.js +23 -0
  32. package/dist-client/google-map/google-map-loader.js.map +1 -0
  33. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +47 -0
  34. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +393 -0
  35. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
  36. package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
  37. package/dist-client/pages/kpi-admin/kpi-system-guide.js +696 -0
  38. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
  39. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +46 -0
  40. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +945 -0
  41. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  42. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +34 -0
  43. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +238 -0
  44. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  45. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +37 -0
  46. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +689 -0
  47. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  48. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +57 -0
  49. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +866 -0
  50. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  51. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +69 -0
  52. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +790 -0
  53. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -0
  54. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +22 -0
  55. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +344 -0
  56. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
  57. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +21 -0
  58. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +335 -0
  59. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
  60. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +51 -0
  61. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +693 -0
  62. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
  63. package/dist-client/pages/sv-project-complete.d.ts +31 -0
  64. package/dist-client/pages/sv-project-complete.js +364 -0
  65. package/dist-client/pages/sv-project-complete.js.map +1 -0
  66. package/dist-client/pages/sv-project-detail.d.ts +73 -0
  67. package/dist-client/pages/sv-project-detail.js +1387 -0
  68. package/dist-client/pages/sv-project-detail.js.map +1 -0
  69. package/dist-client/route.d.ts +1 -1
  70. package/dist-client/route.js +15 -0
  71. package/dist-client/route.js.map +1 -1
  72. package/dist-client/shared/complete-api.d.ts +45 -0
  73. package/dist-client/shared/complete-api.js +274 -0
  74. package/dist-client/shared/complete-api.js.map +1 -0
  75. package/dist-client/shared/func.d.ts +2 -0
  76. package/dist-client/shared/func.js +22 -0
  77. package/dist-client/shared/func.js.map +1 -0
  78. package/dist-client/shared/integration-fetch.d.ts +35 -0
  79. package/dist-client/shared/integration-fetch.js +53 -0
  80. package/dist-client/shared/integration-fetch.js.map +1 -0
  81. package/dist-client/shared/kpi-project-sync.d.ts +16 -0
  82. package/dist-client/shared/kpi-project-sync.js +129 -0
  83. package/dist-client/shared/kpi-project-sync.js.map +1 -0
  84. package/dist-client/tsconfig.tsbuildinfo +1 -1
  85. package/dist-client/viewparts/menu-tools.d.ts +7 -0
  86. package/dist-client/viewparts/menu-tools.js +168 -21
  87. package/dist-client/viewparts/menu-tools.js.map +1 -1
  88. package/dist-server/index.d.ts +2 -0
  89. package/dist-server/index.js +5 -0
  90. package/dist-server/index.js.map +1 -1
  91. package/dist-server/middlewares/index.d.ts +7 -0
  92. package/dist-server/middlewares/index.js +100 -0
  93. package/dist-server/middlewares/index.js.map +1 -0
  94. package/dist-server/service/index.d.ts +4 -0
  95. package/dist-server/service/index.js +19 -0
  96. package/dist-server/service/index.js.map +1 -0
  97. package/dist-server/service/kpi-metric-value/index.d.ts +4 -0
  98. package/dist-server/service/kpi-metric-value/index.js +8 -0
  99. package/dist-server/service/kpi-metric-value/index.js.map +1 -0
  100. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +198 -0
  101. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +1067 -0
  102. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
  103. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +15 -0
  104. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +72 -0
  105. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
  106. package/dist-server/service/kpi-stat/index.d.ts +4 -0
  107. package/dist-server/service/kpi-stat/index.js +8 -0
  108. package/dist-server/service/kpi-stat/index.js.map +1 -0
  109. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +13 -0
  110. package/dist-server/service/kpi-stat/kpi-stat-query.js +751 -0
  111. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
  112. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
  113. package/dist-server/service/kpi-stat/kpi-stat-types.js +126 -0
  114. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
  115. package/dist-server/service/kpi-value/index.d.ts +3 -0
  116. package/dist-server/service/kpi-value/index.js +7 -0
  117. package/dist-server/service/kpi-value/index.js.map +1 -0
  118. package/dist-server/service/kpi-value/kpi-value-query.d.ts +8 -0
  119. package/dist-server/service/kpi-value/kpi-value-query.js +90 -0
  120. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -0
  121. package/dist-server/tsconfig.tsbuildinfo +1 -1
  122. package/package.json +8 -7
  123. package/python/RUNNER_CONVERSION_GUIDE.md +81 -0
  124. package/python/doc_summary_engine_handoff/.env.example +21 -0
  125. package/python/doc_summary_engine_handoff/HANDOFF.md +266 -0
  126. package/python/doc_summary_engine_handoff/README.md +71 -0
  127. package/python/doc_summary_engine_handoff/__main__.py +159 -0
  128. package/python/doc_summary_engine_handoff/api.py +165 -0
  129. package/python/doc_summary_engine_handoff/extract.py +290 -0
  130. package/python/doc_summary_engine_handoff/requirements.txt +15 -0
  131. package/python/doc_summary_engine_handoff/summarize.py +247 -0
  132. package/python/inspection_ai_engine_handoff/.env.example +22 -0
  133. package/python/inspection_ai_engine_handoff/HANDOFF.md +541 -0
  134. package/python/inspection_ai_engine_handoff/README.md +83 -0
  135. package/python/inspection_ai_engine_handoff/__main__.py +169 -0
  136. package/python/inspection_ai_engine_handoff/activities.py +147 -0
  137. package/python/inspection_ai_engine_handoff/api.py +273 -0
  138. package/python/inspection_ai_engine_handoff/checklist.py +53 -0
  139. package/python/inspection_ai_engine_handoff/cv_handlers.py +46 -0
  140. package/python/inspection_ai_engine_handoff/cv_model_registry.py +252 -0
  141. package/python/inspection_ai_engine_handoff/cv_models/README.md +58 -0
  142. package/python/inspection_ai_engine_handoff/cv_models/_example_frame_alignment/handler.py +39 -0
  143. package/python/inspection_ai_engine_handoff/cv_models/_example_frame_alignment/model.json +22 -0
  144. package/python/inspection_ai_engine_handoff/engine.py +333 -0
  145. package/python/inspection_ai_engine_handoff/requirements.txt +12 -0
  146. package/python/inspection_ai_engine_handoff/router.py +200 -0
  147. package/python/inspection_ai_engine_handoff/shot_catalog.py +79 -0
  148. package/python/inspection_ai_engine_handoff/shot_planner.py +278 -0
  149. package/python/inspection_ai_engine_handoff/vlm.py +335 -0
  150. package/python/spec-matching-0.5.5/README.md +113 -0
  151. package/python/spec-matching-0.5.5/__main__.py +331 -0
  152. package/python/spec-matching-0.5.5/kcs.csv +6028 -0
  153. package/python/spec-matching-0.5.5/kcs_embeddings.meta.json +1 -0
  154. package/python/spec-matching-0.5.5/kcs_embeddings.npy +0 -0
  155. package/python/spec-matching-0.5.5/requirements.txt +3 -0
  156. package/python/spec-matching-0.5.5//342/230/2057 SF-TD4 /341/204/221/341/205/263/341/204/205/341/205/251/341/204/200/341/205/263/341/204/205/341/205/242/341/206/267/341/204/211/341/205/245/341/206/257/341/204/200/341/205/250/341/204/211/341/205/245 /341/204/213/341/205/243/341/206/274/341/204/211/341/205/265/341/206/250_V1.0.hwp +0 -0
  157. package/schema.graphql +1274 -22
  158. package/things-factory.config.js +7 -1
  159. package/translations/en.json +2 -0
  160. package/translations/ja.json +2 -0
  161. package/translations/ko.json +2 -0
@@ -0,0 +1,541 @@
1
+ # 시공감리 AI 엔진 — 인계 문서
2
+
3
+ > 받는 분: 시공감리 플랫폼을 개발하시는 분께.
4
+ > 이 문서 하나로 통합에 필요한 정보 다 들어 있습니다. 질문 있으시면 알려주세요.
5
+
6
+ ---
7
+
8
+ ## 1. 이 엔진이 무엇을 하는가
9
+
10
+ 시공감리 플랫폼이 보낸 **(검측요청서 + 현장 사진)** 을 받아서 검사항목별로 **자동 판정**을 돌려주는 HTTP API 입니다.
11
+
12
+ ```
13
+ [시공감리 플랫폼] [이 엔진]
14
+ 검측요청서 (체크리스트) 받음
15
+
16
+ ├─ POST /plan-shots ──────────▶ ① 항목들을 LLM 이 분석
17
+ │ { checklist: [...] } ② "이 검측은 N장으로 다음과 같이 찍어주세요" 응답
18
+ │ ◀ shot plan + 마크다운
19
+ │ → 앱이 화면에 가이드 표시
20
+
21
+ 감리자가 사진 촬영
22
+
23
+ └─ POST /inspect ─────────────▶ ③ 항목별로 LLM 라우터가 처리방식 결정
24
+ (multipart) - "사진으로 판단 가능" → VLM (GPT-Vision)
25
+ images: [...] - "수치 측정 필요" → CV 모델 레지스트리에서 매칭
26
+ checklist: JSON string ④ 각각 실행 → 항목별 verdict 반환
27
+ ◀ items: [{verdict, reason, ...}]
28
+ → 앱이 결과를 검측 체크리스트 화면에
29
+ "감리자 적합/부적합" 자동 채움
30
+ ```
31
+
32
+ 핵심 특성:
33
+ - **체크리스트가 동적** — 매 요청마다 다른 항목을 받음. 공종 무관 (목공사, 마감, 콘크리트, 방수, 전기, 설비 등).
34
+ - **항목별 처리방식이 동적** — LLM 라우터가 항목 텍스트만 보고 VLM/CV 중 결정.
35
+ - **CV 모델은 외부 레지스트리** — `cv_models.json` 한 파일과 핸들러 함수 정의 만으로 추가/교체.
36
+ - **Stateless** — DB·세션 없음. 결과 저장은 플랫폼 측에서.
37
+
38
+ ---
39
+
40
+ ## 2. 빠른 시작 (10분)
41
+
42
+ ```bash
43
+ # 1) 압축 풀고 들어가서
44
+ cd inspection_ai_engine
45
+
46
+ # 2) 의존성
47
+ pip install -r requirements.txt
48
+
49
+ # 3) .env 확인 — 이미 임시 OpenAI 키가 들어있습니다.
50
+ # (운영 배포 전에 반드시 본인 키로 교체)
51
+ cat .env
52
+
53
+ # 4) 서버 실행
54
+ python api.py
55
+ # → http://localhost:8000
56
+ # → Swagger UI: http://localhost:8000/docs
57
+ ```
58
+
59
+ 검증:
60
+ ```bash
61
+ # 헬스체크
62
+ curl http://localhost:8000/health
63
+
64
+ # 검측요청서(checklist) + 사진으로 동작 확인
65
+ curl -X POST http://localhost:8000/inspect \
66
+ -F "images=@some_photo.jpg" \
67
+ -F 'checklist=[{"item_id":"C-01","text":"청소상태는 양호한가?"}]'
68
+ ```
69
+
70
+ > Swagger UI(`/docs`)에서 각 엔드포인트를 바로 시험해볼 수 있습니다.
71
+
72
+ ---
73
+
74
+ ## 3. 통합 시점에 알아둘 것
75
+
76
+ ### 검측요청서 화면 (스크린샷 기반) 의 통합 지점
77
+
78
+ 기존 검측 체크리스트 UI 에 버튼 2개 + 결과 처리 1곳 추가하시면 됩니다:
79
+
80
+ | 위치 | 동작 | API 호출 |
81
+ |---|---|---|
82
+ | 상단 우측 "사진촬영" 옆 | **📋 AI 촬영 가이드** 버튼 | `POST /plan-shots` (체크리스트 전체) → 응답의 `markdown` 그대로 띄우거나 `shots` 로 커스텀 UI |
83
+ | "사진촬영" 으로 사진들 모은 후 | **🤖 AI 자동 판정** 버튼 | `POST /inspect` (체크리스트 + 사진들) → `items_sorted` 로 각 항목 결과 |
84
+ | `/inspect` 응답 처리 | 결과를 UI 에 반영 | `감리자` 컬럼 라디오 자동 선택 + `조치사항` 텍스트 채우기. `verdict=fail/hold` 면 빨강 강조. |
85
+
86
+ ### 권장 배치 구조 (보안)
87
+
88
+ ```
89
+ [감리자 아이패드 앱] ── HTTPS ──▶ [당신네 플랫폼 백엔드] ── HTTP(내부망) ──▶ [이 엔진]
90
+ │ │
91
+ │ 인증/로깅/결과 DB 저장 │ OpenAI API 호출
92
+ │ ▼
93
+ │ ┌──────────┐
94
+ │ │ OpenAI │
95
+ │ └──────────┘
96
+ ```
97
+
98
+ **OpenAI 키를 아이패드 앱에 넣지 마세요.** 반드시 플랫폼 백엔드에서 이 엔진으로 프록시.
99
+
100
+ ---
101
+
102
+ ## 4. HTTP API 상세
103
+
104
+ 서버 기본 `http://localhost:8000` (Swagger: `/docs`).
105
+
106
+ ### GET /health
107
+
108
+ ```json
109
+ {
110
+ "ok": true,
111
+ "checklist_version": "inspection_engine_v1",
112
+ "server_time": 1716700000.123,
113
+ "n_cv_models": 2
114
+ }
115
+ ```
116
+
117
+ ### GET /cv-models
118
+
119
+ 현재 등록된 CV 모델 레지스트리 조회.
120
+
121
+ ```json
122
+ {
123
+ "schema_version": "1.0",
124
+ "n_models": 2,
125
+ "models": [
126
+ {
127
+ "model_id": "frame_alignment_v0",
128
+ "name": "수직·수평 편차 측정 (예제)",
129
+ "description": "...",
130
+ "applicable_to": ["수직 상태", "수평 상태", ...],
131
+ "required_shots": ["full_front"],
132
+ "output_keys": ["vertical_deviation_deg", "horizontal_deviation_deg", ...],
133
+ "status": "stub"
134
+ }
135
+ ]
136
+ }
137
+ ```
138
+
139
+ `status`:
140
+ - `"stub"` — 메타만 등록돼 있고 실제 핸들러는 미구현. verdict 는 항상 `"pending"`.
141
+ - `"ready"` — 실제 모델 동작 중.
142
+
143
+ ### POST /plan-shots
144
+
145
+ 체크리스트 → 통합 촬영 가이드 생성.
146
+
147
+ **요청 body (JSON):**
148
+ ```json
149
+ {
150
+ "checklist": [
151
+ { "item_id": "WD-FN-01", "text": "최종 마감상태 육안 확인", "category": "목공사 · 공사 완료" },
152
+ { "item_id": "WD-PT-01", "text": "도장마감 상태 확인 (얼룩, 색상 균일성)", "category": "목공사 · 공사 완료" }
153
+ ]
154
+ }
155
+ ```
156
+
157
+ **응답 (200):**
158
+ ```json
159
+ {
160
+ "checklist_version": "inspection_engine_v1",
161
+ "shots": [
162
+ {
163
+ "shot_id": "full_front",
164
+ "title": "전체 풀샷 (정면)",
165
+ "instruction": "1~2m 떨어져 부재 전체가 한 프레임에 들어오도록...",
166
+ "must_show": ["부재 사방", "마감 표면 전체"],
167
+ "priority": 1,
168
+ "covers": ["WD-FN-01", "WD-PT-01"]
169
+ }
170
+ ],
171
+ "coverage": { "WD-FN-01": ["full_front"], "WD-PT-01": ["full_front"] },
172
+ "rationale": "마감 상태와 도장 균일성 모두 풀샷으로 동시 확인 가능...",
173
+ "markdown": "## 촬영 가이드\n\n..."
174
+ }
175
+ ```
176
+
177
+ - `priority`: 1=필수, 2=권장, 3=선택.
178
+ - `coverage`: 각 항목을 커버하는 샷 ID 들. 빈 배열인 항목은 LLM 누락 가능성 — UI 에서 경고 표시 권장.
179
+ - `markdown`: 마크다운 그대로 렌더링해도 됨.
180
+
181
+ ### POST /route (선택)
182
+
183
+ 체크리스트 → 항목별 라우팅 결정만 반환. `/inspect` 가 내부적으로 자동 실행하므로 필수 아님. 미리 보고 싶을 때 사용.
184
+
185
+ **응답:**
186
+ ```json
187
+ {
188
+ "decisions": [
189
+ {
190
+ "item_id": "WD-FN-01",
191
+ "mode": "vlm",
192
+ "model_id": null,
193
+ "rationale": "마감상태 육안 확인은 시각적 정성 판정이므로 VLM 적합.",
194
+ "confidence": 0.91
195
+ },
196
+ {
197
+ "item_id": "GN-VH-01",
198
+ "mode": "cv",
199
+ "model_id": "frame_alignment_v0",
200
+ "rationale": "수직·수평 측정은 정량 수치가 본질. frame_alignment_v0 적합.",
201
+ "confidence": 0.93
202
+ }
203
+ ]
204
+ }
205
+ ```
206
+
207
+ `mode`:
208
+ - `"vlm"` — GPT-Vision 정성 판정.
209
+ - `"cv"` — CV 모델 호출. `model_id` 가 레지스트리의 모델.
210
+ - `"cv_missing"` — CV 측정 필요하나 레지스트리에 적합 모델 없음. verdict 는 `"pending"` 반환.
211
+
212
+ ### POST /inspect
213
+
214
+ 현장 사진 + 체크리스트 → 자동 판정. **multipart/form-data**.
215
+
216
+ | 필드 | 타입 | 설명 |
217
+ |---|---|---|
218
+ | `images` | file (반복) | 1~`INSPECTION_API_MAX_IMAGES` 장 (기본 8장). 파일당 최대 15MB. **JPEG/PNG/WebP/HEIC** (아이폰·아이패드 HEIC 그대로 가능). |
219
+ | `checklist` | text (JSON 문자열) | 검측요청서 배열을 JSON 으로 직렬화 **(필수)**. |
220
+
221
+ **응답 (200):**
222
+
223
+ ```json
224
+ {
225
+ "checklist_version": "inspection_engine_v1",
226
+ "n_images": 3,
227
+ "n_items": 7,
228
+ "registry_summary": [
229
+ { "model_id": "frame_alignment_v0", "name": "...", "status": "stub" }
230
+ ],
231
+ "routing": [ /* /route 와 같은 구조 */ ],
232
+ "summary": { "pass": 3, "fail": 1, "hold": 1, "pending": 2, "error": 0, "total": 7 },
233
+ "items": [
234
+ {
235
+ "item_id": "WD-FN-01",
236
+ "category": "목공사 · 공사 완료",
237
+ "text": "최종 마감상태 육안 확인",
238
+ "routing": "vlm",
239
+ "route_rationale": "정성 판정이므로 VLM 적합.",
240
+ "route_confidence": 0.91,
241
+ "verdict": "pass",
242
+ "confidence": 0.87,
243
+ "reason": "Image #1 전체에서 마감면 균일, 얼룩/스크래치 미확인.",
244
+ "observation": "...",
245
+ "used_images": [1],
246
+ "elapsed_sec": 14.2
247
+ },
248
+ {
249
+ "item_id": "GN-VH-01",
250
+ "category": "일반",
251
+ "text": "주요 부재의 수직·수평 상태 확인 (기울기 측정)",
252
+ "routing": "cv",
253
+ "model_id": "frame_alignment_v0",
254
+ "route_rationale": "수치 측정이 본질.",
255
+ "route_confidence": 0.93,
256
+ "verdict": "pending",
257
+ "reason": "CV 모델 미연결 (담당자 구현 대기).",
258
+ "measurements": { "stub": true, "n_images_received": 3 },
259
+ "used_images": [],
260
+ "overlay_b64": null,
261
+ "elapsed_sec": 0.0
262
+ }
263
+ ],
264
+ "items_sorted": [ /* FAIL → HOLD → PASS → PENDING → ERROR 순 */ ],
265
+ "elapsed_total_sec": 41.0,
266
+ "summary_markdown": "# 시공감리 검사 결과\n\n..."
267
+ }
268
+ ```
269
+
270
+ `elapsed_total_sec`: 검사 전체 소요시간(초). `summary_markdown`: **감리자용 한글 라벨로
271
+ 렌더링된** 결과(적합/부적합/보류 등) — 그대로 화면에 띄워도 됩니다.
272
+
273
+ **verdict (기계값) → 감리자용 한글 라벨 매핑** (UI 에서 이렇게 표시 권장):
274
+
275
+ | `verdict` | 한글 라벨 | UI 처리 |
276
+ |---|---|---|
277
+ | `pass` | 🟢 적합 | 감리자 컬럼 "적합" 자동 선택 |
278
+ | `fail` | 🔴 부적합 | "부적합" + `reason` 을 조치사항에 + 빨강 강조 |
279
+ | `hold` | 🟡 보류 (추가 확인 필요) | 사람이 직접 판단 — 노랑 강조 |
280
+ | `pending` | ⏳ AI 미지원 (수동 확인) | 자동 측정 기능 미연결 → 수동 판단 |
281
+ | `error` | ❌ 분석 오류 | `traceback` 로그 + 사람 판단으로 폴백 |
282
+
283
+ > JSON 의 `verdict`/`routing` 은 **기계값**(영문)으로 고정입니다. 위 한글 라벨은 UI 에서
284
+ > 매핑하거나, `summary_markdown`(이미 한글)을 그대로 사용하세요.
285
+
286
+ **에러 응답:**
287
+ - 400 — 체크리스트 JSON 파싱 실패, 유효성 검증 실패, 이미지 없음/디코딩 실패.
288
+ - 413 — 파일 개수/크기 초과.
289
+ - 500 — OpenAI 클라이언트 초기화 실패 (`OPENAI_API_KEY` 누락 등).
290
+
291
+ ---
292
+
293
+ ## 5. 호출 예제
294
+
295
+ ### JavaScript (브라우저 또는 Node)
296
+
297
+ ```js
298
+ // Step 1. 촬영 가이드
299
+ const checklist = [
300
+ { item_id: "WD-FN-01", text: "최종 마감상태 육안 확인", category: "목공사 · 공사 완료" },
301
+ { item_id: "WD-PT-01", text: "도장마감 상태 확인", category: "목공사 · 공사 완료" }
302
+ ];
303
+
304
+ const planRes = await fetch("http://localhost:8000/plan-shots", {
305
+ method: "POST",
306
+ headers: { "Content-Type": "application/json" },
307
+ body: JSON.stringify({ checklist }),
308
+ });
309
+ const plan = await planRes.json();
310
+ // → plan.shots[*] 로 UI 구성, plan.markdown 으로 통째 렌더링도 OK
311
+
312
+ // Step 2. 검사 실행
313
+ const fd = new FormData();
314
+ for (const file of capturedFiles) fd.append("images", file);
315
+ fd.append("checklist", JSON.stringify(checklist));
316
+
317
+ const inspRes = await fetch("http://localhost:8000/inspect", {
318
+ method: "POST",
319
+ body: fd,
320
+ });
321
+ const result = await inspRes.json();
322
+
323
+ // Step 3. UI 에 반영
324
+ for (const item of result.items_sorted) {
325
+ const row = ui.findRow(item.item_id);
326
+ if (item.verdict === "pass") row.setSupervisorVerdict("적합");
327
+ if (item.verdict === "fail") {
328
+ row.setSupervisorVerdict("부적합");
329
+ row.setActionNotes(item.reason);
330
+ }
331
+ if (item.verdict === "hold") row.highlight("yellow");
332
+ if (item.verdict === "pending") row.markAiUnsupported();
333
+ }
334
+ ```
335
+
336
+ ### Python (서버측 프록시)
337
+
338
+ ```python
339
+ import requests
340
+
341
+ resp = requests.post(
342
+ "http://engine:8000/inspect",
343
+ files=[("images", open(p, "rb")) for p in photo_paths],
344
+ data={"checklist": json.dumps(checklist_items)},
345
+ timeout=300,
346
+ )
347
+ result = resp.json()
348
+ ```
349
+
350
+ ### curl
351
+
352
+ ```bash
353
+ curl -X POST http://localhost:8000/inspect \
354
+ -F "images=@shot1.jpg" -F "images=@shot2.jpg" \
355
+ -F 'checklist=[{"item_id":"WD-FN-01","text":"최종 마감상태 육안 확인"}]'
356
+ ```
357
+
358
+ ---
359
+
360
+ ## 6. CV 모델 추가하는 법 (폴더 방식)
361
+
362
+ CV 모델은 `cv_models/` 폴더 아래 **모델마다 자기 폴더**로 등록합니다. 시스템이 시작할 때
363
+ 이 폴더를 스캔해 레지스트리를 자동 조립합니다 — **공유 JSON 한 개를 다 같이 편집하지 않으므로**
364
+ 충돌·전체 파손 위험이 없습니다. (상세: `cv_models/README.md`)
365
+
366
+ ```
367
+ cv_models/
368
+ ├── README.md
369
+ ├── _example_frame_alignment/ ← 작성 예시 ('_' 로 시작 → 스캔 제외, 비활성)
370
+ │ ├── model.json
371
+ │ └── handler.py
372
+ └── <내모델_v1>/ ← 실제 모델 (폴더만 두면 자동 등록)
373
+ ├── model.json ← 메타데이터
374
+ └── handler.py ← def run(images) -> CVResult
375
+ ```
376
+
377
+ **1) 예시 폴더 복사** — `_example_frame_alignment/` 를 통째로 복사해 **'_' 없는** 이름으로:
378
+ 예) `cv_models/my_alignment_v1/`
379
+
380
+ **2) `model.json` 채우기** (handler 경로는 안 적음 — 자동 연결):
381
+
382
+ ```json
383
+ {
384
+ "model_id": "my_alignment_v1",
385
+ "name": "내가 만든 수직·수평 모델",
386
+ "description": "...",
387
+ "applicable_to": ["수직", "수평", "기울기"],
388
+ "input_spec": { "required_shots": ["full_front"], "min_images": 1, "notes": "정면·수평정렬 필수" },
389
+ "output_schema": {
390
+ "vertical_deviation_deg": { "type": "float", "desc": "..." },
391
+ "horizontal_deviation_deg": { "type": "float", "desc": "..." }
392
+ },
393
+ "verdict_rules": {
394
+ "tolerance": { "vertical_deviation_deg": 1.0, "horizontal_deviation_deg": 1.0 },
395
+ "logic": "두 편차 모두 tolerance 이내면 pass, 어느 하나라도 초과면 fail."
396
+ },
397
+ "status": "ready",
398
+ "version": "1.0.0"
399
+ }
400
+ ```
401
+
402
+ **3) `handler.py` 의 `run()` 구현:**
403
+
404
+ ```python
405
+ from typing import Sequence
406
+ from PIL import Image
407
+ from cv_handlers import CVResult
408
+
409
+ def run(images: Sequence[Image.Image]) -> CVResult:
410
+ # 모델 실행, 측정값 계산
411
+ return CVResult(
412
+ verdict="pass", # pass | fail | hold | pending
413
+ reason="수직 0.3°, 수평 0.2° — 둘 다 허용 1.0° 이내.",
414
+ measurements={"vertical_deviation_deg": 0.3, "horizontal_deviation_deg": 0.2},
415
+ used_images=[1],
416
+ overlay_b64=None, # 검출 시각화 PNG base64 (선택)
417
+ )
418
+ ```
419
+
420
+ **4) 서버 재시작** — 끝. 라우터가 `description`/`applicable_to`/`output_keys` 를 보고 자동 매칭. 체크리스트 코드는 손 안 댐.
421
+
422
+ ### 모델 작성 시 주의
423
+
424
+ - 함수 이름은 반드시 `run`, 시그니처는 `(images: Sequence[PIL.Image]) -> CVResult`. CVResult 정의는 `cv_handlers.py`.
425
+ - `handler.py` 없이 `model.json` 만 두면 자동으로 `stub` 처리(결과 항상 `pending`). 메타만 먼저 등록 후 코드는 나중에 추가 가능.
426
+ - 도면 등 외부 입력이 필요하면 모델 내부(또는 모델 폴더)에서 해결.
427
+ - `verdict_rules.logic` 은 사람용 문서. 실제 판정 로직은 `run()` 안에서.
428
+ - 폴더 하나의 `model.json` 이 깨져도 그 폴더만 건너뛰고 나머지는 정상 동작.
429
+
430
+ ---
431
+
432
+ ## 7. 파일 구성
433
+
434
+ ```
435
+ inspection_ai_engine/
436
+ ├── api.py # HTTP API (FastAPI) — 플랫폼이 호출하는 진입점
437
+ ├── activities.py # ★ 「AI 검측」 액티비티(화면↔함수): generate_shot_guide / run_inspection
438
+ ├── engine.py # 검사 오케스트레이션
439
+ ├── checklist.py # 입력 형식 검증(validate_checklist) + 버전
440
+ ├── shot_planner.py # LLM 통합 샷 플래너 (촬영 가이드)
441
+ ├── shot_catalog.py # 자주 쓰는 샷 카탈로그 (LLM 힌트)
442
+ ├── router.py # LLM 항목별 라우터 (사진판독 | 측정 결정)
443
+ ├── vlm.py # GPT vision 판정 핸들러
444
+ ├── cv_handlers.py # CVResult 정의 (CV 핸들러 공용 출력 타입)
445
+ ├── cv_model_registry.py # 레지스트리 로더 (cv_models/ 폴더 스캔 + 핸들러 동적 import)
446
+ ├── cv_models/ # ★ CV 모델 저장소 (모델마다 폴더 1개, 자동 스캔)
447
+ │ ├── README.md
448
+ │ └── _example_frame_alignment/ # 작성 예시 (비활성)
449
+ ├── requirements.txt
450
+ ├── .env # API 키 (별도 전달 — 패키지에는 .env.example 만 포함)
451
+ ├── .env.example
452
+ ├── .gitignore
453
+ ├── HANDOFF.md # 이 문서
454
+ └── README.md
455
+ ```
456
+ > 참고: 데모 UI(`demo_ui.py`)·비용추적(`cost_tracker.py`)·샘플 체크리스트는 검증/시연용이라
457
+ > 배포 패키지에서 제외했습니다. 엔진은 이들 없이 정상 동작합니다.
458
+
459
+ ---
460
+
461
+ ## 8. 환경변수
462
+
463
+ | 변수 | 기본값 | 의미 |
464
+ |---|---|---|
465
+ | `OPENAI_API_KEY` | (필수) | OpenAI 키. |
466
+ | `OPENAI_MODEL` | `gpt-5.5` | VLM/라우터/플래너 공용. |
467
+ | `REASONING_EFFORT` | `high` | `none\|low\|medium\|high\|xhigh`. |
468
+ | `INSPECTION_API_PORT` | `8000` | HTTP 서버 포트. |
469
+ | `INSPECTION_API_MAX_IMAGES` | `8` | 한 요청당 최대 이미지 수. |
470
+ | `INSPECTION_API_MAX_BYTES` | `15728640` | 파일 한 장 최대 크기 (15MB). |
471
+ | `INSPECTION_API_ALLOWED_ORIGINS` | `*` | CORS 화이트리스트. 운영에선 도메인 명시 권장. |
472
+ | `CV_MODELS_DIR` | `./cv_models` | CV 모델 폴더 경로 (모델별 폴더를 스캔). |
473
+ | `DEMO_UI_PORT` | `7870` | Gradio 데모 포트. |
474
+
475
+ ---
476
+
477
+ ## 9. 성능/비용 메모
478
+
479
+ `/inspect` 의 LLM 호출 (항목 6개·VLM 4개 기준):
480
+
481
+ | 단계 | 호출 수 | 비고 |
482
+ |---|---|---|
483
+ | 항목별 라우팅 | **1** (텍스트, 배치) | 전체 항목을 한 번에 분류 |
484
+ | VLM 판정 | M (vlm 라우팅된 항목, 비전) | **동시 실행**(스레드풀) |
485
+ | CV 판정 | 0 (LLM) | CV 모델 자체 추론 시간만 |
486
+
487
+ - 라우팅은 1회 배치 호출, VLM 판정은 항목들을 **병렬**로 처리 → 전체 시간은 보통
488
+ **30초~1분** (가장 느린 VLM 1건 + 라우팅). reasoning·이미지 크기에 따라 변동.
489
+ - 응답 dict 에 `elapsed_total_sec`(전체 소요시간)와 `cost`(추정 비용) 포함.
490
+ - 타임아웃은 넉넉히 (3분 이상) 잡아두길 권장.
491
+
492
+ **속도·비용 더 줄이기:**
493
+ - `REASONING_EFFORT=medium` 또는 `low` (지연·비용 ↓, 미묘한 판정 정확도는 약간 trade).
494
+ - 라우팅 재사용: `/plan-shots`/`/route` 의 결정을 `/inspect` 에 그대로 넘기면 라우팅을
495
+ 한 번만 — 엔진의 `inspect_all(precomputed_decisions=...)` 로 지원 (API 노출은 필요 시 추가).
496
+
497
+ ---
498
+
499
+ ## 10. 트러블슈팅
500
+
501
+ | 증상 | 원인/해결 |
502
+ |---|---|
503
+ | 500 — `OPENAI_API_KEY` 누락 | `.env` 확인 + 서버 재시작. |
504
+ | 모든 항목이 `vlm` 으로만 가고 `cv` 가 안 골라짐 | 라우터가 `cv_models.json` 의 `applicable_to` / `description` 을 보고 매칭. 등록 키워드를 체크리스트 항목 텍스트에 맞게 확장. |
505
+ | `cv_missing` 이 너무 자주 나옴 | 라우터가 cv 라우팅했는데 모델 없음. `applicable_to` 키워드 확장 또는 새 모델 추가. |
506
+ | `/plan-shots` 의 `coverage` 에 빈 배열 | LLM 이 그 항목을 어느 샷에도 매핑 안 함. UI 에서 경고. 다시 호출하거나 effort 올림. |
507
+ | CORS 에러 | `INSPECTION_API_ALLOWED_ORIGINS` 에 도메인 추가. |
508
+ | 응답이 너무 오래 걸림 | reasoning + 이미지 큼 + high effort + 항목 많음. 타임아웃 5분+. `REASONING_EFFORT` 낮추기. |
509
+ | OpenAI rate limit | 동시 요청 줄이거나 큐 도입. 현재 엔진은 순차 처리. |
510
+
511
+ ---
512
+
513
+ ## 11. 자주 받는 질문
514
+
515
+ **Q: 체크리스트 항목 ID 는 자유로운가요?**
516
+ A: 네. 문자열이면 뭐든 OK. 한 요청 내 중복만 없으면 됩니다.
517
+
518
+ **Q: 항목별로 `routing: "vlm"` 같은 힌트를 우리가 강제로 줄 수 있나요?**
519
+ A: 현재 API 는 라우터에 맡깁니다. override 가 필요하면 `/inspect` 에 `route_overrides: {item_id: "vlm"|"cv"}` 같은 필드 추가하면 됩니다 — 요청 주시면 추가합니다.
520
+
521
+ **Q: 결과 저장은 어디서?**
522
+ A: 엔진은 stateless. 플랫폼 측 DB 에 저장. 같은 검측건의 재검사도 매번 새로 호출.
523
+
524
+ **Q: 사진을 Base64 로 보낼 수 있나요?**
525
+ A: 현재는 multipart/form-data 만. Base64 JSON 도 받게 추가 가능 — 요청 주세요.
526
+
527
+ **Q: 결과 스트리밍 (SSE) 은요?**
528
+ A: 미지원. 1~3분 동안 한 번에 반환. 항목별 부분 결과 스트리밍이 필요하면 `/inspect/stream` SSE 엔드포인트 추가 가능.
529
+
530
+ **Q: 도면을 같이 보낼 수 있나요?**
531
+ A: 현재 API 인터페이스에는 도면 입력이 없습니다. `drawing_match_v0` 같은 도면 비교 CV 모델은 모델 내부에서 사전 등록된 도면을 참조하는 구조. 도면을 요청마다 보내는 API 가 필요하면 `/inspect` 에 `drawings` multipart 필드 추가 가능.
532
+
533
+ **Q: 인증/권한은 어떻게?**
534
+ A: 엔진 자체에는 없음. 앞단 (플랫폼 백엔드 또는 API 게이트웨이) 에서 처리하시면 됩니다. CORS 만 조심.
535
+
536
+ **Q: 한 번에 한 명만 호출 가능한가요?**
537
+ A: FastAPI 는 async 라 여러 요청 동시 수용. 다만 OpenAI 호출 자체는 순차. 부하 늘면 worker 늘리거나 큐.
538
+
539
+ ---
540
+
541
+ 문의: (보내실 분 정보)
@@ -0,0 +1,83 @@
1
+ # 시공감리 AI 엔진
2
+
3
+ 시공감리 플랫폼이 보낸 **검측요청서 + 현장 사진**을 받아 검사항목별로 자동 판정을 돌려주는 HTTP API.
4
+
5
+ ```
6
+ [시공감리 플랫폼] [엔진]
7
+ 검측요청서 받음
8
+
9
+ ├─ POST /plan-shots ────▶ LLM 이 항목들 보고 통합 촬영 가이드 생성
10
+ │ → "이렇게 N장 찍으세요"
11
+
12
+ 감리자가 사진 촬영
13
+
14
+ └─ POST /inspect ───────▶ ① LLM 라우터가 항목별 vlm/cv/cv_missing 결정
15
+ ② VLM 항목: GPT-Vision 정성 판정
16
+ ③ CV 항목: 레지스트리에서 모델 찾아 실행
17
+ → 항목별 verdict (pass/fail/hold/pending/error)
18
+ ```
19
+
20
+ 특징:
21
+ - 체크리스트가 **동적** — 공종 무관
22
+ - 항목별 처리 방식이 **LLM 결정** — 사람이 미리 태깅 안 함
23
+ - CV 모델이 **외부 JSON 레지스트리** — 코드 수정 없이 모델 추가
24
+
25
+ 상세 통합 가이드는 **[HANDOFF.md](HANDOFF.md)** 참고.
26
+
27
+ ---
28
+
29
+ ## 빠른 시작
30
+
31
+ ```bash
32
+ # 1. 의존성
33
+ pip install -r requirements.txt
34
+
35
+ # 2. .env 에 OpenAI 키 (이미 임시 키 들어있음, 운영 전 본인 키로 교체)
36
+
37
+ # 3. API 서버
38
+ python api.py
39
+ # → http://localhost:8000 (Swagger: /docs)
40
+
41
+ # (선택) 데모 UI — 단독 동작 확인용
42
+ python demo_ui.py
43
+ # → http://localhost:7870
44
+ ```
45
+
46
+ 기본 모델 `gpt-5.5` (reasoning). `.env` 의 `OPENAI_MODEL`/`REASONING_EFFORT` 로 변경.
47
+
48
+ ---
49
+
50
+ ## 파일 구성
51
+
52
+ ```
53
+ inspection_ai_engine/
54
+ ├── api.py # HTTP API (FastAPI) — 플랫폼이 호출하는 진입점
55
+ ├── demo_ui.py # Gradio 데모 UI (검증용)
56
+ ├── engine.py # 검사 오케스트레이션
57
+ ├── checklist.py # 디폴트 샘플 + validate_checklist
58
+ ├── shot_planner.py # LLM 통합 샷 플래너
59
+ ├── shot_catalog.py # 자주 쓰는 샷 카탈로그 (LLM 힌트)
60
+ ├── router.py # LLM 항목별 라우터
61
+ ├── vlm.py # GPT vision VQA 핸들러
62
+ ├── cv_handlers.py # CVResult 정의 (CV 핸들러 공용 출력 타입)
63
+ ├── cv_model_registry.py # 레지스트리 로더 (cv_models/ 폴더 스캔)
64
+ ├── cv_models/ # ★ CV 모델 저장소 (모델마다 폴더 1개, 자동 스캔)
65
+ │ ├── README.md # 추가 방법 안내
66
+ │ └── _example_frame_alignment/ # 작성 예시 (비활성)
67
+ ├── requirements.txt
68
+ ├── .env / .env.example
69
+ ├── HANDOFF.md # 통합 가이드 (이거 보세요)
70
+ └── README.md # 이 파일
71
+ ```
72
+
73
+ ---
74
+
75
+ ## CV 모델 추가 (CV 팀)
76
+
77
+ `cv_models/` 아래 **모델마다 폴더 하나**를 두면 자동 등록됩니다 (공유 파일 편집 없음):
78
+
79
+ 1. `cv_models/_example_frame_alignment/` 복사 → **'_' 없는** 이름으로 (예: `cv_models/my_v1/`).
80
+ 2. `model.json` 채우기 + `handler.py` 의 `run(images) -> CVResult` 구현.
81
+ 3. 서버 재시작 — 끝.
82
+
83
+ 자세한 내용은 [cv_models/README.md](cv_models/README.md) 또는 [HANDOFF.md §6](HANDOFF.md) 참고.