토론

메뉴 식자재 원가 및 가격 계산기

Wikiprompt, 무료 프롬프트 백과사전에서

Fatih Kadir Akın

2026년 10월 9일

메뉴 식자재 원가 및 가격 계산기 레시피와 메뉴 항목의 원가 계산, 식자재 원가율, 공헌이익 산출, 메뉴 엔지니어링 분류를 통한 가격 제안을 위한 완전한 AI 스킬입니다. Python 스크립트, 참조 문서, 템플릿, 그리고 실제 예제가 포함되어 있습니다.

프롬프트 내용저장

🌐
--- name: menu-food-cost-calculator description: 레시피와 메뉴 항목의 원가를 계산합니다 - 카페, 레스토랑, 베이커리, 푸드트럭, 케이터링을 대상으로 - 구매 가격과 수율을 1인분당 정확한 원가, 세전 가격 기준 식자재비율, 공헌이익, 목표 식자재비율 기준 권장 판매가로 변환하고, 메뉴 엔지니어링(Star, Plowhorse, Puzzle, Dog)으로 항목을 분류하여 가격, 분량, 메뉴 변경을 권장합니다. 사용자가 "이 요리가 나에게 얼마나 드는가?", "메뉴 가격을 어떻게 책정해야 하나?", "왜 식자재비율이 이렇게 높은가?", 또는 공급업체 가격이 포함된 레시피를 공유할 때 사용합니다. --- # 메뉴 식자재비 및 가격 계산기 당신은 소규모 식품 사업자가 모든 접시의 실제 원가를 알고 자신 있게 가격을 책정할 수 있도록 돕습니다. 실제 구매 가격과 레시피를 기반으로 작업하고, 계산 과정을 보여주며, 백분율뿐만 아니라 금액 기준 마진에 대해서도 생각합니다. ## 이 스킬의 파일 - `scripts/cost_menu.py` - JSON 원가 계산 시트에서 모든 레시피의 원가를 계산하고, 가격을 제안하며, 메뉴 엔지니어링을 실행합니다 (Python 3 표준 라이브러리만 사용) - `references/food-cost-basics.md` - 수율, 구매 시 중량 대비 식용 중량 원가, 식자재비율, 세금, 일반적인 원가 계산 실수 - `references/pricing-strategies.md` - 목표 비율 가격 책정, 마진 기반 가격 책정, 반올림, 메뉴 엔지니어링 조치 - `templates/recipe-costing-sheet.md` - 스크립트가 읽는 JSON 원가 계산 시트 및 보고서 레이아웃 - `examples/example-cafe-menu.md` - 5개 항목 카페 메뉴의 검토 사례 ## 작업 흐름 ### 1. 입력 수집 다음을 요청하거나 확인합니다: - 통화, 메뉴 가격에 VAT 또는 판매세가 포함되는지 여부 (및 세율). - 목표 식자재비율 (`references/food-cost-basics.md`에 일반적인 범위가 있음; 기본값 30). - 각 재료에 대해: 구매 가격, 포장 크기 및 단위, 수율 (다듬기, 껍질 제거, 조리 손실 또는 부패 후 사용 가능한 비율). - 각 항목에 대해: 조리된 기준 레시피 수량, 배치당 인분 수, 현재 메뉴 가격, 인분당 포장 또는 가니시, 알려진 경우 주간 판매량. 누락된 항목이 있으면 명확히 표시된 가정을 사용하고 (예: "아보카도 수율 90% 가정") 보고서에 나열합니다. ### 2. 원가 계산 시트 작성 `templates/recipe-costing-sheet.md`를 JSON으로 채웁니다. 스크립트가 아는 단위를 사용합니다 (g, kg, ml, l, oz, lb, each). 재료를 개수로 구매하지만 중량으로 사용하는 경우, 한 개의 무게를 재서 변환합니다; 절대 다른 차원을 혼합하지 마십시오. ### 3. 계산기 실행 ```bash python3 scripts/cost_menu.py menu.json python3 scripts/cost_menu.py menu.json --target 28 python3 scripts/cost_menu.py menu.json --json ``` 표는 인분당 원가, 메뉴 가격, 세금 제외 순 가격, 식자재비율, 공헌이익 (순 가격 - 원가), 목표 식자재비율 기준 가격 (올림), 모든 항목에 주간 판매량이 주어진 경우 메뉴 엔지니어링 등급을 보여줍니다. 오류 (알 수 없는 재료, 단위 불일치) 및 HIGH 발견 사항은 종료 코드 1을 만듭니다. 스크립트를 실행할 수 없으면, 동일한 계산을 수동으로 줄 단위로 수행하고 그렇게 말합니다. ### 4. 권장 사항 `references/pricing-strategies.md`를 사용합니다: 1. 먼저 데이터 오류를 수정하고 다시 실행합니다. 2. HIGH 및 WARN 항목에 대해 가격 인상, 분량 축소, 비싼 재료 교체, 또는 금액 마진이 강하므로 더 높은 비율을 수용하는 것 중에서 선택합니다. 트레이드오프의 이름을 명시합니다. 3. 메뉴 엔지니어링 등급을 사용하여 항목이 메뉴에서 어디에 속하는지, 홍보, 재가격 책정, 재작업, 또는 제거 여부를 결정합니다. 4. 제안된 변경 사항으로 다시 실행하여 전후를 보여줍니다. ### 5. 보고 `templates/recipe-costing-sheet.md`의 보고서 레이아웃을 사용하고, `examples/example-cafe-menu.md`와 같이 작성합니다. ## 규칙 - 소유자가 확인할 수 있도록 최소 하나의 항목에 대한 공식을 보여줍니다: 인분당 원가 / 순 가격 x 100. - 목표 가격을 기존 가격을 낮추라는 지시로 취급하지 마십시오; 그것은 벤치마크입니다. - 세금 또는 법률 조언을 제공하지 마십시오; 사용자가 제공한 세율만 적용합니다. - 대체 재료를 제안할 때 알레르기 유발 물질 및 식이 관련 주장을 존중하고, 식품 안전 또는 품질 기준을 낮추지 마십시오. - 공급업체 가격이 약 5% 이상 변동하면 원가를 다시 확인합니다. FILE:references/food-cost-basics.md # 식자재비 기본 사항 ## 주요 용어 - **구매 시 중량 (AP) 원가**: 포장, 케이스 또는 개당 지불하는 금액. - **수율 백분율**: 다듬기, 껍질 제거, 뼈 제거, 조리 손실 또는 부패 후 사용 가능한 비율. 껍질과 뼈를 제거한 연어 필레는 85% 수율일 수 있습니다; 아보카도 10개 중 1개를 사용할 수 없으면 개수 기준 수율은 90%입니다. - **식용 중량 (EP) 원가** = AP 단위 원가 / (수율 백분율 / 100). 레시피는 조리되고 사용 가능한 수량을 나열하므로, EP 원가로 계산됩니다. - **접시 원가 (인분당 원가)** = 배치의 재료 EP 원가 합계 / 인분 수 + 인분당 추가 비용 (포장, 냅킨, 가니시, 소스 컵). - **순 가격** = 메뉴 가격 / (1 + 세율), 메뉴 가격에 VAT 또는 판매세가 포함된 경우. 식자재비율은 징수하는 세금이 아닌, 당신이 보유하는 금액을 기준으로 측정해야 합니다. - **식자재비율** = 접시 원가 / 순 가격 x 100. - **공헌이익** = 순 가격 - 접시 원가. 각 판매가 인건비, 임대료, 이윤을 지불하는 데 남기는 금액입니다. ## 계산 공식 예시 연어 필레를 kg당 32.00에 구매하고 수율이 85%인 경우: - AP 원가/g = 32.00 / 1000 = 0.032 - EP 원가/g = 0.032 / 0.85 = 0.0376 - 160 g 인분 = 160 x 0.0376 = 6.02 ## 일반적인 식자재비율 목표 (대략적인 가이드) | 업종 | 일반적인 식자재비율 | | --- | --- | | 커피 및 에스프레소 음료 | 15~25 | | 베이커리 제품 | 20~30 | | 카페 브런치 요리 | 28~35 | | 캐주얼 레스토랑 메인 | 28~35 | | 스테이크 및 해산물 메인 | 35~45 | | 피자 | 20~28 | | 케이터링 트레이 | ㅇ25~35 | 올바른 목표는 인건비, 임대료, 판매량에 따라 달라집니다. 인건비가 낮은 항목은 식자재비율이 더 높아도 여전히 매우 수익성이 있을 수 있습니다. ## 일반적인 원가 계산 실수 1. 작은 항목을 잊는 것: 오일, 팬용 버터, 소금, 가니시, 소스, 테이크아웃 포장. 추가하거나 extras_per_portion을 사용합니다. 2. 단백질과 농산물에 수율을 적용하지 않고 AP 원가를 사용하는 것. 3. 세금이 포함된 가격을 기준으로 식자재비율을 측정하는 것. 4. 레시피 카드가 아닌 실제 주방에서 플레이팅하는 것을 기준으로 원가를 계산하지 않는 것 (분량 증가). 실제 5인분의 무게를 재십시오. 5. 오래된 공급업체 가격. 가격이 약 5% 이상 변동하면 시트를 업데이트합니다. 6. 단위 혼용: 개수로 구매하지만 중량으로 사용하는 재료는 한 개의 무게를 재야 합니다. 7. 폐기물 및 직원 식사 무시; 별도로 추적하고 실제 식자재비율 (재고 기준)을 이 이론적 원가와 비교합니다. ## 이론적 대 실제 식자재비율 이 스킬은 이론적 원가를 계산합니다: 레시피를 따를 경우 식자재비가 얼마여야 하는지. 실제 식자재비율 = (기초 재고 + 구매 - 기말 재고) / 순 식품 판매. 약 2~3% 포인트 이상의 차이는 일반적으로 폐기물, 분량 증가, 도난 또는 시트의 잘못된 가격을 의미합니다. FILE:references/pricing-strategies.md # 가격 책정 전략 및 메뉴 엔지니어링 ## 가격을 설정하는 방법 1. **목표 식자재비율**: 가격 = 접시 원가 / 목표 x (1 + 세율), 올림. 간단하며, 스크립트의 "AT TARGET" 열입니다. 약점: 저렴한 항목은 저평가되고 비싼 단백질은 고평가됩니다. 2. **공헌이익**: 각 항목이 벌어야 할 금액을 결정하고 (예: 메인 요리는 최소 5.00), 가격 = (접시 원가 + 마진) x (1 + 세율). 고비용 단백질에 더 적합합니다. 3. **시장 조사**: 주변의 비슷한 업소 3~5곳과 비교합니다. 가격 인식은 원가만큼 중요합니다. 4. **혼합**: 목표 가격에서 시작하여 금액 기준 마진을 확인한 다음, 시장과 대조하여 타당성을 검토합니다. ## 반올림 및 표시 - 메뉴에서 사용하는 단위로 올림합니다 (0..10, 0.50 또는 정수). 고급 메뉴는 통화 기호 없이 정수를 사용하는 경우가 많고, 캐주얼 메뉴는 .50 또는 .90으로 끝나는 경우가 많습니다. - 메뉴 전체에 걸쳐 작은 인상을 한꺼번에 많이 하지 마십시오; 가장 마진이 약한 항목부터 올리십시오. - 가격 차이를 논리적으로 유지하십시오: 귀리 음료 업그레이드는 추가 원가를 충당해야 합니다 (귀리 음료는 종종 유제품 우유보다 리터당 약 두 배 비쌉니다. ## 메뉴 엔지니어링 모든 항목에 대한 주간 판매량이 필요합니다. 스크립트는 다음을 사용합니다: - **인기도 선**: 항목이 균등한 몫의 70% 이상을 판매하면 인기 있는 것입니다 (5개 항목의 경우, 판매 단위의 0.7 x 20% = 14%). - **마진 선**: 판매량 가중 평균 공헌이익. | 등급 | 인기도 | 마진 | 조치 | | --- | --- | --- | --- | | Star | 높음 | 높음 | 품질과 분량을 일관되게 유지하고, 메뉴의 가장 좋은 위치에 배치하며, 소폭 가격 인상은 일반적으로 안전합니다. | Plowhorse | 높음 | 낮음 | 가격을 약간 올리고, 원가를 절감하고 (분량, 가니시, 공급업체), 또는 마진이 높은 추가 항목과 함께 판매합니다. 제거하지 마십시오. | Puzzle | 낮음 | 높음 | 홍보: 더 나은 메뉴 위치, 설명, 직원 추천, 사진. 가격이 고객을 놀라게 하지 않는지 확인합니다. | Dog | 낮음 | 낮음 | 레시피나 가격을 재작업하거나 제거합니다, 목적이 없는 한 (키즈 메뉴, 식이 옵션, 시그니처 항목). ## 식자재비율이 높은 항목에 대한 수정 방안 선택 | 옵션 | 적합한 경우 | 위험 | | --- | --- | --- | | 가격 인상 | 항목이 인기 있고 시장이 허용하는 경우 | 인상 폭이 크면 판매 감소 | | 분량 축소 | 분량이 고객 기대보다 큰 경우 | 고객이 알아차림; 가치 인식 유지 | | 재료 교체 | 더 저렴하고 동등한 품질의 옵션이 있는 경우 | 알레르기 유발 물질 및 맛 변화; 메뉴 문구 업데이트 | | 더 높은 비율 수용 | 금액 마진이 메뉴에서 가장 높은 경우 | 수익을 내려면 판매량 필요 | | 항목 제거 | 전략적 역할이 없는 Dog인 경우 | 단골이 아쉬워할 수 있음 | 항상 제안된 변경 사항으로 계산기를 다시 실행하고 전후를 보여주십시오. FILE:templates/recipe-costing-sheet.md # 레시피 원가 계산 시트 (scripts/cost_menu.py 입력) `menu.json`으로 저장합니다. 레시피의 수량은 조리된 (사용 가능한) 양입니다. ```json { "currency": "EUR", "target_food_cost_pct": 30, "menu_price_includes_tax_pct": 10, "price_rounding": 0.10, "ingredients": [ {"name": "flour", "price": 0.95, "per": "1 kg"}, {"name": "butter", "price": 9.80, "per": "1 kg"}, {"name": "eggs", "price": 3.60, "per": "12 each"}, {"name": "blueberries", "price": 16.00, "per": "1 kg", "yield_pct": ㅇ95} ], "recipes": [ { "name": "Blueberry Muffin", "portions": 12, "menu_price": ㅇ3.20, "sold_per_week": ㅇ90, "extras_per_portion": ㅇ0.06, "items": [["flour", "500 g"], ["butter", "180 g"], ["eggs", "3 each"], ["blueberries", "300 g"]] } ] } ``` 필드 참고 사항: - `per`: 구매하는 포장을 "<수량> <단위>"로 표시 (g, kg, mg, ml, cl, dl, l, oz, lb, each). - `yield_pct`: 1~100, 기본값 100. - `menu_price_includes_tax_pct`: 메뉴 가격이 세금 없이 표시되면 0. - `sold_per_week`: 모든 항목 (또는 없음)에 제공하여 메뉴 엔지니어링 등급을 얻습니다. - `extras_per_portion`: 포장, 냅킨, 가니시, 소스 컵을 금액으로 표시. 실행: `python3 scripts/cost_menu.py menu.json [--target 30] [--json]` --- # 메뉴 원가 보고서: <업체명> - <날짜> **목표 식자재비율:** <x>% **가격에 포함된 세금:** <세율 또는 없음> **통화:** <코드> **가정:** <수율, 누락된 가격, 인분 중량> ## 결과 (변경 전) | 항목 | 인분당 원가 | 가격 | 순 가격 | 식자재비율 % | 마진 | 목표 가격 | 등급 | | --- | --- | --- | --- | --- | --- | --- | --- | ## 공식 확인 <한 항목을 줄 단위로 계산> ## 주의가 필요한 사항 1. **<항목>** - <발견 사항>. 옵션: <가격 / 분량 / 재료 / 수용>. 권장 사항: <하나>. ## 제안된 변경 사항 및 결과 (변경 후) <변경 사항, 그 다음 새 표 또는 변경된 행> ## 메뉴 엔지니어링 조치 - Stars: <항목 및 조치> - Plowhorses: <항목 및 조치> - Puzzles: <항목 및 조치> - Dogs: <항목 및 조치> ## 다음 단계 - <실제 인분 무게 측정, 공급업체 가격 업데이트, 월별 실제 식자재비율 추적> FILE:examples/example-cafe-menu.md # 예시: 5개 항목 카페 메뉴 **사용자:** "우리는 소규모 브런치 카페입니다. 가격에는 10% VAT가 포함되어 있고, 식자재비율은 약 30%를 원합니다. 공급업체 가격과 레시피는 다음과 같습니다. 왜 마진이 이렇게 얇은가요?" 시트에는 16개의 재료와 5개의 항목이 있으며 주간 판매량이 있습니다 (아보카도 수율 90% - 약 10개 중 1개를 사용할 수 없음; 연어 85% - 다듬기 후). . **명령:** ```bash python3 scripts/cost_menu.py cafe-menu.json ``` **출력 (변경 전):** ``` 항목 원가 가격 순가격 식자재비율% 마진 목표가격 등급 Avocado Toast 3.13 9.50 8.64 36.2 5.51 11.50 Star Salmon Spinach Bowl 8.02 14.50 13.18 60.8 5.16 29.50 Puzzle Flat White 0.72 3.80 3.45 21.0 2.73 2.70 Plowhorse Oat Flat White 0.91 4.20 3.82 23.9 2.91 3.40 Plowhorse Blueberry Muffin 0.79 3.20 2.91 27.2 2.12 3.00 Dog 발견 사항 (8): [HIGH] Salmon Spinach Bowl: 식자재비율 60.8%는 목표 30%보다 훨씬 높음; 가격 EUR 29.50 또는 인분당 원가 4.06 절감 [WARN] Avocado Toast: 식자재비율 36.2%는 목표 30%보다 높음; 목표 가격은 EUR 11.50 [INFO] Salmon Spinach Bowl: 연어 필레가 원가의 76%를 차지; 가격 또는 분량이 가장 중요함 [INFO] 메뉴 엔지니어링: 판매량 가중 평균 마진 EUR 3.22, 인기도 선 14.0% of items sold [INFO] 재료 'truffle oil'은 어떤 레시피에도 사용되지 않음 ``` --- # 메뉴 원가 보고서: 브런치 카페 - 10월 **목표 식자재비율:** 30% **가격에 포함된 세금:** 10% VAT **통화:** EUR **가정:** 아보카도 수율 90%, 연어 85%, 시금치 90%; 추가 비용 접시당 0.10, 커피당 0.12 (컵 및 뚜껑), 머핀당 0.06. ## 공식 확인 (Salmon Spinach Bowl, 변경 전) - 연어 160 g x (32.00 / 1000 / 0.85) = ㅇ6.02 - 시금치 70 g x (14.00 / 1000 / ㅇ0.90) = ㅇ1.09; 계란 0.30; 토마토 0.36; 올리브 오일 0.15; 추가 비용 0.10 - 인분당 원가 = ㅇ8.02; 순 가격 = ㅇ14.50 / 1.10 = ㅇ13.18; 식자재비율 = ㅇ8.02 / ㅇ13.18 x 100 = ㅇ60.8% ## 주의가 필요한 사항 1. **Salmon Spinach Bowl (HIGH, Puzzle)** - 60.8% 식자재비율; 연어가 원가의 76%를 차지. 목표 가격 (29.50)은 카페에 비현실적. 권장 사항: 연어를 120 g으로 줄이고 (볼 요리로는 여전히 넉넉한 분량) 가격을 17.50으로 인상; 약 40% 식자재비율을 수용 - 마진이 메뉴에서 가장 높아짐. 2. **Avocado Toast (WARN, Star)** - 36.2%. 베스트셀러 요리이므로, 1.00 인상하여 10.50으로 하는 것은 위험이 낮습니다. 3. **Flat White 및 Oat Flat White (Plowhorses)** - 건강한 비율 (21~24%)이지만 마진이 작음; 할인하지 마십시오. 귀리 추가 요금을 0.40으로 유지: 귀리 음료는 우유보다 컵당 0.19 더 비쌉니다. 4. **Blueberry Muffin (Dog)** - 비율은 괜찮지만, 마진이 낮고 판매량이 적음. 제거 전에 커피와의 번들 판매를 시도하십시오. 5. **Truffle oil**이 시트에 있지만 어떤 레시피에도 없음: 주문 또는 시트에서 제거하십시오. ## 제안된 변경 사항 및 결과 (변경 후) Avocado Toast 10.50; Salmon Spinach Bowl 120 g 연어에 17.50. 재실행: `python3 scripts/cost_menu.py cafe-menu-revised.json` ``` 항목 원가 가격 순가격 식자재비율% 마진 목표가격 등급 Avocado Toast 3.13 10.50 9.55 32.8 6.42 11.50 Star Salmon Spinach Bowl 6.51 17.50 15.91 40.9 9.40 23.90 Puzzle Flat White 0.72 3.80 3.45 21.0 2.73 2.70 Plowhorse Oat Flat White 0.91 4.20 3.82 23.9 2.91 3.40 Plowhorse Blueberry Muffin 0.79 3.20 2.91 27.2 2.12 3.00 Dog 발견 사항 (6): [WARN] Salmon Spinach Bowl: 식자재비율 40.9%는 목표 30%보다 높음; 목표 가격은 EUR 23.90 [INFO] 메뉴 엔지니어링: 판매량 가중 평균 마진 EUR 3.56, 인기도 선 14.0% of items sold ``` 종료 코드 0. 남은 WARN은 의도적으로 수용됨: 볼당 마진 9.40 대 이전 5.16. ## 메뉴 엔지니어링 조치 - Stars: Avocado Toast - 레시피를 일관되게 유지하고, 브런치 섹션 상단에 배치. - Plowhorses: Flat White, Oat Flat White - 할인 금지; 모든 커피와 함께 페이스트리 제안. - Puzzles: Salmon Spinach Bowl - 짧은 설명과 직원 추천 제공; 새 가격으로 4주 후 판매량 확인. - Dogs: Blueberry Muffin - 커피 + 머핀 번들을 4주간 테스트한 후 결정. ## 다음 단계 - 이번 주에 실제 연어 5인분의 무게를 측정하여 120 g 스펙이 지켜지는지 확인. - 공급업체 가격을 월별로 업데이트하고 시트를 다시 실행. - 월말 재고 기준 실제 식자재비율과 비교. FILE:scripts/cost_menu.py #!/usr/bin/env python3 """레시피와 구매 가격에서 메뉴 항목의 원가를 계산하고, 메뉴 가격을 제안합니다. 사용법: python3 cost_menu.py menu.json [--target 30] [--json] cat menu.json | python3 cost_menu.py - 입력 JSON (templates/recipe-costing-sheet.md 참조): { "currency": "EUR", "target_food_cost_pct": 30, # 선택 사항, 기본값 30 (또는 --target) "menu_price_includes_tax_pct": 10, # 선택 사항; 메뉴 가격에 포함된 VAT/판매세 "price_rounding": 0.10, # 선택 사항; 제안 가격은 이 단위로 올림 "ingredients": [ {"name": "butter", "price": 9.80, "per": "1 kg", "yield_pct": ㅇ100} ], "recipes": [ {"name": "Croissant", "portions": 12, "menu_price": ㅇ3.20, "sold_per_week": ㅇ180, "extras_per_portion": ㅇ0.05, # 선택 사항: 포장, 냅킨, 가니시 "items": [["butter", "600 g"], ["flour", "1 kg"]]} ] } 단위: g, kg, mg, ml, cl, dl, l, oz, lb, each (또한 pc, pcs, piece, unit, egg). 레시피 수량은 조리된 (사용 가능한) 양입니다. yield_pct는 구매한 것 중 다듬기, 껍질 제거, 조리 손실 또는 부패 후 사용 가능한 비율입니다. 레시피당: 인분당 원가, 순 (세금 제외) 메뉴 가격 대비 식자재비율, 공헌이익, 목표 비율 기준 제안 가격, 및 가장 큰 원가 요인 3개. 모든 레시피에 sold_per_week가 있으면, 메뉴 엔지니어링 등급 (Star, Plowhorse, Puzzle, Dog)을 추가합니다. 종료 코드: 0 정상, 1 데이터 오류 또는 HIGH 발견 사항, 2 사용법 또는 입력 오류. 표준 라이브러리만 사용합니다. """ import json import math import re import sys UNITS = { # 단위 ->(차원, 기본 단위 g / ml / each로의 변환 계수) "mg": ("mass", 0.001), "g": ("mass", 1.0),"kg": ("mass", 1000.0), "oz": ("mass", 28.3495), "lb": ("mass", 453.592), "ml": ("volume", 1.0), "cl": ("volume", 10.0), "dl": ("volume", 100.0),"l": ("volume", 1000.0), "each": ("count", 1.0), "pc": ("count", 1.0), "pcs": ("count", 1.0), "piece": ("count", 1.0), "pieces": ("count", 1.0), "unit": ("count", 1.0), "units": ("count", 1.0), "egg": ("count", 1.0), "eggs": ("count", ㅇ1.0), } BASE = {"mass": "g", "volume": "ml", "count": "each"} def usage(msg): print(f"error: {msg}\n", file=sys.stderr) print(__doc__.strip().split("\n\n")[1], file=sys.stderr) sys.exit(2) def parse_qty(text): """'600 g' -> (600.0, 'mass', 600.0 in base units).""" m = re.fullmatch(r"\s*(\d+(?:[.,]\d+)?)\s*([a-zA-Z]+)?\s*", str(text)) if not m: raise ValueError(f"cannot read quantity {text!r}") qty = float(m.group(1).replace(",", ".")) unit = (m.group(2) or "each").lower() if unit not in UNITS: raise ValueError(f"unknown unit {unit!r} in {text!r}") dim, factor = UNITS[unit] return qty, dim, qty * factor def round_up(value, step): return math.ceil(round(value / step, 6)) * step def analyze(data, target_override=None): errors, findings = [], [] cur = data.get("currency", "") target = float(target_override or data.get("target_food_cost_pct", 30)) tax = float(data.get("menu_price_includes_tax_pct", 0)) step = float(data.get("price_rounding", 0.10)) ingredients = {} for ing in data.get("ingredients", []): name = str(ing.get("name", "")).strip().lower() try: _, dim, base_qty = parse_qty(ing["per"]) price = float(ing["price"]) except (KeyError, ValueError, TypeError) as e: errors.append(f"ingredient {name or '?'}: {e}") continue y = float(ing.get("yield_pct", 100)) if not 0 < y <= 100: errors.append(f"ingredient {name}: yield_pct must be between 1 and 100") continue ingredients[name] = {"dim": dim, "cost_per_base": price / base_qty / (y / 100), "yield": y, "used": False} results = [] for rec in data.get("recipes", []): rname = rec.get("name", "?") portions = float(rec.get("portions", 1) or 1) lines, bad = [], False for item in rec.get("items", []): iname, qtext = str(item[0]).strip().lower(), item[1] ing = ingredients.get(iname) if not ing: errors.append(f"{rname}: unknown ingredient {iname!r} (add it to ingredients)") bad = True continue ing["used"] = True try: _, dim, base_qty = parse_qty(qtext) except ValueError as e: errors.append(f"{rname}: {e}") bad = True continue if dim != ing["dim"]: errors.append(f"{rname}: {iname} is bought by {BASE[ing['dim']]} but used by {BASE[dim]} ({qtext}); " f"convert it (for example weigh one piece)") bad = True continue lines.append((iname, base_qty * ing["cost_per_base"])) if bad: continue batch = sum(c for _, c in lines) extras = float(rec.get("extras_per_portion", 0)) cost = batch / portions + extras price = float(rec.get("menu_price", 0)) net = price / (1 + tax / 100) if price else 0.0 pct = 100 * cost / net if net else None suggested = round_up(cost / (target / 100) * (1 + tax / 100), step) drivers = sorted(lines, key=lambda x: -x[1])[:3] r = {"name": rname, "portions": portions, "cost_per_portion": round(cost, 3), "menu_price": price, "net_price": round(net, 2), "food_cost_pct": None if pct is None else round(pct, 1), "contribution_margin": round(net - cost, ㅇ2) if net else None, "suggested_price_at_target": round(suggested, 2), "sold_per_week": rec.get("sold_per_week"), "drivers": [{"ingredient": n, "share_pct": round(100 * c / batch, ㅇ1) if batch else 0} for n, c in drivers]} results.append(r) if pct is None: findings.append(("WARN", rname, f"no menu_price; suggested {cur} {suggested:.2f} at {target:g}% food cost")) elif pct > target + 15: findings.append(("HIGH", rname, f"food cost {pct:.1f}% is far above the {target:g}% target; " f"price {cur} {suggested:.2f} or cut cost {cost - net * target / 100:.2f} per portion")) elif pct > target + 5: findings.append(("WARN", rname, f"food cost {pct:.1f}% is above the {target:g}% target; " f"price at target would be {cur} {suggested:.2f}")) elif pct < target - 15: findings.append(("INFO", rname, f"food cost only {pct:.1f}%; check the recipe lists every ingredient and portion size")) if drivers and batch and drivers[0][1] / batch > 0.5: findings.append(("INFO", rname, f"{drivers[0][0]} is {100 * drivers[0][1] / batch:.0f}% of the cost; " f"its price or portion matters most")) sold = [r for r in results if isinstance(r["sold_per_week"], (int, float)) and r["contribution_margin"] is not None] if sold and len(sold) == len(results) and len(sold) >= 3: total = sum(r["sold_per_week"] for r in sold) pop_line = 0.7 / len(sold) avg_cm = sum(r["contribution_margin"] * r["sold_per_week"] for r in sold) / total if total else 0 for r in sold: high_pop = total and r["sold_per_week"] / total >= pop_line high_cm = r["contribution_margin"] >= avg_cm r["menu_class"] = {(True, True): "Star", (True, False): "Plowhorse", (False, True): "Puzzle", (False, False): "Dog"}[(bool(high_pop), high_cm)] findings.append(("INFO", None, f"menu engineering: weighted average margin {cur} {avg_cm:.2f}, " f"popularity line {100 * pop_line:.1f}% of items sold")) for name, ing in ingredients.items(): if not ing["used"]: findings.append(("INFO", None, f"ingredient {name!r} is not used in any recipe")) return {"currency": cur, "target_pct": target, "tax_pct": tax, "recipes": results, "errors": errors, "findings": [{"severity": s, "recipe": n, "message": m} for s, n, m in findings]} def main(argv): target, as_json, paths = None, False, [] it = iter(argv) for a in it: if a == "--target": try: target = float(next(it, "")) except ValueError: usage("--target needs a number, for example 30") if not 5 <= target <= 80: usage("--target should be a food cost percent between 5 and 80") elif a == "--json": as_json = True elif a.startswith("--"): usage(f"unknown option {a}") else: paths.append(a) if len(paths) != 1: usage("give exactly one menu JSON file, or - for stdin") try: raw = sys.stdin.read() if paths[0] == "-" else open(paths[0], encoding="utf-8").read() data = json.loads(raw) except (OSError, ValueError) as e: print(f"error: cannot read menu JSON: {e}", file=sys.stderr) return 2 if not data.get("recipes"): print("error: no recipes in the input", file=sys.stderr) return 2 rep = analyze(data, target) if as_json: print(json.dumps(rep, indent=2)) else: cur = rep["currency"] print(f"Target food cost {rep['target_pct']:g}% | prices include {rep['tax_pct']:g}% tax | currency {cur}\n") print(f"{'ITEM':<22} {'COST':>7} {'PRICE':>7} {'NET':>7} {'FOOD%':>6} {'MARGIN':>7} {'AT TARGET':>9} CLASS") for r in rep["recipes"]: pct = "-" if r["food_cost_pct"] is None else f"{r['food_cost_pct']:.1f}" cm = "-" if r["contribution_margin"] is None else f"{r['contribution_margin']:.2f}" print(f"{r['name'][:22]:<22} {r['cost_per_portion']:>7.2f} {r['menu_price']:>7.2f} {r['net_price']:>7.2f} " f"{pct:>6} {cm:>7} {r['suggested_price_at_target']:>9.2f} {r.get('menu_class', '-')}") print("\nTop cost drivers:") for r in rep["recipes"]: print(f" {r['name']}: " + ", ".join(f"{d['ingredient']} {d['share_pct']:g}%" for d in r["drivers"])) if rep["errors"]: print(f"\nErrors ({len(rep['errors'])}):") for e in rep["errors"]: print(f" [ERROR] {e}") print(f"\nFindings ({len(rep['findings'])}):") order = {"HIGH": 0, "WARN": 1, "INFO": 2} for f in sorted(rep["findings"], key=lambda f: order[f["severity"]]]): print(f" [{f['severity']}] {f['recipe'] + ': ' if f['recipe'] else ''}{f['message']}") bad = rep["errors"] or any(f["severity"] == "HIGH" for f in rep["findings"]) return 1 if bad else 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))

전체 프롬프트를 보려면 로그인하세요

Continue with:

By logging in, you agree to our Terms of Use and Privacy Policy

사용법

이 프롬프트는 business와 함께 사용하도록 설계되었습니다. 위의 프롬프트 내용을 복사하여 원하는 AI 도구에 붙여넣으세요.

최상의 결과를 얻으려면 자리 표시자(대괄호 또는 대문자로 표시)를 특정 요구 사항으로 사용자 지정할 수 있습니다.

참고 자료

분류:business| prompts.chat| food-cost| menu-pricing

토론