@nomadamas/k-skill 0.1.0
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.
- package/bin/k-skill.js +60 -0
- package/package.json +37 -0
- package/skills/assembly-bill-vote-search/instruction.md +111 -0
- package/skills/assembly-bill-vote-search/skill.json +10 -0
- package/skills/biz-health-check/instruction.md +69 -0
- package/skills/biz-health-check/scripts/biz_health_check.py +163 -0
- package/skills/biz-health-check/skill.json +10 -0
- package/skills/bok-ecos-stats/instruction.md +95 -0
- package/skills/bok-ecos-stats/scripts/bok_ecos.py +302 -0
- package/skills/bok-ecos-stats/skill.json +10 -0
- package/skills/building-register-search/instruction.md +69 -0
- package/skills/building-register-search/scripts/building_register.py +309 -0
- package/skills/building-register-search/scripts/building_register_xml.py +32 -0
- package/skills/building-register-search/skill.json +9 -0
- package/skills/bunjang-search/instruction.md +154 -0
- package/skills/bunjang-search/skill.json +10 -0
- package/skills/catchtable-sniper/instruction.md +270 -0
- package/skills/catchtable-sniper/skill.json +9 -0
- package/skills/cheap-gas-nearby/instruction.md +107 -0
- package/skills/cheap-gas-nearby/skill.json +10 -0
- package/skills/corporate-registration-consulting/instruction.md +117 -0
- package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +106 -0
- package/skills/corporate-registration-consulting/skill.json +8 -0
- package/skills/coupang-product-search/instruction.md +219 -0
- package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +146 -0
- package/skills/coupang-product-search/skill.json +10 -0
- package/skills/court-auction-notice-search/instruction.md +201 -0
- package/skills/court-auction-notice-search/skill.json +9 -0
- package/skills/court-payment-order-assistant/instruction.md +120 -0
- package/skills/court-payment-order-assistant/skill.json +10 -0
- package/skills/d2b-notice-search/instruction.md +117 -0
- package/skills/d2b-notice-search/skill.json +10 -0
- package/skills/daangn-cars-search/instruction.md +91 -0
- package/skills/daangn-cars-search/scripts/daangn_cars.py +72 -0
- package/skills/daangn-cars-search/skill.json +9 -0
- package/skills/daangn-jobs-search/instruction.md +90 -0
- package/skills/daangn-jobs-search/scripts/daangn_jobs.py +98 -0
- package/skills/daangn-jobs-search/skill.json +9 -0
- package/skills/daangn-realty-search/instruction.md +114 -0
- package/skills/daangn-realty-search/scripts/daangn_detail_ld.py +76 -0
- package/skills/daangn-realty-search/scripts/daangn_realty.py +216 -0
- package/skills/daangn-realty-search/scripts/daangn_relay_store.py +174 -0
- package/skills/daangn-realty-search/skill.json +8 -0
- package/skills/daangn-used-goods-search/instruction.md +90 -0
- package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +80 -0
- package/skills/daangn-used-goods-search/skill.json +9 -0
- package/skills/daishin-report-search/instruction.md +138 -0
- package/skills/daishin-report-search/skill.json +10 -0
- package/skills/daiso-product-search/instruction.md +172 -0
- package/skills/daiso-product-search/skill.json +8 -0
- package/skills/danawa-price-search/instruction.md +183 -0
- package/skills/danawa-price-search/scripts/danawa_search.py +354 -0
- package/skills/danawa-price-search/skill.json +8 -0
- package/skills/delivery-tracking/instruction.md +358 -0
- package/skills/delivery-tracking/skill.json +8 -0
- package/skills/donation-place-search/instruction.md +129 -0
- package/skills/donation-place-search/skill.json +9 -0
- package/skills/emergency-room-beds/instruction.md +82 -0
- package/skills/emergency-room-beds/skill.json +8 -0
- package/skills/ev-charger-nearby/instruction.md +80 -0
- package/skills/ev-charger-nearby/scripts/ev_charger.py +222 -0
- package/skills/ev-charger-nearby/skill.json +10 -0
- package/skills/ev-subsidy-status/instruction.md +165 -0
- package/skills/ev-subsidy-status/skill.json +10 -0
- package/skills/express-bus-booking/instruction.md +207 -0
- package/skills/express-bus-booking/references/kobus-http-flow.md +159 -0
- package/skills/express-bus-booking/scripts/kobus_express_booking.py +243 -0
- package/skills/express-bus-booking/skill.json +8 -0
- package/skills/fine-dust-location/instruction.md +89 -0
- package/skills/fine-dust-location/skill.json +9 -0
- package/skills/flight-ticket-search/instruction.md +237 -0
- package/skills/flight-ticket-search/scripts/flight_ticket_search.py +501 -0
- package/skills/flight-ticket-search/skill.json +9 -0
- package/skills/foresttrip-vacancy/instruction.md +167 -0
- package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +549 -0
- package/skills/foresttrip-vacancy/skill.json +10 -0
- package/skills/fsc-corporate-info/instruction.md +57 -0
- package/skills/fsc-corporate-info/scripts/fsc_corporate_info.py +113 -0
- package/skills/fsc-corporate-info/skill.json +10 -0
- package/skills/g2b-order-plan-search/instruction.md +131 -0
- package/skills/g2b-order-plan-search/scripts/g2b_order_plan.py +158 -0
- package/skills/g2b-order-plan-search/skill.json +10 -0
- package/skills/g2b-sanctioned-supplier/instruction.md +61 -0
- package/skills/g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py +114 -0
- package/skills/g2b-sanctioned-supplier/skill.json +10 -0
- package/skills/gangnamunni-clinic-search/instruction.md +113 -0
- package/skills/gangnamunni-clinic-search/skill.json +9 -0
- package/skills/geeknews-search/instruction.md +69 -0
- package/skills/geeknews-search/scripts/geeknews_search.py +296 -0
- package/skills/geeknews-search/skill.json +8 -0
- package/skills/gongsijiga-search/instruction.md +128 -0
- package/skills/gongsijiga-search/skill.json +9 -0
- package/skills/gov-overseas-trip-report/instruction.md +488 -0
- package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +1206 -0
- package/skills/gov-overseas-trip-report/skill.json +9 -0
- package/skills/han-river-water-level/instruction.md +83 -0
- package/skills/han-river-water-level/skill.json +10 -0
- package/skills/highway-traffic-status/instruction.md +88 -0
- package/skills/highway-traffic-status/scripts/highway_traffic.py +315 -0
- package/skills/highway-traffic-status/skill.json +11 -0
- package/skills/hipass-receipt/instruction.md +97 -0
- package/skills/hipass-receipt/skill.json +10 -0
- package/skills/hola-poke-yeoksam/instruction.md +247 -0
- package/skills/hola-poke-yeoksam/skill.json +8 -0
- package/skills/household-waste-info/instruction.md +117 -0
- package/skills/household-waste-info/skill.json +10 -0
- package/skills/housing-official-price/instruction.md +177 -0
- package/skills/housing-official-price/skill.json +10 -0
- package/skills/hwp/instruction.md +206 -0
- package/skills/hwp/skill.json +8 -0
- package/skills/intercity-bus-booking/instruction.md +189 -0
- package/skills/intercity-bus-booking/references/tmoney-intercity-http-flow.md +126 -0
- package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +381 -0
- package/skills/intercity-bus-booking/skill.json +8 -0
- package/skills/iros-registry-automation/instruction.md +229 -0
- package/skills/iros-registry-automation/scripts/iros_pdf_summary.py +249 -0
- package/skills/iros-registry-automation/scripts/upstream.pin +1 -0
- package/skills/iros-registry-automation/skill.json +9 -0
- package/skills/job-posting-match/instruction.md +130 -0
- package/skills/job-posting-match/scripts/job_posting_match.py +396 -0
- package/skills/job-posting-match/scripts/test_job_posting_match.py +54 -0
- package/skills/job-posting-match/skill.json +8 -0
- package/skills/jobkorea-talent-search/instruction.md +118 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_models.py +27 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_parse.py +186 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +94 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search_condition.py +136 -0
- package/skills/jobkorea-talent-search/scripts/test_jobkorea_talent_search.py +76 -0
- package/skills/jobkorea-talent-search/skill.json +9 -0
- package/skills/joseon-sillok-search/instruction.md +76 -0
- package/skills/joseon-sillok-search/scripts/sillok_search.py +552 -0
- package/skills/joseon-sillok-search/skill.json +8 -0
- package/skills/k-dart/instruction.md +406 -0
- package/skills/k-dart/skill.json +8 -0
- package/skills/k-schoollunch-menu/instruction.md +109 -0
- package/skills/k-schoollunch-menu/skill.json +9 -0
- package/skills/k-skill-cleaner/instruction.md +80 -0
- package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +410 -0
- package/skills/k-skill-cleaner/skill.json +8 -0
- package/skills/k-skill-setup/instruction.md +253 -0
- package/skills/k-skill-setup/skill.json +11 -0
- package/skills/kakao-bar-nearby/instruction.md +76 -0
- package/skills/kakao-bar-nearby/skill.json +8 -0
- package/skills/kakao-map/instruction.md +176 -0
- package/skills/kakao-map/skill.json +10 -0
- package/skills/kakaotalk-mac/instruction.md +189 -0
- package/skills/kakaotalk-mac/skill.json +8 -0
- package/skills/kbl-results/instruction.md +89 -0
- package/skills/kbl-results/skill.json +9 -0
- package/skills/kbo-results/instruction.md +82 -0
- package/skills/kbo-results/skill.json +8 -0
- package/skills/keris-academic-search/instruction.md +81 -0
- package/skills/keris-academic-search/scripts/keris_academic.py +210 -0
- package/skills/keris-academic-search/skill.json +10 -0
- package/skills/kleague-results/instruction.md +92 -0
- package/skills/kleague-results/skill.json +8 -0
- package/skills/kopis-performance-search/instruction.md +109 -0
- package/skills/kopis-performance-search/skill.json +10 -0
- package/skills/korea-weather/instruction.md +93 -0
- package/skills/korea-weather/skill.json +10 -0
- package/skills/korean-character-count/instruction.md +87 -0
- package/skills/korean-character-count/scripts/korean_character_count.js +268 -0
- package/skills/korean-character-count/skill.json +8 -0
- package/skills/korean-cinema-search/instruction.md +177 -0
- package/skills/korean-cinema-search/skill.json +8 -0
- package/skills/korean-heritage-search/instruction.md +106 -0
- package/skills/korean-heritage-search/scripts/korean_heritage_search.py +321 -0
- package/skills/korean-heritage-search/skill.json +8 -0
- package/skills/korean-holiday-calendar/instruction.md +97 -0
- package/skills/korean-holiday-calendar/skill.json +9 -0
- package/skills/korean-humanizer/instruction.md +389 -0
- package/skills/korean-humanizer/references/ai-tell-taxonomy.md +147 -0
- package/skills/korean-humanizer/skill.json +8 -0
- package/skills/korean-jangbu-for/instruction.md +133 -0
- package/skills/korean-jangbu-for/scripts/install.sh +237 -0
- package/skills/korean-jangbu-for/scripts/upstream.pin +1 -0
- package/skills/korean-jangbu-for/skill.json +8 -0
- package/skills/korean-law-search/instruction.md +126 -0
- package/skills/korean-law-search/skill.json +10 -0
- package/skills/korean-marathon-schedule/instruction.md +111 -0
- package/skills/korean-marathon-schedule/skill.json +9 -0
- package/skills/korean-middle-korean/instruction.md +79 -0
- package/skills/korean-middle-korean/scripts/korean_middle_korean.js +214 -0
- package/skills/korean-middle-korean/skill.json +8 -0
- package/skills/korean-patent-search/instruction.md +79 -0
- package/skills/korean-patent-search/scripts/patent_search.py +409 -0
- package/skills/korean-patent-search/skill.json +9 -0
- package/skills/korean-privacy-terms/instruction.md +128 -0
- package/skills/korean-privacy-terms/scripts/install.sh +108 -0
- package/skills/korean-privacy-terms/scripts/upstream.pin +1 -0
- package/skills/korean-privacy-terms/skill.json +8 -0
- package/skills/korean-scholarship-search/instruction.md +317 -0
- package/skills/korean-scholarship-search/references/report-format.md +40 -0
- package/skills/korean-scholarship-search/references/school-discovery.md +61 -0
- package/skills/korean-scholarship-search/references/search-clues.md +58 -0
- package/skills/korean-scholarship-search/references/source-patterns.md +67 -0
- package/skills/korean-scholarship-search/scripts/scholarship_filter.py +811 -0
- package/skills/korean-scholarship-search/scripts/test_scholarship_filter.py +224 -0
- package/skills/korean-scholarship-search/scripts/university_search_plan.py +148 -0
- package/skills/korean-scholarship-search/skill.json +8 -0
- package/skills/korean-slang-writing/instruction.md +181 -0
- package/skills/korean-slang-writing/scripts/_slang_http.py +91 -0
- package/skills/korean-slang-writing/scripts/slang_lookup.py +291 -0
- package/skills/korean-slang-writing/scripts/slang_search.py +284 -0
- package/skills/korean-slang-writing/skill.json +8 -0
- package/skills/korean-spell-check/instruction.md +105 -0
- package/skills/korean-spell-check/scripts/korean_spell_check.py +523 -0
- package/skills/korean-spell-check/skill.json +9 -0
- package/skills/korean-stock-search/instruction.md +194 -0
- package/skills/korean-stock-search/skill.json +10 -0
- package/skills/korean-transit-route/instruction.md +113 -0
- package/skills/korean-transit-route/skill.json +10 -0
- package/skills/kosis-stats/instruction.md +232 -0
- package/skills/kosis-stats/references/kosis-openapi-guide.md +171 -0
- package/skills/kosis-stats/scripts/run_kosis_stats.py +896 -0
- package/skills/kosis-stats/skill.json +9 -0
- package/skills/kr-whois-lookup/instruction.md +107 -0
- package/skills/kr-whois-lookup/skill.json +10 -0
- package/skills/kstartup-search/instruction.md +186 -0
- package/skills/kstartup-search/scripts/run_kstartup.py +424 -0
- package/skills/kstartup-search/skill.json +11 -0
- package/skills/ktx-booking/instruction.md +244 -0
- package/skills/ktx-booking/skill.json +10 -0
- package/skills/lck-analytics/instruction.md +192 -0
- package/skills/lck-analytics/scripts/_lib.js +103 -0
- package/skills/lck-analytics/scripts/analyze-live-game.js +52 -0
- package/skills/lck-analytics/scripts/build-match-report.js +44 -0
- package/skills/lck-analytics/scripts/sync-oracle.js +50 -0
- package/skills/lck-analytics/skill.json +9 -0
- package/skills/lh-notice-search/instruction.md +206 -0
- package/skills/lh-notice-search/skill.json +10 -0
- package/skills/library-book-search/instruction.md +139 -0
- package/skills/library-book-search/skill.json +9 -0
- package/skills/local-election-candidate-search/instruction.md +77 -0
- package/skills/local-election-candidate-search/skill.json +8 -0
- package/skills/localdata-business-status/instruction.md +64 -0
- package/skills/localdata-business-status/scripts/localdata_business_status.py +206 -0
- package/skills/localdata-business-status/skill.json +8 -0
- package/skills/lotto-results/instruction.md +80 -0
- package/skills/lotto-results/skill.json +8 -0
- package/skills/lovebug-report/instruction.md +185 -0
- package/skills/lovebug-report/skill.json +9 -0
- package/skills/market-kurly-search/instruction.md +125 -0
- package/skills/market-kurly-search/skill.json +8 -0
- package/skills/mfds-drug-safety/instruction.md +86 -0
- package/skills/mfds-drug-safety/scripts/mfds_drug_safety.py +184 -0
- package/skills/mfds-drug-safety/skill.json +10 -0
- package/skills/mfds-food-safety/instruction.md +126 -0
- package/skills/mfds-food-safety/scripts/mfds_food_safety.py +281 -0
- package/skills/mfds-food-safety/skill.json +10 -0
- package/skills/myrealtrip-search/instruction.md +239 -0
- package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +194 -0
- package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +99 -0
- package/skills/myrealtrip-search/skill.json +9 -0
- package/skills/naming-house/instruction.md +146 -0
- package/skills/naming-house/skill.json +8 -0
- package/skills/national-pension-workplace/instruction.md +64 -0
- package/skills/national-pension-workplace/scripts/national_pension_workplace.py +113 -0
- package/skills/national-pension-workplace/skill.json +10 -0
- package/skills/naver-ad-performance/instruction.md +108 -0
- package/skills/naver-ad-performance/scripts/naver_ad_performance.py +240 -0
- package/skills/naver-ad-performance/skill.json +9 -0
- package/skills/naver-blog-research/instruction.md +128 -0
- package/skills/naver-blog-research/scripts/_naver_http.py +58 -0
- package/skills/naver-blog-research/scripts/naver_download_images.py +233 -0
- package/skills/naver-blog-research/scripts/naver_read.py +256 -0
- package/skills/naver-blog-research/scripts/naver_search.py +192 -0
- package/skills/naver-blog-research/skill.json +8 -0
- package/skills/naver-news-search/instruction.md +103 -0
- package/skills/naver-news-search/skill.json +10 -0
- package/skills/naver-shopping-search/instruction.md +94 -0
- package/skills/naver-shopping-search/skill.json +11 -0
- package/skills/nhis-care-checkup-search/instruction.md +116 -0
- package/skills/nhis-care-checkup-search/skill.json +10 -0
- package/skills/nts-business-registration/instruction.md +115 -0
- package/skills/nts-business-registration/scripts/nts_business_registration.py +215 -0
- package/skills/nts-business-registration/skill.json +10 -0
- package/skills/nts-tax-delinquency/instruction.md +54 -0
- package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +150 -0
- package/skills/nts-tax-delinquency/skill.json +8 -0
- package/skills/ohou-today-deal/instruction.md +182 -0
- package/skills/ohou-today-deal/scripts/ohou_today_deal.py +369 -0
- package/skills/ohou-today-deal/skill.json +9 -0
- package/skills/olive-young-search/instruction.md +154 -0
- package/skills/olive-young-search/skill.json +8 -0
- package/skills/parking-lot-search/instruction.md +96 -0
- package/skills/parking-lot-search/skill.json +9 -0
- package/skills/popbill/instruction.md +146 -0
- package/skills/popbill/scripts/popbill_cli.py +284 -0
- package/skills/popbill/scripts/popbill_registry.py +54 -0
- package/skills/popbill/scripts/popbill_safety.py +37 -0
- package/skills/popbill/scripts/popbill_templates.py +71 -0
- package/skills/popbill/skill.json +9 -0
- package/skills/public-restroom-nearby/instruction.md +89 -0
- package/skills/public-restroom-nearby/skill.json +9 -0
- package/skills/real-estate-search/instruction.md +172 -0
- package/skills/real-estate-search/skill.json +10 -0
- package/skills/rhwp-advanced/instruction.md +145 -0
- package/skills/rhwp-advanced/skill.json +8 -0
- package/skills/rhwp-edit/instruction.md +153 -0
- package/skills/rhwp-edit/skill.json +8 -0
- package/skills/s2b-notice-search/instruction.md +66 -0
- package/skills/s2b-notice-search/skill.json +9 -0
- package/skills/saju-fortune/instruction.md +177 -0
- package/skills/saju-fortune/skill.json +8 -0
- package/skills/saramin-talent-search/instruction.md +119 -0
- package/skills/saramin-talent-search/skill.json +9 -0
- package/skills/seoul-bike/instruction.md +83 -0
- package/skills/seoul-bike/scripts/seoul_bike.py +247 -0
- package/skills/seoul-bike/skill.json +10 -0
- package/skills/seoul-density/instruction.md +109 -0
- package/skills/seoul-density/scripts/seoul_density.py +271 -0
- package/skills/seoul-density/skill.json +10 -0
- package/skills/seoul-subway-arrival/instruction.md +85 -0
- package/skills/seoul-subway-arrival/skill.json +9 -0
- package/skills/sh-notice-search/instruction.md +150 -0
- package/skills/sh-notice-search/skill.json +9 -0
- package/skills/srt-booking/instruction.md +181 -0
- package/skills/srt-booking/scripts/srt_booking.py +272 -0
- package/skills/srt-booking/scripts/srt_seats.py +156 -0
- package/skills/srt-booking/skill.json +20 -0
- package/skills/subway-lost-property/instruction.md +93 -0
- package/skills/subway-lost-property/scripts/subway_lost_property.py +244 -0
- package/skills/subway-lost-property/skill.json +8 -0
- package/skills/ticket-availability/instruction.md +175 -0
- package/skills/ticket-availability/scripts/ticket_availability.py +430 -0
- package/skills/ticket-availability/skill.json +9 -0
- package/skills/toss-securities/instruction.md +116 -0
- package/skills/toss-securities/skill.json +9 -0
- package/skills/used-car-price-search/instruction.md +109 -0
- package/skills/used-car-price-search/skill.json +8 -0
- package/skills/yebigun-training/instruction.md +177 -0
- package/skills/yebigun-training/skill.json +9 -0
- package/skills/zipcode-search/instruction.md +159 -0
- package/skills/zipcode-search/scripts/zipcode_search.py +150 -0
- package/skills/zipcode-search/skill.json +8 -0
- package/src/assemble.js +134 -0
- package/src/detect.js +18 -0
- package/templates/action-account.md +4 -0
- package/templates/action-booking.md +5 -0
- package/templates/action-commerce.md +5 -0
- package/templates/action-communication.md +5 -0
- package/templates/action-submission.md +5 -0
- package/templates/browser.md +4 -0
- package/templates/core.md +5 -0
- package/templates/hard-boundary.md +3 -0
- package/templates/local.md +2 -0
- package/templates/lookup.md +2 -0
- package/templates/proxy.md +2 -0
- package/templates/vault.md +4 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# 식품 안전 체크
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
식약처/식품안전나라 공개 표면을 **`k-skill-proxy` 경유**로 조회해 식품 안전 정보를 확인한다.
|
|
6
|
+
|
|
7
|
+
조회 가능한 공식 데이터:
|
|
8
|
+
- **건강기능식품 기능성 원료 인정현황** (I-0040) — 원료가 공식 인정됐는지, 1일 섭취량, 섭취시 주의사항
|
|
9
|
+
- **건강기능식품 개별인정형 정보** (I-0050) — 개별 인정 원료의 기능성, 섭취량 상·하한, 주의사항
|
|
10
|
+
- **건강기능식품 품목제조 신고사항** (I0030) — 신고된 제품의 원재료, 기능성, 섭취 주의사항, 기준규격 (고시형 원료 포함)
|
|
11
|
+
- **검사부적합 현황(국내)** (I2620) — 국내 유통 식품 부적합 판정 이력
|
|
12
|
+
- **부적합 식품 목록** — 공공데이터포털 경유
|
|
13
|
+
- **회수·판매중지 공개 목록** (I0490) — 식품안전나라 경유
|
|
14
|
+
|
|
15
|
+
하지만 사용자가 복통, 설사, 발진 같은 증상을 말하면 **바로 단정하지 말고 먼저 되묻는다.**
|
|
16
|
+
|
|
17
|
+
- 누가 먹었는지 (본인/아이/임산부/고령자)
|
|
18
|
+
- 무엇을 언제 얼마나 먹었는지
|
|
19
|
+
- 같이 먹은 음식/술/약
|
|
20
|
+
- 현재 증상과 시작 시점
|
|
21
|
+
- 기저질환, 임신, 알레르기
|
|
22
|
+
- red flag (`혈변`, `탈수`, `호흡곤란`, `의식저하`, `심한 복통/고열`)
|
|
23
|
+
|
|
24
|
+
red flag 가 있으면 식품 조회보다 **즉시 응급실·119·의료진 안내**가 우선이다.
|
|
25
|
+
|
|
26
|
+
## When to use
|
|
27
|
+
|
|
28
|
+
- "이 음식 먹어도 괜찮니?" → 건강기능식품 원료 인정현황 + 검사부적합 조회
|
|
29
|
+
- "차전자피를 먹어도 되나?" → 기능성 원료 인정현황에서 1일 섭취량·주의사항 확인
|
|
30
|
+
- "이 김밥 먹고 배가 아픈데 회수 이력 있나?" → 회수·부적합 목록 조회
|
|
31
|
+
- "식약처 공식 부적합 식품 목록에서 제품명 확인해줘"
|
|
32
|
+
- "식품안전나라 공개 회수 목록에서 업체명으로 찾아줘"
|
|
33
|
+
|
|
34
|
+
## Prerequisites
|
|
35
|
+
|
|
36
|
+
- 인터넷 연결
|
|
37
|
+
- `python3`
|
|
38
|
+
- 설치된 skill payload 안에 `scripts/mfds_food_safety.py` helper 포함
|
|
39
|
+
- `k-skill-proxy`의 food-safety route들이 있는 hosted/self-host 프록시에 접근 가능할 것
|
|
40
|
+
|
|
41
|
+
## Credential requirements
|
|
42
|
+
|
|
43
|
+
- 사용자 측 **필수** 시크릿 없음.
|
|
44
|
+
- `KSKILL_PROXY_BASE_URL` — self-host·별도 프록시를 쓸 때만 설정. 비우면 기본 hosted `https://k-skill-proxy.nomadamas.org` 를 사용한다.
|
|
45
|
+
- `DATA_GO_KR_API_KEY` 는 **프록시 운영 서버** 환경에서 부적합 식품 live 조회용으로만 둔다.
|
|
46
|
+
- `FOODSAFETYKOREA_API_KEY` 는 **프록시 운영 서버** 환경에서 식품안전나라 live 조회용으로만 둔다. 없으면 public sample feed로 fallback 할 수 있다.
|
|
47
|
+
- FOODSAFETYKOREA_API_KEY 발급: `https://www.foodsafetykorea.go.kr` 에서 회원가입 후 OpenAPI 이용신청. 하나의 키로 I-0040, I-0050, I0490, I2620 등 모든 서비스 호출 가능.
|
|
48
|
+
|
|
49
|
+
## Mandatory interview first
|
|
50
|
+
|
|
51
|
+
증상/섭취상황이 언급되면 결론을 말하기 전에 먼저 되묻는다.
|
|
52
|
+
|
|
53
|
+
권장 첫 질문 예시:
|
|
54
|
+
|
|
55
|
+
- `누가 무엇을 언제 얼마나 먹었는지, 지금 복통/구토/설사/발진 같은 증상이 있는지 먼저 알려주세요.`
|
|
56
|
+
- `호흡곤란, 혈변, 심한 탈수, 의식저하, 심한 복통/고열이 있으면 즉시 응급실이나 119가 우선입니다.`
|
|
57
|
+
|
|
58
|
+
## Official surfaces
|
|
59
|
+
|
|
60
|
+
- 공공데이터포털 문서: `https://www.data.go.kr/data/15056516/openapi.do`
|
|
61
|
+
- 부적합 식품 endpoint: `https://apis.data.go.kr/1471000/PrsecImproptFoodInfoService03/getPrsecImproptFoodList01`
|
|
62
|
+
- 식품안전나라 회수·판매중지 (I0490): `https://www.foodsafetykorea.go.kr/api/openApiInfo.do?menu_grp=MENU_GRP31&menu_no=661&show_cnt=10&start_idx=1&svc_no=I0490&svc_type_cd=API_TYPE06`
|
|
63
|
+
- 식품안전나라 회수 sample: `https://openapi.foodsafetykorea.go.kr/api/sample/I0490/json/1/5`
|
|
64
|
+
- 건강기능식품 기능성 원료 인정현황 (I-0040): `https://www.foodsafetykorea.go.kr/api/openApiInfo.do?menu_grp=MENU_GRP31&menu_no=661&show_cnt=10&start_idx=1&svc_no=I-0040&svc_type_cd=API_TYPE06`
|
|
65
|
+
- 건강기능식품 기능성 원료 sample: `https://openapi.foodsafetykorea.go.kr/api/sample/I-0040/json/1/5`
|
|
66
|
+
- 건강기능식품 개별인정형 정보 (I-0050): `https://www.foodsafetykorea.go.kr/api/openApiInfo.do?menu_grp=MENU_GRP31&menu_no=661&show_cnt=10&start_idx=1&svc_no=I-0050&svc_type_cd=API_TYPE06`
|
|
67
|
+
- 건강기능식품 개별인정형 sample: `https://openapi.foodsafetykorea.go.kr/api/sample/I-0050/json/1/5`
|
|
68
|
+
- 검사부적합(국내) (I2620): `https://www.foodsafetykorea.go.kr/api/openApiInfo.do?menu_grp=MENU_GRP31&menu_no=661&show_cnt=10&start_idx=1&svc_no=I2620&svc_type_cd=API_TYPE06`
|
|
69
|
+
- 검사부적합(국내) sample: `https://openapi.foodsafetykorea.go.kr/api/sample/I2620/json/1/5`
|
|
70
|
+
- 건강기능식품 품목제조 신고사항 (I0030): `https://www.foodsafetykorea.go.kr/api/openApiInfo.do?menu_grp=MENU_GRP31&menu_no=661&show_cnt=10&start_idx=1&svc_no=I0030&svc_type_cd=API_TYPE06`
|
|
71
|
+
- 건강기능식품 품목제조 신고 sample: `https://openapi.foodsafetykorea.go.kr/api/sample/I0030/json/1/5`
|
|
72
|
+
- 프록시 route: `GET /v1/mfds/food-safety/search`
|
|
73
|
+
- 프록시 route: `GET /v1/mfds/food-safety/health-food-ingredient`
|
|
74
|
+
- 프록시 route: `GET /v1/mfds/food-safety/product-report`
|
|
75
|
+
- 프록시 route: `GET /v1/mfds/food-safety/inspection-fail`
|
|
76
|
+
|
|
77
|
+
## Workflow
|
|
78
|
+
|
|
79
|
+
1. 증상/섭취상황이 있으면 인터뷰를 먼저 진행한다.
|
|
80
|
+
2. red flag 가 있으면 즉시 응급 안내로 전환한다.
|
|
81
|
+
3. "이거 먹어도 되나?" 류 질문:
|
|
82
|
+
- `/v1/mfds/food-safety/product-report` 로 건강기능식품 품목제조 신고사항을 조회한다 (원재료, 기능성, 섭취 주의사항, 기준규격). 고시형 원료(차전자피 등)는 여기서 확인.
|
|
83
|
+
- `/v1/mfds/food-safety/health-food-ingredient` 로 건강기능식품 기능성 원료 인정현황을 조회한다 (개별인정형 원료의 1일 섭취량, 주의사항).
|
|
84
|
+
- `/v1/mfds/food-safety/inspection-fail` 로 국내 검사부적합 이력을 확인한다.
|
|
85
|
+
- `/v1/mfds/food-safety/search` 로 회수·부적합 공개 목록도 함께 조회한다.
|
|
86
|
+
4. 제품명, 업체명, 기능성, 섭취량, 주의사항, 부적합 사유를 짧게 정리하고, 먹어도 되는지 단정하지 않는다.
|
|
87
|
+
5. 프록시가 `FOODSAFETYKOREA_API_KEY` 없이 동작 중이면 결과가 sample feed 기반일 수 있음을 warnings 로 확인한다.
|
|
88
|
+
|
|
89
|
+
## CLI examples
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python3 scripts/mfds_food_safety.py interview \
|
|
93
|
+
--question "이 김밥 먹어도 되나요?" \
|
|
94
|
+
--symptoms "복통과 설사"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python3 scripts/mfds_food_safety.py search --query "김밥" --limit 5
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
python3 scripts/mfds_food_safety.py product-report --query "차전자피" --limit 5
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python3 scripts/mfds_food_safety.py health-food-ingredient --query "스타놀" --limit 5
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python3 scripts/mfds_food_safety.py inspection-fail --query "쪽갓" --limit 5
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Response policy
|
|
114
|
+
|
|
115
|
+
- 이 스킬은 **직접 진단**을 하지 않는다.
|
|
116
|
+
- 이 스킬은 **식중독 진단**이나 **섭취 허가/금지의 최종 판정**을 하지 않는다.
|
|
117
|
+
- 공식 공개 목록에 있는 사실만 전달한다.
|
|
118
|
+
- 증상이 있는 질문은 인터뷰 없이 바로 답하지 않는다.
|
|
119
|
+
- red flag 또는 고위험군이면 의료진 상담을 우선 권고한다.
|
|
120
|
+
|
|
121
|
+
## Done when
|
|
122
|
+
|
|
123
|
+
- 증상 또는 섭취상황을 먼저 되물었다.
|
|
124
|
+
- red flag 여부를 확인했다.
|
|
125
|
+
- 프록시 route를 통해 공식 공개 목록에서 제품명 또는 업체명 기준 결과를 최소 1건 이상 찾았거나, 없다고 분명히 알렸다.
|
|
126
|
+
- 제품명, 업체명, 공개사유/부적합 사유, 공개일자를 포함한 요약을 제공했다.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
import urllib.error
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import urllib.request
|
|
11
|
+
from html import unescape
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
PROXY_BASE_URL_ENV_VAR = "KSKILL_PROXY_BASE_URL"
|
|
15
|
+
DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
|
|
16
|
+
PROXY_DOWN_MSG = "설정된 k-skill-proxy 서버가 응답하지 않습니다. 잠시 후 재시도하거나 운영자에게 문의하세요."
|
|
17
|
+
PROXY_KEY_NOT_CONFIGURED_MSG = "k-skill-proxy에 필요한 API 키가 설정되어 있지 않습니다. 운영자에게 문의하세요."
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ApiError(RuntimeError):
|
|
21
|
+
def __init__(self, message: str, *, status_code: int | None = None, url: str | None = None):
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.status_code = status_code
|
|
24
|
+
self.url = url
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def summarize_text(value: Any) -> str:
|
|
28
|
+
if value is None:
|
|
29
|
+
return ""
|
|
30
|
+
text = unescape(str(value))
|
|
31
|
+
text = re.sub(r"<[^>]+>", " ", text)
|
|
32
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
33
|
+
return text
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def resolve_proxy_base_url(explicit_base_url: str | None = None, env: dict[str, str] | None = None) -> str:
|
|
37
|
+
env = env or os.environ
|
|
38
|
+
candidate = summarize_text(explicit_base_url or env.get(PROXY_BASE_URL_ENV_VAR))
|
|
39
|
+
if candidate.casefold() in {"off", "false", "0", "disable", "disabled", "none"}:
|
|
40
|
+
raise ValueError("KSKILL_PROXY_BASE_URL 가 비활성화되어 있습니다.")
|
|
41
|
+
if candidate and candidate != "replace-me":
|
|
42
|
+
return candidate.rstrip("/")
|
|
43
|
+
return DEFAULT_PROXY_BASE_URL
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_food_interview(question: str | None = None, symptoms: str | None = None) -> dict[str, Any]:
|
|
47
|
+
return {
|
|
48
|
+
"domain": "food",
|
|
49
|
+
"question": summarize_text(question),
|
|
50
|
+
"symptoms": summarize_text(symptoms),
|
|
51
|
+
"must_ask": [
|
|
52
|
+
"누가 먹었거나 먹으려는지 알려주세요. (본인/아이/임산부/고령자)",
|
|
53
|
+
"무엇을 언제 먹었는지, 얼마나 먹었는지 알려주세요.",
|
|
54
|
+
"같이 먹은 음식이나 술, 복용 중인 약이 있는지 알려주세요.",
|
|
55
|
+
"복통·구토·설사·발진 같은 증상이 언제부터 시작됐는지 알려주세요.",
|
|
56
|
+
"기저질환, 임신 여부, 알레르기 여부를 알려주세요.",
|
|
57
|
+
],
|
|
58
|
+
"red_flags": [
|
|
59
|
+
"호흡곤란, 입술·혀 붓기 같은 급성 알레르기 반응",
|
|
60
|
+
"혈변 또는 검은변",
|
|
61
|
+
"심한 탈수, 소변 감소, 계속되는 구토",
|
|
62
|
+
"의식저하, 고열, 심한 복통",
|
|
63
|
+
],
|
|
64
|
+
"urgent_action": "red flag 가 있으면 식품 조회보다 즉시 응급실·119·의료진 연결을 우선하세요.",
|
|
65
|
+
"policy": "이 helper 는 공식 식품 안전정보 조회 전에 반드시 되묻기 흐름을 제공하며, 먹어도 되는지 단정하지 않습니다.",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def normalize_food_recall_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
70
|
+
return {
|
|
71
|
+
"source": "foodsafetykorea_recall",
|
|
72
|
+
"product_name": summarize_text(row.get("product_name") or row.get("PRDLST_NM") or row.get("PRDTNM")),
|
|
73
|
+
"company_name": summarize_text(row.get("company_name") or row.get("BSSH_NM") or row.get("BSSHNM")),
|
|
74
|
+
"reason": summarize_text(row.get("reason") or row.get("RTRVLPRVNS")),
|
|
75
|
+
"created_at": summarize_text(row.get("created_at") or row.get("CRET_DTM")),
|
|
76
|
+
"distribution_deadline": summarize_text(row.get("distribution_deadline") or row.get("DISTBTMLMT")),
|
|
77
|
+
"category": summarize_text(row.get("category") or row.get("PRDLST_TYPE") or row.get("PRDLST_CD_NM")),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def normalize_improper_food_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
82
|
+
reason_parts = [
|
|
83
|
+
summarize_text(item.get("reason") or item.get("IMPROPT_ITM")),
|
|
84
|
+
summarize_text(item.get("INSPCT_RESULT")),
|
|
85
|
+
]
|
|
86
|
+
return {
|
|
87
|
+
"source": "mfds_improper_food",
|
|
88
|
+
"product_name": summarize_text(item.get("product_name") or item.get("PRDUCT")),
|
|
89
|
+
"company_name": summarize_text(item.get("company_name") or item.get("ENTRPS")),
|
|
90
|
+
"reason": "; ".join(part for part in reason_parts if part),
|
|
91
|
+
"created_at": summarize_text(item.get("created_at") or item.get("REGIST_DT")),
|
|
92
|
+
"category": summarize_text(item.get("category") or item.get("FOOD_TY")),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def filter_food_items(items: list[dict[str, Any]], query: str) -> list[dict[str, Any]]:
|
|
97
|
+
needle = summarize_text(query).casefold()
|
|
98
|
+
if not needle:
|
|
99
|
+
return items
|
|
100
|
+
|
|
101
|
+
product_matches = [item for item in items if needle in summarize_text(item.get("product_name")).casefold()]
|
|
102
|
+
if product_matches:
|
|
103
|
+
return product_matches
|
|
104
|
+
|
|
105
|
+
company_matches = [item for item in items if needle in summarize_text(item.get("company_name")).casefold()]
|
|
106
|
+
if company_matches:
|
|
107
|
+
return company_matches
|
|
108
|
+
|
|
109
|
+
return [item for item in items if needle in summarize_text(item.get("reason")).casefold()]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def read_json_response(request: urllib.request.Request | str) -> dict[str, Any]:
|
|
113
|
+
try:
|
|
114
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
115
|
+
return json.loads(response.read().decode("utf-8"))
|
|
116
|
+
except urllib.error.HTTPError as error:
|
|
117
|
+
body = error.read().decode("utf-8", errors="replace")
|
|
118
|
+
try:
|
|
119
|
+
payload = json.loads(body)
|
|
120
|
+
except json.JSONDecodeError:
|
|
121
|
+
payload = None
|
|
122
|
+
if error.code == 503 and isinstance(payload, dict) and payload.get("error") == "upstream_not_configured":
|
|
123
|
+
raise ApiError(PROXY_KEY_NOT_CONFIGURED_MSG, status_code=error.code, url=getattr(error, "url", None)) from error
|
|
124
|
+
|
|
125
|
+
if isinstance(payload, dict) and payload.get("message"):
|
|
126
|
+
raise ApiError(str(payload["message"]), status_code=error.code, url=getattr(error, "url", None)) from error
|
|
127
|
+
raise ApiError(
|
|
128
|
+
f"MFDS food proxy request failed with HTTP {error.code}",
|
|
129
|
+
status_code=error.code,
|
|
130
|
+
url=getattr(error, "url", None),
|
|
131
|
+
) from error
|
|
132
|
+
except urllib.error.URLError as error:
|
|
133
|
+
raise ApiError(f"{PROXY_DOWN_MSG} (상세: {error.reason})") from error
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def search_food_safety(
|
|
137
|
+
query: str,
|
|
138
|
+
*,
|
|
139
|
+
limit: int = 10,
|
|
140
|
+
base_url: str | None = None,
|
|
141
|
+
request_json: Any = read_json_response,
|
|
142
|
+
) -> dict[str, Any]:
|
|
143
|
+
resolved_base_url = resolve_proxy_base_url(base_url)
|
|
144
|
+
url = f"{resolved_base_url}/v1/mfds/food-safety/search"
|
|
145
|
+
params = urllib.parse.urlencode({"query": query, "limit": str(limit)})
|
|
146
|
+
request = urllib.request.Request(
|
|
147
|
+
f"{url}?{params}",
|
|
148
|
+
headers={"Accept": "application/json", "User-Agent": "k-skill-mfds/1.0"},
|
|
149
|
+
)
|
|
150
|
+
return request_json(request)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def search_health_food_ingredient(
|
|
154
|
+
query: str,
|
|
155
|
+
*,
|
|
156
|
+
limit: int = 10,
|
|
157
|
+
base_url: str | None = None,
|
|
158
|
+
request_json: Any = read_json_response,
|
|
159
|
+
) -> dict[str, Any]:
|
|
160
|
+
resolved_base_url = resolve_proxy_base_url(base_url)
|
|
161
|
+
url = f"{resolved_base_url}/v1/mfds/food-safety/health-food-ingredient"
|
|
162
|
+
params = urllib.parse.urlencode({"query": query, "limit": str(limit)})
|
|
163
|
+
request = urllib.request.Request(
|
|
164
|
+
f"{url}?{params}",
|
|
165
|
+
headers={"Accept": "application/json", "User-Agent": "k-skill-mfds/1.0"},
|
|
166
|
+
)
|
|
167
|
+
return request_json(request)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def search_product_report(
|
|
171
|
+
query: str,
|
|
172
|
+
*,
|
|
173
|
+
limit: int = 10,
|
|
174
|
+
base_url: str | None = None,
|
|
175
|
+
request_json: Any = read_json_response,
|
|
176
|
+
) -> dict[str, Any]:
|
|
177
|
+
resolved_base_url = resolve_proxy_base_url(base_url)
|
|
178
|
+
url = f"{resolved_base_url}/v1/mfds/food-safety/product-report"
|
|
179
|
+
params = urllib.parse.urlencode({"query": query, "limit": str(limit)})
|
|
180
|
+
request = urllib.request.Request(
|
|
181
|
+
f"{url}?{params}",
|
|
182
|
+
headers={"Accept": "application/json", "User-Agent": "k-skill-mfds/1.0"},
|
|
183
|
+
)
|
|
184
|
+
return request_json(request)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def search_inspection_fail(
|
|
188
|
+
query: str,
|
|
189
|
+
*,
|
|
190
|
+
limit: int = 10,
|
|
191
|
+
base_url: str | None = None,
|
|
192
|
+
request_json: Any = read_json_response,
|
|
193
|
+
) -> dict[str, Any]:
|
|
194
|
+
resolved_base_url = resolve_proxy_base_url(base_url)
|
|
195
|
+
url = f"{resolved_base_url}/v1/mfds/food-safety/inspection-fail"
|
|
196
|
+
params = urllib.parse.urlencode({"query": query, "limit": str(limit)})
|
|
197
|
+
request = urllib.request.Request(
|
|
198
|
+
f"{url}?{params}",
|
|
199
|
+
headers={"Accept": "application/json", "User-Agent": "k-skill-mfds/1.0"},
|
|
200
|
+
)
|
|
201
|
+
return request_json(request)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
205
|
+
parser = argparse.ArgumentParser(description="MFDS food-safety helper")
|
|
206
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
207
|
+
|
|
208
|
+
interview = subparsers.add_parser("interview", help="print the mandatory symptom follow-up interview")
|
|
209
|
+
interview.add_argument("--question", default="")
|
|
210
|
+
interview.add_argument("--symptoms", default="")
|
|
211
|
+
|
|
212
|
+
search = subparsers.add_parser("search", help="search official food-safety records through k-skill-proxy")
|
|
213
|
+
search.add_argument("--query", required=True)
|
|
214
|
+
search.add_argument("--limit", type=int, default=10)
|
|
215
|
+
search.add_argument("--proxy-base-url")
|
|
216
|
+
|
|
217
|
+
product_report = subparsers.add_parser("product-report", help="search health food product manufacturing reports")
|
|
218
|
+
product_report.add_argument("--query", required=True)
|
|
219
|
+
product_report.add_argument("--limit", type=int, default=10)
|
|
220
|
+
product_report.add_argument("--proxy-base-url")
|
|
221
|
+
|
|
222
|
+
ingredient = subparsers.add_parser("health-food-ingredient", help="search health food ingredient recognition status")
|
|
223
|
+
ingredient.add_argument("--query", required=True)
|
|
224
|
+
ingredient.add_argument("--limit", type=int, default=10)
|
|
225
|
+
ingredient.add_argument("--proxy-base-url")
|
|
226
|
+
|
|
227
|
+
inspection = subparsers.add_parser("inspection-fail", help="search domestic inspection failure records")
|
|
228
|
+
inspection.add_argument("--query", required=True)
|
|
229
|
+
inspection.add_argument("--limit", type=int, default=10)
|
|
230
|
+
inspection.add_argument("--proxy-base-url")
|
|
231
|
+
return parser
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def main(argv: list[str] | None = None) -> int:
|
|
235
|
+
args = build_parser().parse_args(argv)
|
|
236
|
+
|
|
237
|
+
if args.command == "interview":
|
|
238
|
+
print(json.dumps(build_food_interview(question=args.question, symptoms=args.symptoms), ensure_ascii=False, indent=2))
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
if args.command == "search":
|
|
242
|
+
try:
|
|
243
|
+
payload = search_food_safety(args.query, limit=args.limit, base_url=args.proxy_base_url)
|
|
244
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
245
|
+
return 0
|
|
246
|
+
except (ValueError, ApiError) as error:
|
|
247
|
+
print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
|
|
248
|
+
return 1
|
|
249
|
+
|
|
250
|
+
if args.command == "product-report":
|
|
251
|
+
try:
|
|
252
|
+
payload = search_product_report(args.query, limit=args.limit, base_url=args.proxy_base_url)
|
|
253
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
254
|
+
return 0
|
|
255
|
+
except (ValueError, ApiError) as error:
|
|
256
|
+
print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
|
|
257
|
+
return 1
|
|
258
|
+
|
|
259
|
+
if args.command == "health-food-ingredient":
|
|
260
|
+
try:
|
|
261
|
+
payload = search_health_food_ingredient(args.query, limit=args.limit, base_url=args.proxy_base_url)
|
|
262
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
263
|
+
return 0
|
|
264
|
+
except (ValueError, ApiError) as error:
|
|
265
|
+
print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
|
|
266
|
+
return 1
|
|
267
|
+
|
|
268
|
+
if args.command == "inspection-fail":
|
|
269
|
+
try:
|
|
270
|
+
payload = search_inspection_fail(args.query, limit=args.limit, base_url=args.proxy_base_url)
|
|
271
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
272
|
+
return 0
|
|
273
|
+
except (ValueError, ApiError) as error:
|
|
274
|
+
print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
|
|
275
|
+
return 1
|
|
276
|
+
|
|
277
|
+
return 1
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if __name__ == "__main__":
|
|
281
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mfds-food-safety",
|
|
3
|
+
"description": "식약처/식품안전나라 공개 표면을 k-skill-proxy 경유로 조회하기 전에 증상·섭취상황을 반드시 되묻는 인터뷰형 식품 안전 체크 스킬.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"proxy",
|
|
6
|
+
"vault",
|
|
7
|
+
"lookup"
|
|
8
|
+
],
|
|
9
|
+
"frontmatter": "name: mfds-food-safety\ndescription: 식약처/식품안전나라 공개 표면을 k-skill-proxy 경유로 조회하기 전에 증상·섭취상황을 반드시 되묻는 인터뷰형 식품 안전 체크 스킬.\nlicense: MIT\nmetadata:\n category: public-health\n locale: ko-KR\n phase: v1"
|
|
10
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# 마이리얼트립 검색
|
|
2
|
+
|
|
3
|
+
## 이 스킬이 하는 일
|
|
4
|
+
|
|
5
|
+
마이리얼트립 공식 개발자센터의 MCP 문서에 공개된 엔드포인트를 사용해 여행 검색을 수행한다.
|
|
6
|
+
|
|
7
|
+
- MCP 엔드포인트: `https://mcp-servers.myrealtrip.com/mcp`
|
|
8
|
+
- 문서: <https://docs.myrealtrip.com/#/api/mcp/overview>
|
|
9
|
+
- 연결 가이드: <https://docs.myrealtrip.com/#/api/mcp/setup>
|
|
10
|
+
- 인증: 문서 기준 별도 인증 없음
|
|
11
|
+
|
|
12
|
+
지원 범위:
|
|
13
|
+
|
|
14
|
+
- 국내선/국제선 항공권 검색
|
|
15
|
+
- 항공권 날짜별 최저가 캘린더와 프로모션 항공사 조회
|
|
16
|
+
- 숙소 검색 및 상세/객실/리뷰/편의시설 조회
|
|
17
|
+
- 투어·티켓·액티비티 검색, 상세, 날짜별 옵션/가격/예약 가능 여부 조회
|
|
18
|
+
- 현재 한국 시간(KST) 조회
|
|
19
|
+
|
|
20
|
+
## 동작 방식
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Claude Code / Codex / Hermes Agent
|
|
24
|
+
→ myrealtrip-search/scripts/myrealtrip_mcp.py
|
|
25
|
+
→ https://mcp-servers.myrealtrip.com/mcp (Streamable HTTP MCP)
|
|
26
|
+
├─ searchDomesticFlights / searchInternationalFlights
|
|
27
|
+
├─ flightsFareCalendar / getPromotionAirlines
|
|
28
|
+
├─ searchStays / getStayDetail
|
|
29
|
+
├─ getCategoryList / searchTnas / getTnaDetail / getTnaOptions
|
|
30
|
+
└─ getCurrentTime
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
이 스킬은 화면 scraping이 아니라 마이리얼트립이 공개한 MCP 서버를 직접 호출한다. 따라서 DOM 선택자나 브라우저 자동화에 의존하지 않는다.
|
|
34
|
+
|
|
35
|
+
## 사용할 때
|
|
36
|
+
|
|
37
|
+
- "마이리얼트립에서 제주 항공권 찾아줘"
|
|
38
|
+
- "다음 주 오사카 가는 최저가 항공권 봐줘"
|
|
39
|
+
- "부산 해운대 호텔 2박 검색해줘"
|
|
40
|
+
- "이 호텔 객실 타입이랑 취소정책 자세히 봐줘"
|
|
41
|
+
- "오사카 유니버설 스튜디오 티켓 가격 확인해줘"
|
|
42
|
+
- "도쿄 디즈니랜드 날짜별 옵션 확인해줘"
|
|
43
|
+
- "항공권 특가 항공사 있어?"
|
|
44
|
+
|
|
45
|
+
## 사용하지 않을 때
|
|
46
|
+
|
|
47
|
+
- 실제 예약/결제/로그인이 필요한 경우
|
|
48
|
+
- 마이리얼트립 계정의 예약 내역이나 개인화 데이터가 필요한 경우
|
|
49
|
+
- 항공권·숙소·투어 외의 파트너 API 수익/예약 내역 조회가 목적일 때
|
|
50
|
+
- 가격·재고를 법적/회계적 증빙 수준으로 확정해야 할 때
|
|
51
|
+
|
|
52
|
+
## 준비 사항
|
|
53
|
+
|
|
54
|
+
Python MCP SDK가 필요하다. 없으면 설치한다.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python3 -m pip install mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
기본 엔드포인트는 공식 문서의 URL이다. 필요할 때만 환경변수로 바꾼다.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
export MYREALTRIP_MCP_ENDPOINT="https://mcp-servers.myrealtrip.com/mcp"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Hermes Agent에 MCP 서버로 직접 등록하려면 다음 설정을 쓸 수 있다.
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
mcp_servers:
|
|
70
|
+
myrealtrip:
|
|
71
|
+
url: "https://mcp-servers.myrealtrip.com/mcp"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
CLI별 공식 연결 명령은 문서 기준 다음과 같다.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
claude mcp add --transport http myrealtrip https://mcp-servers.myrealtrip.com/mcp
|
|
78
|
+
codex mcp add myrealtrip --url https://mcp-servers.myrealtrip.com/mcp
|
|
79
|
+
gemini mcp add -t http -s user myrealtrip https://mcp-servers.myrealtrip.com/mcp
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 사용 흐름
|
|
83
|
+
|
|
84
|
+
### 1. 요청 유형을 분류한다
|
|
85
|
+
|
|
86
|
+
- 국내 항공: 출발지와 도착지가 모두 한국 도시/공항이면 `searchDomesticFlights`
|
|
87
|
+
- 국제 항공: 목적지가 해외면 `searchInternationalFlights`
|
|
88
|
+
- 날짜 유연 항공 최저가: `flightsFareCalendar` 후 실제 검색 도구로 재확인
|
|
89
|
+
- 숙소 목록: `searchStays`
|
|
90
|
+
- 특정 숙소 상세/객실/리뷰/편의시설: `searchStays`로 `gid` 확보 후 `getStayDetail`
|
|
91
|
+
- 투어·티켓·액티비티 목록: `searchTnas`
|
|
92
|
+
- 도시별 카테고리 필터가 필요하면 `getCategoryList` 후 `searchTnas`
|
|
93
|
+
- 특정 투어 상세: `searchTnas` 결과의 `gid`, `url`로 `getTnaDetail`
|
|
94
|
+
- 날짜별 실제 옵션/가격/가능 여부: `getTnaOptions`
|
|
95
|
+
|
|
96
|
+
### 2. 도구 목록과 입력 스키마를 확인한다
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py tools
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. 항공권 검색
|
|
103
|
+
|
|
104
|
+
국내선은 두 공항이 모두 한국일 때만 사용한다.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call searchDomesticFlights \
|
|
108
|
+
--arg origin=GMP \
|
|
109
|
+
--arg destination=CJU \
|
|
110
|
+
--arg departDate=2026-05-20 \
|
|
111
|
+
--arg maxResults=10
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
국제선은 해외 목적지일 때 사용한다. 사용자가 출발 공항을 말하지 않으면 인천(`ICN`)을 기본값으로 둔다.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call searchInternationalFlights \
|
|
118
|
+
--json '{"tripType":"ROUND_TRIP","origin":"ICN","destination":"KIX","departDate":"2026-06-10","returnDate":"2026-06-14","passengers":{"adults":1,"children":0,"infants":0},"maxResults":5}'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
날짜가 유동적인 최저가 요청은 캘린더를 먼저 본다. 캘린더 가격은 캐시/추정값이므로, 최종 답변 전에 실제 검색 도구로 후보 날짜를 다시 확인한다.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call flightsFareCalendar \
|
|
125
|
+
--arg from=ICN \
|
|
126
|
+
--arg to=NRT \
|
|
127
|
+
--arg departureDate=2026-06-01 \
|
|
128
|
+
--arg period=4 \
|
|
129
|
+
--arg maxResults=30
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
특가/할인 항공사 요청은 프로모션 항공사를 먼저 확인한다.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call getPromotionAirlines
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 4. 숙소 검색과 상세 조회
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call searchStays \
|
|
142
|
+
--json '{"keyword":"부산 해운대","checkIn":"2026-06-10","checkOut":"2026-06-12","adultCount":2,"childCount":0,"isDomestic":true,"order":"recommended","minReviewRating":4.0}'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
사용자가 특정 숙소의 객실, 가격, 편의시설, 리뷰, 취소정책을 묻거나 "자세히"를 요청하면 `searchStays` 결과의 `gid`로 상세를 이어서 호출한다.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call getStayDetail \
|
|
149
|
+
--json '{"gid":123456,"checkIn":"2026-06-10","checkOut":"2026-06-12","adultCount":2,"childCount":0}'
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 5. 투어/티켓/액티비티 검색과 옵션 확인
|
|
153
|
+
|
|
154
|
+
도시별 카테고리 필터를 쓰려면 카테고리 값을 추측하지 말고 먼저 조회한다.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call getCategoryList --arg city=Osaka
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
검색어는 한국어가 가장 잘 맞는다.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call searchTnas \
|
|
164
|
+
--arg query="오사카 유니버설 스튜디오" \
|
|
165
|
+
--arg perPage=5
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
상세는 검색 결과의 `gid`와 `url`을 같이 넘긴다.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call getTnaDetail \
|
|
172
|
+
--json '{"gid":"123456","url":"https://www.myrealtrip.com/offers/123456"}'
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
날짜별 예약 가능 여부와 실제 가격은 `getTnaOptions`로 확인한다.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python3 myrealtrip-search/scripts/myrealtrip_mcp.py call getTnaOptions \
|
|
179
|
+
--json '{"gid":"123456","url":"https://www.myrealtrip.com/offers/123456","selectedDate":"2026-06-10"}'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 사용 가능한 도구
|
|
183
|
+
|
|
184
|
+
| 도구명 | 용도 | 주요 입력 |
|
|
185
|
+
|--------|------|-----------|
|
|
186
|
+
| `getCurrentTime` | 현재 KST 조회 | 없음 |
|
|
187
|
+
| `searchDomesticFlights` | 국내선 항공권 검색 | `origin`, `destination`, `departDate`, `returnDate`, `passengers`, `cabinClass`, `maxResults` |
|
|
188
|
+
| `searchInternationalFlights` | 국제선 항공권 검색 | `origin`, `destination`, `departDate`, `returnDate`, `passengers`, `cabinClass`, `directFlightOnly`, `preferredAirline` |
|
|
189
|
+
| `flightsFareCalendar` | 날짜별 최저가 캘린더 | `from`, `to`, `departureDate`, `period`, `international`, `transfer` |
|
|
190
|
+
| `getPromotionAirlines` | 프로모션 항공사 조회 | 없음 |
|
|
191
|
+
| `searchStays` | 숙소 목록 검색 | `keyword`, `checkIn`, `checkOut`, `adultCount`, `childCount`, `isDomestic`, 가격/평점/시설 필터 |
|
|
192
|
+
| `getStayDetail` | 숙소 상세/객실/리뷰/편의시설 | `gid`, `checkIn`, `checkOut`, `adultCount`, `childCount` |
|
|
193
|
+
| `getCategoryList` | 도시별 TNA 카테고리 조회 | `city` |
|
|
194
|
+
| `searchTnas` | 투어/티켓/액티비티 검색 | `query`, `page`, `perPage`, `category`, `sort` |
|
|
195
|
+
| `getTnaDetail` | TNA 상품 상세 | `gid`, `url` |
|
|
196
|
+
| `getTnaOptions` | 날짜별 예약 가능 여부와 실제 가격 | `gid`, `url`, `selectedDate` |
|
|
197
|
+
|
|
198
|
+
## 응답 형식
|
|
199
|
+
|
|
200
|
+
래퍼는 MCP `CallToolResult`를 JSON으로 출력한다. 보통 `content[].text`에 사람이 읽을 수 있는 검색 결과가 들어온다.
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"content": [
|
|
205
|
+
{
|
|
206
|
+
"type": "text",
|
|
207
|
+
"text": "검색 결과 ..."
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
"isError": false
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
답변할 때는 원문을 그대로 길게 붙이지 말고 다음 기준으로 정리한다.
|
|
215
|
+
|
|
216
|
+
- 항공: 항공사, 출발/도착 시각, 총 소요시간, 직항/경유, 가격, 예약 URL
|
|
217
|
+
- 숙소: 숙소명, 지역, 1박/총액 가격, 평점, 핵심 장점, 상세 확인 필요 여부
|
|
218
|
+
- 투어/TNA: 상품명, 시작가, 평점/리뷰, 포함사항 핵심, 날짜별 옵션 확인 결과, URL
|
|
219
|
+
- 후보가 많으면 상위 3~5개만 비교
|
|
220
|
+
- 가격·좌석·재고·예약 가능 여부는 실시간 변동 가능하다고 명시
|
|
221
|
+
|
|
222
|
+
## 실패 상황과 대응
|
|
223
|
+
|
|
224
|
+
- `Python package 'mcp' is required`: `python3 -m pip install mcp` 후 재시도한다.
|
|
225
|
+
- MCP 엔드포인트 연결 실패: `MYREALTRIP_MCP_ENDPOINT` 오타, 네트워크, upstream 장애를 확인한다.
|
|
226
|
+
- 빈 결과: 날짜/지역/인원/공항코드를 바꿔 재검색한다.
|
|
227
|
+
- 항공 검색 실패: 국내/국제 도구 선택이 맞는지 확인한다. 해외 목적지는 반드시 `searchInternationalFlights`다.
|
|
228
|
+
- 숙소 상세 실패: `searchStays` 결과의 `gid`가 최신인지 확인하고 같은 날짜로 다시 검색한다.
|
|
229
|
+
- TNA 옵션 실패: `getTnaOptions`에는 `searchTnas` 결과의 `gid`와 `url`, `YYYY-MM-DD` 날짜가 모두 필요하다.
|
|
230
|
+
- 캘린더 가격 불일치: `flightsFareCalendar`는 캐시/추정값이므로 실제 검색 결과를 우선한다.
|
|
231
|
+
- 예약/결제 요청: 자동화하지 말고 예약 URL을 제공해 사용자가 직접 진행하게 한다.
|
|
232
|
+
|
|
233
|
+
## 완료 기준
|
|
234
|
+
|
|
235
|
+
- 요청 유형에 맞는 MCP 도구를 선택했다.
|
|
236
|
+
- 필요한 날짜, 인원, 공항/도시, `gid`/`url` 등 필수 입력을 확보했다.
|
|
237
|
+
- 실제 MCP 호출 결과를 바탕으로 상위 후보를 요약했다.
|
|
238
|
+
- 가격·재고·예약 가능 여부의 변동 가능성을 안내했다.
|
|
239
|
+
- 예약/결제는 자동화하지 않고 마이리얼트립 URL을 제공했다.
|