Communitygithub.com

pharder56k/quote-generator-skill

Interior design quoting skill: Feishu Base/Excel → Professional PDF Quotation. MIT.

What is quote-generator-skill?

This AI agent skill converts construction bill of quantities data from Feishu Multidimensional Tables or Excel files into professional PDF quotations. It supports multi-page output including a cover page, summary table, and detailed itemized pages. The skill automates data validation, logo handling, tax rate and management fee confirmation, template selection, and grouping by area or engineering category. It integrates with Feishu CLI tools to read data from Base tables, render PDFs with Playwright, and send the final document to chat or save to Feishu Drive. It is designed for interior design professionals who need to quickly generate accurate, formatted quotations from project data.

Works with✓Claude Code~Codex CLI~Cursor
npx skills add pharder56k/quote-generator-skill

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Full Design Quotation System

This skill renders bill of quantities data into professional PDF quotations, supporting multi-page output with cover, total price table, and detailed pages.

1. When to Use This Skill

Trigger Conditions

Use this skill in the following scenarios:

  • User enters /quote - Generate PDF quotation
  • User mentions converting quotation data from a multidimensional table or Excel into a PDF quotation

Do not use this skill in the following scenarios:

  • User is simply editing multidimensional table data (use lark-base skill instead)
  • User is just viewing quotation history (no regeneration needed)

Prerequisites

  • lark-base skill - Read Feishu multidimensional table data
  • lark-im skill - Send PDF to Feishu chat
  • lark-drive skill - Upload PDF to Feishu Docs
  • xlsx skill - Parse Excel files (when user provides Excel)
  • Node.js runtime + Playwright (already installed in the project directory)

Step 0: First-Time Use Check

When the user uses the Skill for the first time or encounters dependency-related errors when executing commands, the Agent should perform the following check process.

1. Check Node.js Environment

Try executing node --version, require >= 18. If it fails or the version is too low:

  • Prompt the user to install Node.js (>= 18): https://nodejs.org
  • Suggest using nvm to manage versions

2. Check Playwright Browser

Check if npx playwright install chromium has been completed in the project directory. If render.js throws an Executable doesn't exist error:

  • Run in the project directory: npx playwright install chromium
  • Retry rendering

3. Check lark-cli

Try executing lark-cli --version. If it fails (command not found):

  • Prompt the user to install lark-cli (Feishu command-line tool)
  • After installation, run: lark-cli auth login

4. Check Feishu Login Status

Try executing lark-cli base +table-list --base-token LfjJbLrTHacijesHmIjcDtSpnJf to test login status. If an authentication error is returned:

  • Prompt the user to run: lark-cli auth login
  • Retry after completion

5. Provide Feishu Template

The user needs to copy the project template to their own Feishu space:

Excel Data Source Processing

When the user provides an Excel file, do not directly parse and render. Guide the process:

  1. Prompt: Recommend first importing to Feishu multidimensional table for better field and order alignment
  2. Guide the user to import Excel into Feishu:
    • Open Feishu → New multidimensional table → Import → Select Excel file
    • Or directly copy the template multidimensional table and paste Excel data into it
  3. After the user provides the multidimensional table link, continue the /quote process

2. Workflow

User triggers → Environment check → Confirm data source → Read data → Data validation → Confirm logo → Confirm tax rate → Confirm template → Render PDF → Send to Feishu

Step 1: Confirm Data Source

The user must provide one of the following:

  1. Feishu multidimensional table link - e.g., https://xxx.feishu.cn/base/XXX
  2. Excel file - Local file path or uploaded via chat

If the user has not provided one, ask the user to choose a data source.

Step 2: Read Data

Feishu Multidimensional Table Data Source

  1. Extract the base-token from the user-provided link (the part after /base/ in the URL)
  2. List tables: lark-cli base +table-list --base-token <token>
  3. Find the table IDs for Project Information and Quotation Details
  4. Read project information: lark-cli base +record-list --base-token <token> --table-id <Project Information table_id> --format json
  5. First, query views: lark-cli base +view-list --base-token <token> --table-id <Quotation Details table_id>, get the view_id of the default grid view (e.g., "All Items")
  6. Read quotation details (must include --view-id, otherwise the return order will not match the user's drag-and-drop order in the Feishu interface): lark-cli base +record-list --base-token <token> --table-id <Quotation Details table_id> --view-id <View ID> --format json --limit 200
  7. Extract from project information: Project Name, Engineering Number, Preparation Date, Prepared By, Contact Email, Company Logo, Tax Rate, Management Fee
  8. Extract from quotation details: Area, Engineering Category, Item Name, Item Description, Unit, Quantity, Unit Price, Total Price, Remarks (Serial number does not need to be read; it is automatically generated during rendering)

Excel Data Source

Use the xlsx skill to parse the Excel file and extract data with the same structure. Excel may have multiple sheets; find the sheet containing quotation details (usually with column headers like "Serial No.", "Engineering Category", "Unit Price", etc.).

Step 3: Data Validation

Before rendering the PDF, the Agent must validate the read data. Do not skip directly.

Validation Checklist (execute item by item):

3.1 Project Information Completeness

Check the three fields: Project Name, Engineering Number, Preparation Date:

  • Any is empty → Inform the user which specific field is missing and ask them to fill it in the "Project Information" table in Feishu
  • All pass → Continue

3.2 Number of Quotation Detail Items

Check the items array:

  • Length is 0 or does not exist → "There is no data in the quotation details. Please add items in the 'Quotation Details' table in Feishu and try again."
  • Has data → Continue

3.3 Item-by-Item Field Check

Check required fields (Engineering Category, Item Name, Unit, Quantity, Unit Price) for each record, and generate a summary report:

Data Validation Results:
- Total items: 104
- Missing Area: 3 (Items 6, 9, 20)
- Missing Engineering Category: 2 (Items 5, 18)
- Missing Item Name: 0
- Missing Unit: 1 (Item 12)
- Missing Quantity: 0
- Missing Unit Price: 3 (Items 5, 7, 22)
- Engineering Category not in standard list: 0

3.4 Handling Missing Fields

  • Missing items ≤ 20% of total items: Inform the user which specific items have issues and ask "Do you want to skip these items and continue generation?"
  • Missing items > 20%: Ask the user to correct the data and retry; do not continue rendering
  • User confirms to skip → Filter out the problematic items and continue with valid data

3.7 Missing Area Check (only when grouping by area)

When the user selects to group by area (Step 5.6 selects "By Area"), additionally check the Area field for each record:

Missing Area: X (Item X)
  • Missing items ≤ 20% of total items: Inform the user which specific items lack an area and ask "Do you want to categorize these items under 'Other' and continue generation?"
  • Missing items > 20%: Ask the user to fill in the areas and retry
  • User confirms → Missing area items are categorized under "Other" (mutually exclusive with Step 3.4 skip logic; user chooses one: skip or categorize as Other)

3.5 Automatic Total Price Correction

For each record, check if Total Price equals Quantity × Unit Price:

  • Deviation < 1 yuan → No action
  • Deviation ≥ 1 yuan or empty → Automatically calculate Total Price = Quantity × Unit Price, write the value into rendering data
  • Corrected items exceed 10% → Prompt the user "X items' total price have been automatically corrected," suggest checking the multidimensional table formulas

3.6 Standard Engineering Category List

The following are the 13 standard categories:

Measure Project, Demolition Works, Masonry Works, Concrete & Reinforced Concrete Works,
Metal Structure Works, Waterproofing Works, Thermal Insulation Works, Floor & Ground Finishing Works,
Wall & Column Finishing & Partition Works, Ceiling Works, Paint & Coating Works,
Other Decorative Works, Installation Works

Categories not in the list can still be rendered, but inform the user: "Category 'XXX' is not in the standard list."

Step 4: Logo Processing

Must be executed, cannot be skipped. Regardless of whether the data source is a multidimensional table or Excel, the logo must be checked and processed.

  1. Read from Multidimensional Table: Check the Company Logo attachment field in the Project Information table
    • If there is an attachment, download using the media API (drive +download does not support multidimensional table attachments):
      lark-cli api GET /open-apis/drive/v1/medias/{file_token}/download --output ./logo.png
      
    • Convert the image to a base64 data URI and pass it to the logo_url field in the rendering data
  2. Read from Excel: Check if the Excel file contains a logo image (usually embedded in a sheet or as an attachment)
    • If yes, extract and convert to data URI
  3. When Logo is Missing (must ask the user):
    • If no logo image is found in the multidimensional table or Excel, must ask the user:
      • "No company logo found in the data source. How would you like to proceed?"
      • Option A: Upload a logo image
      • Option B: Provide a logo image URL
      • Option C: Do not use a logo (display a default "R M" text identifier in the PDF)
    • Do not skip this step by default. Even if the user says "just generate," confirm the logo handling method.

Step 5: Confirm Tax Rate and Management Fee

Prioritize using the Tax Rate and Management Fee field values in the multidimensional table's project information. If these fields do not exist in the multidimensional table, ask the user.

Rate Rules (common for Tax Rate and Management Fee):

  • Value > 1 → Treated as a percentage integer, automatically ÷100 (e.g., 3 → 3%, 10 → 10%)
  • Value ≤ 1 → Treated as a decimal and used directly (e.g., 0.03 → 3%, 0.1 → 10%)
  • Empty value: Tax rate defaults to 3%, Management fee defaults to 0 (not charged)

Calculation Order:

Subtotal = Σ Total Price of each item
Management Fee = Subtotal × Management Fee Rate
VAT = (Subtotal + Management Fee) × Tax Rate
Grand Total = Subtotal + Management Fee + VAT

When Management Fee > 0, the total price table shows the "Management Fee" row; when Management Fee = 0, it is not displayed (compatible with old data).

Step 5.5: Confirm Template Style

Must ask, cannot be skipped. Even if a template was used before, confirm it every time.

Ask the user to choose a template:

Please select the quotation template style:
1. Swiss IKB (Default) - Blue full-bleed cover + bilingual category titles
2. Swiss IKB Zebra - Same as above + zebra striping on detail rows (white/light blue alternating)
3. B&W - White cover + light gray accents, suitable for black & white printing
4. B&W Zebra - Same as above + zebra striping on detail rows (white/light gray alternating)

If the user's reply includes a clear template name or number, use the corresponding template:

  • swiss-ikb - Swiss IKB (Default)
  • swiss-ikb-zebra - Swiss IKB Zebra
  • bw - B&W Black & White Print Version
  • bw-zebra - B&W Zebra Black & White Print Zebra Version

Under Development (not yet publicly available): The code for the Editorial / Editorial B&W / Card / Card B&W templates already exists, pending user modification and improvement before activation. If a user actively requests these templates, commands like node scripts/render.js --template editorial can be executed, but priority should be given to recommending the 4 live templates above.

Step 5.6: Confirm Grouping Method

Must ask, cannot be skipped. Confirm before each render; do not default to the last option.

Ask the user to choose the grouping method:

Please select the quotation grouping method:
1. By Area (Foyer, Living Room, Master Bedroom…) - Summary table grouped by area, detail table adds an Engineering Category column after the serial number
2. By Engineering Category (Measure Project, Demolition Works…) - Traditional method, detail table does not include an Engineering Category column

After the user replies with a clear number or name, use the corresponding mode:

  • area - Group by area, add --group-by area to the render command
  • category - Group by engineering category (default), do not pass or add --group-by category

Note: When selecting "By Area," ensure the Area field in the quotation detail table is filled in (checked in Step 3.7). If the data does not have area information yet, it is recommended to use "By Engineering Category" first.

Step 5.7: Area Name English Translation (only when grouping by area)

Area names vary by project. Must be dynamically translated before each render; hardcoded mappings are strictly prohibited.

  1. Collect all area names appearing in this quotation (deduplicated)
  2. The Agent translates each area name into English (conventional design industry translations, e.g., 玄关→Foyer, 客厅→Living Room, 阳光房→Sunroom)
  3. Write the translation results as a region_names object into the rendering data JSON:
{
  "region_names": {
    "玄关": "Foyer",
    "阳光房": "Sunroom"
  }
}
  1. During rendering, render.js prioritizes the translations in region_names (unprovided area names fall back to a built-in common area mapping, and finally display AREA)
  2. After rendering, list the area translation mapping in the report. The user can correct it; translations will be redone for the next quotation.

Step 6: Render PDF

Execute in the project directory:

node scripts/render.js --input <data.json> --template <Template Name> --vat-rate <Tax Rate> --group-by <area|category> --output ./output/<Project Name>_<Engineering Number>.pdf
  • --group-by area - Group by area (major category = area, details include Engineering Category column)
  • --group-by category or not passed - Group by engineering category (original behavior)

Rendering data JSON format:

{
  "项目名称": "xxx",
  "工程编号": "xxx",
  "编制日期": "xxx",
  "编制人员": "xxx",
  "联系邮箱": "xxx",
  "logo_url": "data:image/png;base64,...",
  "税率": 0.08,
  "管理费": 0.1,
  "items": [
    {
      "区域": "玄关",
      "工程分类": "措施项目",
      "项目名称": "脚手架",
      "项目特征": "室内脚手架",
      "单位": "项",
      "数量": 1,
      "综合单价": 3200,
      "合价": 3200
    }
  ]
}

Automatic Serial Number Generation Rules: The 序号 field does not need to be provided in the data (the column can be omitted in the Feishu multidimensional table). During rendering, it is automatically generated using Group Serial Number.Item Serial Number within Group: When grouping by area, Area 01 → 1.1, 1.2, …, Area 02 → 2.1, 2.2, …; When grouping by engineering category, Measure Project → 1.x, Demolition Works → 2.x… Installation Works → 13.x. Item order within a group = Feishu record order (records can be sorted by dragging in the multidimensional table).

Tax Rate & Management Fee (see Step 5 for details):

  • Values for 税率 / 管理费 > 1 are treated as percentage integers (3 → 3%), ≤ 1 are treated as decimals (0.08 → 8%)
  • Management Fee = Subtotal × Management Fee Rate; VAT = (Subtotal + Management Fee) × Tax Rate; Grand Total = Subtotal + Management Fee + VAT
  • Management Fee not provided or 0 → not charged, Management Fee row not shown in total price table
  • If the --vat-rate parameter is not provided, render.js first reads the 税率 field from the data

Step 7: Save and Send

After PDF rendering is complete, must ask the user to choose a save method:

PDF generated: <Project Name>_<Engineering Number>_<Template Name>.pdf (XX items, ¥XXX including tax)

How would you like to save it?
1. Send to current chat window
2. Save to Feishu Cloud Docs, push document link
3. Both

Execute based on the user's choice:

Option 1 - Send to Chat Window

lark-cli im +messages-send --type media --file <PDF Path>

Option 2 - Save to Feishu Cloud Docs

lark-cli drive +upload --file <PDF Path> --title "<Project Name>_<Engineering Number>"

Get the uploaded file link and reply to the user:

Saved to Feishu Cloud Docs: <Project Name>_<Engineering Number>_<Template Name>.pdf
Document Link: https://xxx.feishu.cn/drive/xxx

Option 3 - Both

First execute Option 2 (upload), then execute Option 1 (send file + link).

Clean up temporary files

After sending/saving is complete, delete temporary JSON files and logo download files (if any) generated during the rendering process.

3. Project Structure

quote-generator-skill/
├── package.json                # Project configuration
├── setup.sh                     # One-click installation script
├── SKILL.md                     # Skill definition
├── scripts/
│   ├── render.js               # HTML → PDF rendering engine
│   ├── demo.js                 # Out-of-the-box demo
│   └── generate-large-test.js  # Large test data generator
├── references/
│   ├── templates/
│   │   ├── content.html        # Handlebars PDF template (content pages)
│   │   ├── cover-screen.html   # Swiss IKB cover standalone template
│   │   ├── cover-bw.html       # B&W Black & White print cover template
│   │   ├── swiss-ikb.json      # Swiss IKB configuration
│   │   ├── swiss-ikb-zebra.json # Swiss IKB Zebra configuration
│   │   ├── bw.json             # B&W Black & White print configuration
│   │   └── bw-zebra.json       # B&W Zebra configuration
│   ├── helpers.js              # Handlebars custom helpers
│   └── bitable-config.json     # Multidimensional table field configuration
├── docs/plans/                 # Design documents
├── output/                     # Generated PDF output directory
└── README.md

4. Template Description

PDF templates contain three types of pages:

  1. Cover Page - Project name, number, date, prepared by, logo (top right)
  2. Summary Table - Amounts summarized by engineering category + Subtotal/VAT/Grand Total
  3. Detail Pages - Approximately 8 rows per page, including category titles, detail rows, subtotal rows, and page numbers

5. Notes

  • The Total Price in the multidimensional table is a formula field (=Quantity × Unit Price) and is already calculated when read
  • Serial Number does not need to be maintained in Feishu: automatically generated during rendering (Group Serial Number.Item Serial Number within Group, item order within group follows record order). To adjust the detail order, drag and drop records in the Feishu multidimensional table
  • Engineering Category is a single-select field, with 13 options corresponding to 13 engineering categories
  • Area is a free text field (it is recommended to change it to single-select in Feishu: Foyer, Living Room, Dining Room, Kitchen, Master Bedroom, Secondary Bedroom, Bathroom, Balcony, Study, Walk-in Closet, Corridor, Whole House, etc.), only used when grouping by area, can be empty
  • Group titles in area mode display Chinese-English comparison (e.g., Living Room 客厅). Area names vary by project. The Agent dynamically translates them before each render per Step 5.7 and writes them into region_names; a built-in common area mapping serves only as a fallback; AREA is displayed if unmatched
  • Unit is a single-select field, supports custom extensions
  • Logo supports both image (recommended) and text formats
  • VAT rate is specified by the user each time, not fixed
  • Output PDF is A4 size, suitable for printing

6. Common Problem Handling

Error PhenomenonCauseAgent Handling Method
command not found: lark-clilark-cli not installedPrompt to install lark-cli, refer to Step 0.3
lark-cli returns authentication errorNot logged in or token expiredPrompt to run lark-cli auth login
Executable doesn't existPlaywright Chromium not installedRun npx playwright install chromium
Rendered PDF is empty or has formatting issuesData JSON format incorrectCheck that Project Name, Engineering Number are non-empty, and items has at least 1 item
Detail serial numbers differ from manually entered onesSerial numbers have been changed to auto-generationNormal behavior: serial numbers are automatically renumbered by group (e.g., 01 Whole House → 1.1, 1.2…). To adjust order, drag records in Feishu
Font size/font appearance abnormalExtremely rare (fonts are embedded as WOFF2, do not rely on external CDN)Check that font files in the project references/fonts/ directory are complete
Logo download failsMultidimensional table attachment API permission issueConfirm user has authorized, or choose "Do not use Logo"
base-token cannot be extracted from URLUser did not provide a multidimensional table linkPrompt the user to provide a Feishu multidimensional table link (URL should contain /base/)

Related Skills