전액 설계 견적 시스템
이 Skill은 작업량 데이터를 전문 PDF 견적서로 렌더링하며, 표지, 총액 표, 세부 내역 페이지 등 여러 페이지 출력을 지원합니다.
1. 이 Skill을 사용하는 경우
트리거 조건
다음 시나리오에서는 이 Skill을 사용해야 합니다:
- 사용자가
/报价또는/quote를 입력하는 경우 — PDF 견적서 생성 - 사용자가 멀티차트 또는 Excel의 견적 데이터를 PDF 견적서로 변환하려는 경우
다음 시나리오에서는 이 Skill을 사용하지 말아야 합니다:
- 사용자가 멀티차트 데이터를 편집하는 중인 경우 (lark-base Skill 사용)
- 사용자가 과거 견적을 조회만 하는 경우 (재생성 불필요)
사전 의존성
lark-baseSkill — 飞书 멀티차트 데이터 읽기lark-imSkill — PDF를 飞书 대화에 전송lark-driveSkill — PDF를 飞书 문서에 업로드xlsxSkill — Excel 파일 파싱 (사용자가 Excel 제공 시)- Node.js 런타임 + Playwright (프로젝트 디렉토리에 설치됨)
Step 0: 첫 사용 시 확인
사용자가 Skill을 처음 사용하거나 명령 실행 중 의존성 관련 오류가 발생하는 경우, Agent는 다음 확인 절차를 수행해야 합니다.
1. Node.js 환경 확인
node --version을 실행하고 버전이 >= 18인지 확인합니다. 실패하거나 버전이 낮은 경우:
- 사용자에게 Node.js (>= 18) 설치를 안내합니다: https://nodejs.org
- nvm을 사용한 버전 관리를 권장합니다
2. Playwright 브라우저 확인
프로젝트 디렉토리에서 npx playwright install chromium이 완료되었는지 확인합니다. render.js 실행 시 Executable doesn't exist 오류가 발생하는 경우:
- 프로젝트 디렉토리에서:
npx playwright install chromium실행 - 렌더링 재시도
3. lark-cli 확인
lark-cli --version을 실행합니다. 실패하는 경우 (command not found):
- 사용자에게 lark-cli (飞书 명령줄 도구) 설치를 안내합니다
- 설치 후:
lark-cli auth login실행
4. 飞书 로그인 상태 확인
lark-cli base +table-list --base-token LfjJbLrTHacijesHmIjcDtSpnJf를 실행하여 로그인 상태를 테스트합니다. 인증 오류가 반환되면:
- 사용자에게
lark-cli auth login실행을 안내합니다 - 완료 후 재시도
5. 飞书 템플릿 제공
사용자는 프로젝트 템플릿을 자신의 飞书 공간에 복사해야 합니다:
- 템플릿 링크: https://li1fn1sw90.feishu.cn/base/LfjJbLrTHacijesHmIjcDtSpnJf?from=from_copylink
- 사용자를 링크로 안내 → "이 멀티차트 복사" 클릭 → 자신의 공간에 프로젝트 데이터와 견적 세부 내역 입력
- 완료 후 새 멀티차트 링크를 Agent에 전송
Excel 데이터 소스 처리
사용자가 Excel 파일을 제공하는 경우, 직접 파싱하여 렌더링하지 않습니다. 안내 절차:
- 안내: 먼저 飞书 멀티차트로 가져오는 것을 권장합니다. 필드와 순서가 더 잘 정렬됩니다.
- 사용자가 Excel을 飞书로 가져오도록 안내:
- 飞书 열기 → 새 멀티차트 생성 → 가져오기 → Excel 파일 선택
- 또는 템플릿 멀티차트를 복사하고 Excel 데이터를 붙여넣기
- 사용자가 멀티차트 링크를 제공하면
/报价절차 계속
2. 작업 흐름
사용자 트리거 → 환경 확인 → 데이터 소스 확인 → 데이터 읽기 → 데이터 검증 → 로고 확인 → 세율 확인 → 템플릿 확인 → PDF 렌더링 → 飞书 전송
Step 1: 데이터 소스 확인
사용자는 다음 중 하나를 제공해야 합니다:
- 飞书 멀티차트 링크 — 예:
https://xxx.feishu.cn/base/XXX - Excel 파일 — 로컬 파일 경로 또는 대화를 통해 업로드
사용자가 제공하지 않은 경우, 데이터 소스를 선택하도록 요청합니다.
Step 2: 데이터 읽기
飞书 멀티차트 데이터 소스
- 사용자가 제공한 링크에서
base-token추출 (URL에서/base/다음 부분) - 테이블 나열:
lark-cli base +table-list --base-token <token> - 프로젝트 정보와 견적 세부 내역 두 테이블의 table_id 찾기
- 프로젝트 정보 읽기:
lark-cli base +record-list --base-token <token> --table-id <프로젝트정보_table_id> --format json - 먼저 뷰 조회:
lark-cli base +view-list --base-token <token> --table-id <견적세부내역_table_id>기본 그리드 뷰 (예: "전체 프로젝트")의 view_id 가져오기 - 견적 세부 내역 읽기 (반드시
--view-id를 사용해야 함, 그렇지 않으면 반환 순서가 사용자가 飞书에서 드래그한 순서와 일치하지 않음):lark-cli base +record-list --base-token <token> --table-id <견적세부내역_table_id> --view-id <뷰ID> --format json --limit 200 - 프로젝트 정보에서 추출: 프로젝트 이름, 엔지니어링 번호, 작성 날짜, 작성자, 연락처 이메일, 회사 로고, 세율, 관리비
- 견적 세부 내역에서 추출: 영역, 엔지니어링 분류, 프로젝트 이름, 프로젝트 특성, 단위, 수량, 단가, 합계, 비고 (일련 번호는 읽지 않아도 됨, 렌더링 시 자동 생성)
Excel 데이터 소스
xlsx Skill을 사용하여 Excel을 파싱하고 동일한 구조의 데이터를 추출합니다. Excel에는 여러 시트가 있을 수 있으므로 견적 세부 내역이 포함된 시트를 찾아야 합니다 (일반적으로 "일련 번호", "엔지니어링 분류", "단가" 등 열 헤더가 있음).
Step 3: 데이터 검증
PDF를 렌더링하기 전에 Agent는 읽은 데이터를 검증해야 합니다. 건너뛰지 마세요.
검증 체크리스트 (항목별 실행):
3.1 프로젝트 정보 완전성
프로젝트 이름, 엔지니어링 번호, 작성 날짜 세 필드 확인:
- 하나라도 비어 있음 → 사용자에게 정확히 어떤 필드가 누락되었는지 알리고, 飞书 멀티차트의 '프로젝트 정보' 테이블에서 보완하도록 요청
- 모두 통과 → 계속
3.2 견적 세부 내역 항목 수
items 배열 확인:
- 0이거나 존재하지 않음 → "견적 세부 내역에 데이터가 없습니다. 飞书 멀티차트의 '견적 세부 내역' 테이블에 항목을 추가한 후 다시 시도하세요."
- 데이터 있음 → 계속
3.3 항목별 필드 확인
각 레코드에 대해 필수 필드를 확인하고 (엔지니어링 분류, 프로젝트 이름, 단위, 수량, 단가), 요약 보고:
데이터 검증 결과:
- 총 항목: 104
- 영역 누락: 3 (6, 9, 20번 항목)
- 엔지니어링 분류 누락: 2 (5, 18번 항목)
- 프로젝트 이름 누락: 0
- 단위 누락: 1 (12번 항목)
- 수량 누락: 0
- 단가 누락: 3 (5, 7, 22번 항목)
- 엔지니어링 분류가 표준 목록에 없음: 0
3.4 누락된 필드 처리
- 누락 항목 ≤ 총 항목의 20%: 사용자에게 구체적으로 어떤 항목에 문제가 있는지 알리고 "이 항목들을 건너뛰고 계속 생성하시겠습니까?" 질문
- 누락 항목 > 20%: 사용자에게 데이터를 수정한 후 다시 시도하도록 요청, 렌더링 계속하지 않음
- 사용자가 건너뛰기 확인 → 문제 항목 필터링, 유효한 데이터로 계속
3.7 영역 누락 확인 (영역별 분류 시에만)
사용자가 영역별 분류(Step 5.6에서 "영역별" 선택)를 선택한 경우, 각 레코드의 영역 필드를 추가로 확인:
영역 누락: X (X번 항목)
- 누락 항목 ≤ 총 항목의 20%: 사용자에게 구체적으로 어떤 항목에 영역이 누락되었는지 알리고 "이 항목들을 '기타'로 분류하여 계속 생성하시겠습니까?" 질문
- 누락 항목 > 20%: 사용자에게 영역을 보완한 후 다시 시도하도록 요청
- 사용자 확인 → 영역이 누락된 항목은 "기타" 그룹으로 분류 (Step 3.4의 건너뛰기 논리와 상호 배타적, 사용자는 둘 중 하나 선택: 건너뛰기 또는 기타로 분류)
3.5 합계 자동 수정
각 레코드에 대해 합계가 수량 × 단가와 같은지 확인:
- 차이 < 1원 → 처리하지 않음
- 차이 ≥ 1원 또는 비어 있음 → 자동 계산
합계 = 수량 × 단가, 값을 렌더링 데이터에 기록 - 수정 항목이 10% 초과 → 사용자에게 "X개 항목의 합계 데이터를 자동 수정했습니다" 알리고 멀티차트 수식 확인 권장
3.6 엔지니어링 분류 표준 목록
다음은 13가지 표준 분류입니다:
조치 공사, 철거 공사, 조적 공사, 콘크리트 및 철근 콘크리트 공사,
금속 구조 공사, 방수 공사, 단열 공사, 바닥 마감 공사,
벽/기둥면 마감 및 칸막이 공사, 천장 공사, 페인트/도장 공사,
기타 마감 공사, 설비 공사
목록에 없는 분류도 렌더링할 수 있지만, 사용자에게 "분류 'XXX'가 표준 목록에 없습니다."라고 알립니다.
Step 4: 로고 처리
반드시 실행해야 합니다. 건너뛸 수 없습니다. 데이터 소스가 멀티차트이든 Excel이든 관계없이 로고를 확인하고 처리해야 합니다.
- 멀티차트에서 읽기: 프로젝트 정보 테이블의
회사 로고첨부 파일 필드 확인- 첨부 파일이 있는 경우 media API를 사용하여 다운로드 (
drive +download는 멀티차트 첨부 파일을 지원하지 않음):lark-cli api GET /open-apis/drive/v1/medias/{file_token}/download --output ./logo.png - 이미지를 base64 data URI로 변환하여 렌더링 데이터의
logo_url필드에 전달
- 첨부 파일이 있는 경우 media API를 사용하여 다운로드 (
- Excel에서 읽기: Excel에 로고 이미지가 있는지 확인 (일반적으로 시트에 포함되거나 첨부 파일로 제공됨)
- 있는 경우 추출하여 data URI로 변환
- 로고가 없는 경우 (반드시 사용자에게 질문):
- 데이터 소스에서 회사 로고 이미지를 찾을 수 없는 경우 반드시 사용자에게 질문:
- "데이터 소스에서 회사 로고를 찾을 수 없습니다. 어떻게 처리하시겠습니까?"
- 옵션 A: 로고 이미지 업로드
- 옵션 B: 로고 이미지 URL 제공
- 옵션 C: 로고 사용 안 함 (PDF에 기본 "R M" 텍스트 표시)
- 이 단계를 기본적으로 건너뛰지 마십시오. 사용자가 "바로 생성"이라고 말하더라도 로고 처리 방식을 확인해야 합니다.
- 데이터 소스에서 회사 로고 이미지를 찾을 수 없는 경우 반드시 사용자에게 질문:
Step 5: 세율 및 관리비 확인
우선적으로 멀티차트 프로젝트 정보의 세율, 관리비 필드 값을 사용합니다. 멀티차트에 해당 필드가 없으면 사용자에게 질문합니다.
요율 규칙 (세율 및 관리비 공통):
- 값 > 1 → 백분율 정수로 간주, 자동 ÷100 (예:
3→ 3%,10→ 10%) - 값 ≤ 1 → 소수로 직접 사용 (예:
0.03→ 3%,0.1→ 10%) - 빈 값: 세율 기본 3%, 관리비 기본 0 (부과 안 함)
계산 순서:
합계 = Σ 각 항목 합계
관리비 = 합계 × 관리비율
부가세 = (합계 + 관리비) × 세율
총액 = 합계 + 관리비 + 부가세
관리비 > 0인 경우 총액 표에 '관리비' 행 표시; 관리비 = 0인 경우 표시하지 않음 (이전 데이터와 호환).
Step 5.5: 템플릿 스타일 확인
반드시 질문해야 합니다. 건너뛸 수 없습니다. 이전에 특정 템플릿을 사용했더라도 매번 확인해야 합니다.
사용자에게 템플릿 선택 요청:
견적서 템플릿 스타일을 선택해 주세요:
1. Swiss IKB (기본) — 파란색 배경 전체 표지 + 이중 언어 분류 제목
2. Swiss IKB Zebra — 위와 동일 + 내용 세부 행 얼룩말 무늬 (흰색/연한 파란색 교대)
3. B&W — 흰색 배경 표지 + 연한 회색 강조, 흑백 인쇄에 적합
4. B&W Zebra — 위와 동일 + 내용 세부 행 얼룩말 무늬 (흰색/연한 회색 교대)
사용자 응답에 명확한 템플릿 이름 또는 번호가 포함된 경우 해당 템플릿을 직접 사용합니다:
swiss-ikb— Swiss IKB (기본)swiss-ikb-zebra— Swiss IKB Zebrabw— B&W 흑백 인쇄 버전bw-zebra— B&W Zebra 흑백 인쇄 얼룩말 무늬 버전
개발 중 (현재 외부 제공 안 함): Editorial / Editorial B&W / Card / Card B&W 네 가지 템플릿 코드는 이미 존재하지만, 사용자가 향후 수정/완성할 때까지 활성화되지 않습니다. 사용자가 이러한 템플릿을 요청할 경우
node scripts/render.js --template editorial등 명령을 실행할 수 있지만, 위의 4가지 활성화된 템플릿을 우선 권장합니다.
Step 5.6: 분류 방식 확인
반드시 질문해야 합니다. 건너뛸 수 없습니다. 렌더링할 때마다 확인해야 하며, 이전 옵션을 기본값으로 사용하지 마세요.
사용자에게 분류 방식 선택 요청:
견적서 분류 방식을 선택해 주세요:
1. 영역별 (현관, 거실, 주방…) — 총액 표를 영역별로 요약, 세부 표에 일련 번호 뒤에 엔지니어링 분류 열 추가
2. 엔지니어링 분류별 (조치 공사, 철거 공사…) — 전통적인 방식, 세부 표에 엔지니어링 분류 열 없음
사용자가 명확한 번호 또는 이름으로 응답하면 해당 모드를 사용합니다:
area— 영역별 분류, 렌더링 명령에--group-by area추가category— 엔지니어링 분류별 (기본), 전달하지 않거나--group-by category추가
참고: "영역별"을 선택하는 경우 견적 세부 내역 테이블의
영역필드가 채워져 있는지 확인해야 합니다 (Step 3.7에서 확인). 데이터에 영역이 아직 입력되지 않은 경우 "엔지니어링 분류별"을 우선 사용하는 것이 좋습니다.
Step 5.7: 영역명 영어 번역 (영역별 분류 시에만)
영역명은 프로젝트마다 다릅니다. 렌더링할 때마다 동적으로 번역해야 하며, 하드코딩된 매핑을 사용해서는 안 됩니다.
- 견적 세부 내역에서 이번에 나타난 모든 영역명 수집 (중복 제거)
- Agent가 영역명을 하나씩 영어로 번역 (디자인 업계 관용 번역 사용, 예: 玄关→Foyer, 客厅→Living Room, 阳光房→Sunroom)
- 번역 결과를
region_names객체로 렌더링 데이터 JSON에 기록:
{
"region_names": {
"玄关": "Foyer",
"阳光房": "Sunroom"
}
}
- 렌더링 시 render.js는
region_names의 번역을 우선 사용 (제공되지 않은 영역명은 내장된 일반 영역 매핑으로 폴백, 그 후에도 없으면 AREA 표시) - 렌더링 완료 후 보고서에 이번 영역 번역 대조표를 나열합니다. 사용자가 수정할 수 있으며, 다음 견적 시 다시 번역합니다.
Step 6: PDF 렌더링
프로젝트 디렉토리에서 실행:
node scripts/render.js --input <data.json> --template <템플릿명> --vat-rate <세율> --group-by <area|category> --output ./output/<프로젝트이름>_<엔지니어링번호>.pdf
--group-by area— 영역별 분류 (큰 분류=영역, 세부는 엔지니어링 분류 열 포함)--group-by category또는 전달 안 함 — 엔지니어링 분류별 (기존 동작)
렌더링 데이터 JSON 형식:
{
"프로젝트 이름": "xxx",
"엔지니어링 번호": "xxx",
"작성 날짜": "xxx",
"작성자": "xxx",
"연락처 이메일": "xxx",
"logo_url": "data:image/png;base64,...",
"세율": 0.08,
"관리비": 0.1,
"items": [
{
"영역": "玄关",
"엔지니어링 분류": "조치 공사",
"프로젝트 이름": "비계",
"프로젝트 특성": "실내 비계",
"단위": "항",
"수량": 1,
"단가": 3200,
"합계": 3200
}
]
}
일련 번호 자동 생성 규칙:
일련 번호필드는 데이터에 제공할 필요가 없습니다 (飞书 멀티차트에 해당 열을 만들지 않아도 됨). 렌더링 시그룹 일련 번호.그룹 내 일련 번호형식으로 자동 생성: 영역별 분류 시 01 영역 →1.1, 1.2, …, 02 영역 →2.1, 2.2, …; 엔지니어링 분류별 시 조치 공사 →1.x, 철거 공사 →2.x… 설비 공사 →13.x. 그룹 내 순서 = 飞书 레코드 순서 (멀티차트에서 레코드를 드래그하여 정렬 가능).
세율 및 관리비 (Step 5 참조):
세율/관리비값 > 1은 백분율 정수로 간주 (3 → 3%), ≤ 1은 소수로 간주 (0.08 → 8%)- 관리비 = 합계 × 관리비율; 부가세 = (합계 + 관리비) × 세율; 총액 = 합계 + 관리비 + 부가세
- 관리비가 제공되지 않거나 0인 경우 → 부과 안 함, 총액 표에 관리비 행 표시 안 함
--vat-rate매개변수가 제공되지 않은 경우 render.js는 데이터의세율필드를 우선 읽음
Step 7: 저장 및 전송
PDF 렌더링 완료 후 반드시 사용자에게 저장 방식을 질문해야 합니다:
PDF 생성 완료: <프로젝트이름>_<엔지니어링번호>_<템플릿명>.pdf (XX개 항목, ¥XXX 세금 포함)
어떻게 저장하시겠습니까?
1. 현재 채팅창으로 전송
2. 飞书 클라우드 문서에 저장하고 문서 링크 푸시
3. 둘 다
사용자 선택에 따라 실행:
옵션 1 — 채팅창으로 전송
lark-cli im +messages-send --type media --file <PDF경로>
옵션 2 — 飞书 클라우드 문서에 저장
lark-cli drive +upload --file <PDF경로> --title "<프로젝트이름>_<엔지니어링번호>"
업로드 후 파일 링크를 가져와 사용자에게 응답:
飞书 클라우드 문서에 저장되었습니다: <프로젝트이름>_<엔지니어링번호>_<템플릿명>.pdf
문서 링크: https://xxx.feishu.cn/drive/xxx
옵션 3 — 둘 다
먼저 옵션 2 (업로드)를 실행한 후 옵션 1 (파일 + 링크 전송)을 실행합니다.
임시 파일 정리
전송/저장이 완료되면 렌더링 과정에서 생성된 임시 JSON 파일과 로고 다운로드 파일(있는 경우)을 삭제합니다.
3. 프로젝트 구조
quote-generator-skill/
├── package.json # 프로젝트 설정
├── setup.sh # 원클릭 설치 스크립트
├── SKILL.md # Skill 정의
├── scripts/
│ ├── render.js # HTML → PDF 렌더링 엔진
│ ├── demo.js # 즉시 사용 가능한 데모
│ └── generate-large-test.js # 대용량 테스트 데이터 생성기
├── references/
│ ├── templates/
│ │ ├── content.html # Handlebars PDF 템플릿 (내용 페이지)
│ │ ├── cover-screen.html # Swiss IKB 표지 개별 템플릿
│ │ ├── cover-bw.html # B&W 흑백 인쇄 표지 템플릿
│ │ ├── swiss-ikb.json # Swiss IKB 설정
│ │ ├── swiss-ikb-zebra.json # Swiss IKB Zebra 설정
│ │ ├── bw.json # B&W 흑백 인쇄 설정
│ │ └── bw-zebra.json # B&W Zebra 설정
│ ├── helpers.js # Handlebars 사용자 정의 helper
│ └── bitable-config.json # 멀티차트 필드 설정
├── docs/plans/ # 설계 문서
├── output/ # 생성된 PDF 출력 디렉토리
└── README.md
4. 템플릿 설명
PDF 템플릿에는 세 가지 유형의 페이지가 포함됩니다:
- 표지 — 프로젝트 이름, 번호, 날짜, 작성자, 로고 (오른쪽 위)
- 총액 표 — 엔지니어링 분류별 금액 합계 + 합계/부가세/총액
- 세부 페이지 — 페이지당 약 8개 행 데이터, 분류 제목, 세부 행, 소계 행, 페이지 번호 포함
5. 참고 사항
- 멀티차트의
합계는 수식 필드 (=수량×단가)이며, 읽을 때 이미 계산되어 있습니다. 일련 번호는 飞书에서 유지 관리할 필요가 없습니다: 렌더링 시 자동 생성 (그룹 일련 번호.그룹 내 일련 번호, 그룹 내 레코드 순서). 세부 순서를 조정하려면 飞书 멀티차트에서 레코드를 드래그하여 정렬하면 됩니다.엔지니어링 분류는 단일 선택 필드이며, 13개 옵션이 13가지 엔지니어링 분류에 해당합니다.영역은 자유 텍스트 필드입니다 (飞书에서 단일 선택 필드로 변경 권장: 현관, 거실, 식당, 주방, 침실, 작은방, 욕실, 발코니, 서재, 드레스룸, 복도, 전집 등). 영역별 분류 시에만 사용되며 비워 둘 수 있습니다.- 영역 모드의 그룹 제목은 중영문 대조로 표시됩니다 (예:
Living Room 거실). 영역명은 프로젝트마다 다르므로 Agent는 렌더링할 때마다 Step 5.7에 따라 동적으로 번역하여region_names에 기록합니다. 내장된 일반 영역 매핑은 폴백으로만 사용되며, 일치하지 않으면AREA가 표시됩니다. 단위는 단일 선택 필드이며 사용자 정의 확장을 지원합니다.- 로고는 이미지 (권장)와 텍스트 두 가지 형식을 지원합니다.
- 부가세율은 매번 사용자가 지정하며 고정되지 않습니다.
- 출력 PDF는 A4 크기이며 인쇄에 적합합니다.
6. 일반적인 문제 처리
| 오류 현상 | 원인 | Agent 처리 방식 |
|---|---|---|
command not found: lark-cli | lark-cli 미설치 | lark-cli 설치 안내, Step 0.3 참조 |
lark-cli 인증 오류 반환 | 미로그인 또는 토큰 만료 | lark-cli auth login 실행 안내 |
Executable doesn't exist | Playwright Chromium 미설치 | npx playwright install chromium 실행 |
| 렌더링된 PDF가 비어 있거나 형식이 잘못됨 | 데이터 JSON 형식 오류 | 프로젝트 이름, 엔지니어링 번호가 비어 있지 않은지, items가 1개 이상인지 확인 |
| 세부 일련 번호가 수동으로 입력한 것과 다름 | 일련 번호가 자동 생성으로 변경됨 | 정상 현상: 일련 번호가 그룹별로 자동 재편성됨 (예: 01 전집 → 1.1, 1.2…). 순서 조정은 飞书에서 레코드를 드래그 |
| 글꼴 크기/이상 현상 | 극히 드물게 발생 (글꼴은 WOFF2로 내장되어 외부 CDN에 의존하지 않음) | 프로젝트 references/fonts/ 디렉토리의 글꼴 파일이 완전한지 확인 |
| 로고 다운로드 실패 | 멀티차트 첨부 파일 API 권한 문제 | 사용자 인증 확인 또는 "로고 사용 안 함" 선택 |
URL에서 base-token 추출 불가 | 사용자가 멀티차트 링크를 제공하지 않음 | 사용자에게 飞书 멀티차트 링크 제공 요청 (URL에 /base/ 포함되어야 함) |