feat(gemini): 屋根伏図 지붕투영면 외곽선 추출 프롬프트 강화 + media_resolution HIGH

- BASE_PROMPT를 지붕 투영면(屋根伏図 footprint) 기준으로 재작성: 屋根伏図 우선(처마/케라바 포함), 立面図/配置図/求積図/断面図 배제, 도머/개구부 제외
- 寸法線 mm 1순위 축척 규칙, 910/1820mm 모듈 단서, L자/직각 형상 보존 규칙 추가
- generationConfig에 mediaResolution: MEDIA_RESOLUTION_HIGH 추가(미지원 SDK 시 무시), usageMetadata 로깅
- FLOOR_PLAN_SCHEMA 및 응답에 selectedDrawingType 추가

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sangwook yoo 2026-06-01 09:12:13 +09:00
parent 3e37b7448c
commit afda525be9

View File

@ -26,37 +26,58 @@ const FLOOR_PLAN_SCHEMA = {
scale: { type: SchemaType.NUMBER },
confidence: { type: SchemaType.NUMBER },
notes: { type: SchemaType.STRING },
selectedDrawingType: { type: SchemaType.STRING },
},
required: ['outerline', 'unit'],
}
const BASE_PROMPT = `당신은 건축 평면도(주택 도면)의 외곽선 추출 전문가입니다.
첨부된 PDF에서 건물의 외곽(외벽선) 폴리곤 좌표를 추출하여 JSON 으로만 응답하세요.
const BASE_PROMPT = `당신은 일본 주택 건축 도면에서 "지붕 투영면(屋根伏図 footprint)" 외곽선을 추출하는 전문가입니다.
첨부된 PDF 에서 지붕을 위에서 내려다본 외곽(처마 끝선 포함) 폴리곤 좌표를 추출하여 JSON 으로만 응답하세요.
규칙:
1. 모든 좌표는 밀리미터(mm) 단위의 실측 치수로 환산해야 합니다. 도면의 축척(scale) 식별하여 적용하세요.
[도면 종류 식별 매우 중요]
- 추출 대상: 屋根伏図(지붕평면도) 있으면 지붕 외곽선(처마/軒先·케라바/ケラバ 끝선 포함) 사용합니다.
- 屋根伏図 없으면 平面図(1/1 평면도) 외벽 외주에 처마 출폭(軒の出) 더한 지붕 투영 외곽을 추정합니다.
- 절대 사용 금지: 立面図(입면도)·配置図(배치도)·求積図(면적도)·断面図(단면도). 도면들에서는 윤곽을 추출하지 마세요.
- 도머(ドーマー)·천창·개구부(開口部) 지붕 돌출/구멍은 외곽선에 포함하지 마세요(외곽 윤곽이 아닙니다).
- 평면도가 여러 층이면 1(1F) 기준으로 지붕 투영을 판단합니다.
[축척·치수 결정 우선순위]
1. 도면에 적힌 寸法線(치수선) 실제 mm 숫자를 최우선 근거로 좌표를 환산합니다.
2. 다음 縮尺 표기(: 縮尺 1/100, S=1:100) 사용합니다.
3. 마지막으로 通り芯(grid axis) 간격으로 보정합니다.
4. 치수선 숫자와 축척이 충돌하면 치수선 숫자를 신뢰하세요.
5. 일본 주택은 尺貫法 모듈(1=1820mm, 半間=910mm, 455mm, 303mm) 정렬되는 경향이 있습니다 픽셀비례로 추정한 길이를 가장 가까운 모듈값과 대조해 검토하세요(강제 아님, 참고용).
[형상 규칙]
1. 모든 좌표는 밀리미터(mm) 단위의 실측 치수로 환산합니다.
2. 좌표계는 도면의 좌상단을 원점으로 하고, x 우측 양수, y 하단 양수로 합니다.
3. 외곽선은 시계 반대 방향(CCW) 으로 정렬된 정점 배열로 반환합니다.
4. 자기교차가 없는 단순 폴리곤이어야 하며, 정점은 3 이상이어야 합니다.
5. PDF 평면도가 아니거나 외곽을 추출할 없으면 outerline 배열로 반환하고 notes 사유를 적습니다.
6. 응답은 반드시 단일 JSON 객체만 포함하며, 다른 텍스트, 마크다운, 코드펜스를 포함하지 마세요.
7. 좌표는 정수 또는 소수 첫째 자리까지 허용합니다.
5. L자·凹凸(요철) 평면의 모든 꺾임을 정점으로 보존하고, 직각(直角) 가까운 코너는 직각으로 간주합니다(비스듬히 근사 금지).
6. 분리된 복수 () 있으면 가장 건물 하나만 반환하고, 별동은 notes 기재합니다.
7. 지붕 투영을 추출할 없으면 outerline 배열로 반환하고 notes 사유를 적습니다.
[출력]
- 응답은 반드시 단일 JSON 객체만 포함하며, 다른 텍스트, 마크다운, 코드펜스를 포함하지 마세요.
- 좌표는 정수 또는 소수 첫째 자리까지 허용합니다.
- selectedDrawingType 사용한 도면 종류('roof_plan' | 'floor_plan_1f' | 'other') 기재합니다.
스키마:
{
"outerline": [{"x": number, "y": number}, ...],
"unit": "mm",
"scale": number (선택, 도면 1mm 실제 mm),
"scale": number (선택, 축척 분모. 1/100 이면 100),
"confidence": number (0~1),
"notes": string (선택)
"notes": string (선택),
"selectedDrawingType": string (선택)
}`
const buildPrompt = ({ pageMode, facadePages, floorPages } = {}) => {
if (pageMode === 'specify' && (facadePages || floorPages)) {
const hints = []
if (floorPages) hints.push(`평면도(平面図) 페이지: ${floorPages}`)
if (floorPages) hints.push(`평면도(平面図)·지붕평면도(屋根伏図) 페이지: ${floorPages}`)
if (facadePages) hints.push(`입면도(立面図) 페이지: ${facadePages}`)
return `${BASE_PROMPT}\n\n페이지 힌트: ${hints.join(' / ')}. 외곽선은 위에서 지정한 평면도 페이지 기준으로 추출하세요.`
return `${BASE_PROMPT}\n\n페이지 힌트: ${hints.join(' / ')}. 입면도 페이지는 외곽선 추출에 쓰지 말고 높이·층수 참고로만 사용하세요. 지붕 투영 외곽선은 지정된 평면도/지붕평면도 페이지 기준으로 추출하세요.`
}
return BASE_PROMPT
}
@ -157,6 +178,8 @@ export async function POST(req) {
responseMimeType: 'application/json',
responseSchema: FLOOR_PLAN_SCHEMA,
temperature: 0.1,
// Gemini 3 고fidelity 경로 — 얇은 외벽선/작은 寸法 텍스트 가독성 확보(미지원 시 무시됨). 적용 여부는 usageMetadata 토큰 증가로 검증.
mediaResolution: 'MEDIA_RESOLUTION_HIGH',
},
})
@ -174,6 +197,7 @@ export async function POST(req) {
const raw = result.response.text()
logger.debug('[gemini/floor-plan] raw response', raw)
logger.debug('[gemini/floor-plan] usageMetadata', result.response.usageMetadata)
let parsed
try {
@ -193,6 +217,7 @@ export async function POST(req) {
scale: parsed.scale ?? null,
confidence: parsed.confidence ?? null,
notes: parsed.notes ?? null,
selectedDrawingType: parsed.selectedDrawingType ?? null,
empty: validation.empty,
})
} catch (error) {