@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,185 @@
|
|
|
1
|
+
# Lovebug Report
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
`러브버그.com`(`https://xn--2i0bt2q2wd1wb.com/`)의 공개 지도/제보 표면을 이용해 러브버그(붉은등우단털파리) 관련 지역 정보를 취득하고, 사용자가 제공한 현재 위치 좌표로 익명 제보를 제출한다.
|
|
6
|
+
|
|
7
|
+
- 수도권 시·구별 러브버그 스코어와 최근 제보량을 조회한다.
|
|
8
|
+
- 동/읍/면 단위 area snapshot을 함께 조회해 어느 동네 제보인지 좁힌다.
|
|
9
|
+
- 사이트가 쓰는 익명 Supabase RPC 표면으로 제보를 보낸다.
|
|
10
|
+
- 좌표가 선택한 구 밖이거나 정확도가 낮거나 오늘 이미 같은 익명 기기에서 제보한 경우 upstream 거절을 그대로 알린다.
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
- "우리 동네 러브버그 많아?"
|
|
15
|
+
- "중랑구 러브버그 현황 알려줘"
|
|
16
|
+
- "러브버그 지도에서 동안구 제보량 확인해줘"
|
|
17
|
+
- "현재 위치로 러브버그 많다고 제보해줘"
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- 인터넷 연결
|
|
22
|
+
- Node.js 18+
|
|
23
|
+
- 이 저장소의 `lovebug-report` npm package 또는 동일 로직
|
|
24
|
+
- 제보 제출 시: 사용자가 제공한 현재 좌표(`lng`, `lat`), GPS 정확도, 구 코드(`gu_code`), 안정적인 익명 device hash
|
|
25
|
+
|
|
26
|
+
## Public access path discovered
|
|
27
|
+
|
|
28
|
+
### Primary source: lovebug.com public map JSON
|
|
29
|
+
|
|
30
|
+
- site: `https://xn--2i0bt2q2wd1wb.com/`
|
|
31
|
+
- gu score: `GET https://xn--2i0bt2q2wd1wb.com/api/map/gu-score`
|
|
32
|
+
- weekly report count: `GET https://xn--2i0bt2q2wd1wb.com/api/map/weekly-report-count`
|
|
33
|
+
- cluster snapshot: `GET https://xn--2i0bt2q2wd1wb.com/api/map/clusters?level=sigungu&historicalYear=2026`
|
|
34
|
+
- area snapshot: `GET https://xn--2i0bt2q2wd1wb.com/api/map/areas?historicalYear=2026&includePolygon=false`
|
|
35
|
+
- boundary GeoJSON: `GET https://xn--2i0bt2q2wd1wb.com/api/map/boundaries?level=sigungu`
|
|
36
|
+
|
|
37
|
+
Discovery result: the Next.js page loads these unauthenticated JSON endpoints directly. A live smoke on 2026-06-24 returned current `gu-score`, `weekly-report-count`, `clusters`, `areas`, and `boundaries` JSON.
|
|
38
|
+
|
|
39
|
+
No `k-skill-proxy` route is used because these upstream surfaces are public and do not require an API key.
|
|
40
|
+
|
|
41
|
+
### Anonymous report surface
|
|
42
|
+
|
|
43
|
+
The site bundle calls Supabase RPC `submit_anonymous_report` for logged-out reporting:
|
|
44
|
+
|
|
45
|
+
```http
|
|
46
|
+
POST https://sewrbxfawkmusnyzjoab.supabase.co/rest/v1/rpc/submit_anonymous_report
|
|
47
|
+
Content-Type: application/json
|
|
48
|
+
apikey: <site anon key>
|
|
49
|
+
Authorization: Bearer <site anon key>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Body:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"p_gu_code": "11070",
|
|
57
|
+
"p_lng": 127.09,
|
|
58
|
+
"p_lat": 37.59,
|
|
59
|
+
"p_accuracy_m": 25,
|
|
60
|
+
"p_level": 2,
|
|
61
|
+
"p_device_hash": "stable-anonymous-device-id",
|
|
62
|
+
"p_context": "street",
|
|
63
|
+
"p_image_url": null,
|
|
64
|
+
"p_indoor": false
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Official level labels:
|
|
69
|
+
|
|
70
|
+
| `p_level` | Label |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `0` | 잠잠해요 |
|
|
73
|
+
| `1` | 살짝 보임 |
|
|
74
|
+
| `2` | 많아요 |
|
|
75
|
+
| `3` | 매우 많아요 |
|
|
76
|
+
|
|
77
|
+
Official context labels:
|
|
78
|
+
|
|
79
|
+
| `p_context` | Label |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| `indoor` | 실내 |
|
|
82
|
+
| `street` | 길거리 |
|
|
83
|
+
| `park` | 공원 |
|
|
84
|
+
| `transit` | 지하철·버스 |
|
|
85
|
+
| `shop` | 상가 |
|
|
86
|
+
| `other` | 기타 |
|
|
87
|
+
|
|
88
|
+
## Workflow
|
|
89
|
+
|
|
90
|
+
### 1. Resolve the region
|
|
91
|
+
|
|
92
|
+
Use `searchLovebugRegions` first. It combines gu score and area snapshot rows.
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
const { searchLovebugRegions } = require("lovebug-report")
|
|
96
|
+
|
|
97
|
+
const result = await searchLovebugRegions({
|
|
98
|
+
query: "중랑",
|
|
99
|
+
includeAreas: true,
|
|
100
|
+
limit: 5
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
console.log(result.items)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
CLI:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
node packages/lovebug-report/src/cli.js search --query 중랑 --include-areas
|
|
110
|
+
node packages/lovebug-report/src/cli.js list --limit 10
|
|
111
|
+
node packages/lovebug-report/src/cli.js find --query 동안
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Returned gu fields include:
|
|
115
|
+
|
|
116
|
+
- `gu_code`, `gu_name`, `sido`
|
|
117
|
+
- `score`, `score_label`, `level`, `level_label`, `advisory`
|
|
118
|
+
- `counts.report`, `counts.report_24h`, `counts.spotted`, `counts.quiet`, `counts.low`, `counts.medium`, `counts.high`
|
|
119
|
+
- `coordinates`
|
|
120
|
+
- `areas[]` when `includeAreas` is enabled
|
|
121
|
+
|
|
122
|
+
### 2. Answer conservatively
|
|
123
|
+
|
|
124
|
+
State that the score is based on recent lovebug.com user reports, not government-certified pest density.
|
|
125
|
+
|
|
126
|
+
Good answer shape:
|
|
127
|
+
|
|
128
|
+
- 지역: `중랑구`
|
|
129
|
+
- 러브버그 스코어: `77` / `엄청 많아요, 조심!`
|
|
130
|
+
- 최근 제보: `14일 439건`, `24시간 105건`
|
|
131
|
+
- 동네 detail: area rows that matched the query
|
|
132
|
+
- 출처: `러브버그.com 공개 지도 JSON`
|
|
133
|
+
|
|
134
|
+
### 3. Submit a report only when required inputs are known
|
|
135
|
+
|
|
136
|
+
Do not invent coordinates or device hash. If the user asks to report but the automation environment has no current location, state that `lng`, `lat`, and GPS accuracy are required.
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const { reportLovebug } = require("lovebug-report")
|
|
140
|
+
|
|
141
|
+
await reportLovebug({
|
|
142
|
+
guCode: "11070",
|
|
143
|
+
level: "많아요",
|
|
144
|
+
context: "길거리",
|
|
145
|
+
lng: 127.09,
|
|
146
|
+
lat: 37.59,
|
|
147
|
+
accuracyM: 25,
|
|
148
|
+
deviceHash: "stable-anonymous-device-id"
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
CLI:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
node packages/lovebug-report/src/cli.js report \
|
|
156
|
+
--gu-code 11070 \
|
|
157
|
+
--level 많아요 \
|
|
158
|
+
--context 길거리 \
|
|
159
|
+
--lng 127.09 \
|
|
160
|
+
--lat 37.59 \
|
|
161
|
+
--accuracy 25 \
|
|
162
|
+
--device-hash stable-anonymous-device-id
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Done when
|
|
166
|
+
|
|
167
|
+
- Region status was fetched from `러브버그.com` public JSON.
|
|
168
|
+
- Region ambiguity was resolved with `gu_code`/`gu_name` when needed.
|
|
169
|
+
- Report submission used real current coordinates and the upstream RPC returned success.
|
|
170
|
+
- Source URL and user-report nature were disclosed.
|
|
171
|
+
|
|
172
|
+
## Failure modes
|
|
173
|
+
|
|
174
|
+
- `ANON_DAILY_DUPLICATE`: the same anonymous device already reported that region today.
|
|
175
|
+
- `OUTSIDE_GU_AREA`: supplied coordinates are outside `p_gu_code`.
|
|
176
|
+
- `ACCURACY_TOO_LOW`: GPS accuracy is too low for the upstream validation.
|
|
177
|
+
- Public Next.js/Supabase bundle keys, routes, table names, or RPC names can change.
|
|
178
|
+
- The map is user-report-based and can lag or be noisy.
|
|
179
|
+
- The site may add stricter rate limits, bot checks, login requirements, or image-upload restrictions.
|
|
180
|
+
|
|
181
|
+
## Notes
|
|
182
|
+
|
|
183
|
+
- No login, CAPTCHA bypass, queue bypass, or privileged workflow automation.
|
|
184
|
+
- No `k-skill-proxy` because upstream is public and keyless from the user's machine.
|
|
185
|
+
- Image upload is not handled by this skill; pass `imageUrl` only when a site-compatible image URL already exists.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lovebug-report",
|
|
3
|
+
"description": "러브버그.com 공개 지도 표면으로 수도권 시·구별 러브버그 출몰 지수·동네별 제보 현황을 조회하고, 사용자가 제공한 현재 좌표로 익명 러브버그 제보를 제출한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"proxy",
|
|
6
|
+
"action:submission"
|
|
7
|
+
],
|
|
8
|
+
"frontmatter": "name: lovebug-report\ndescription: 러브버그.com 공개 지도 표면으로 수도권 시·구별 러브버그 출몰 지수·동네별 제보 현황을 조회하고, 사용자가 제공한 현재 좌표로 익명 러브버그 제보를 제출한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: local-info\n locale: ko-KR\n phase: v1"
|
|
9
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Market Kurly Search
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
마켓컬리 웹앱이 실제로 사용하는 **비로그인 검색/상품 상세 표면**을 사용해 아래 흐름을 처리한다.
|
|
6
|
+
|
|
7
|
+
- 키워드로 상품 후보를 검색한다.
|
|
8
|
+
- 현재 가격과 할인 여부를 확인한다.
|
|
9
|
+
- 품절 여부와 배송 타입을 확인한다.
|
|
10
|
+
- 상품 링크를 함께 반환한다.
|
|
11
|
+
- **주문/장바구니 같은 액션은 하지 않는다. 조회형으로만 답한다.**
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
- "마켓컬리에서 우유 얼마야?"
|
|
16
|
+
- "컬리에서 딸기 검색해줘"
|
|
17
|
+
- "이 상품 품절인지 보고 링크도 줘"
|
|
18
|
+
- "지금 컬리 가격만 빠르게 보고 싶어"
|
|
19
|
+
|
|
20
|
+
## When not to use
|
|
21
|
+
|
|
22
|
+
- 주문/장바구니/결제까지 자동화해야 하는 경우
|
|
23
|
+
- 주소 기반 배송 가능 여부나 회원 전용 가격을 확정해야 하는 경우
|
|
24
|
+
- 로그인 세션이 필요한 개인화 추천/찜 정보를 조회해야 하는 경우
|
|
25
|
+
|
|
26
|
+
## Prerequisites
|
|
27
|
+
|
|
28
|
+
- 인터넷 연결
|
|
29
|
+
- `node` 18+
|
|
30
|
+
- 이 저장소의 `market-kurly-search` package 또는 동일 로직
|
|
31
|
+
|
|
32
|
+
## Required inputs
|
|
33
|
+
|
|
34
|
+
### 1. Ask for a product keyword if it is missing
|
|
35
|
+
|
|
36
|
+
상품명 또는 검색어가 없으면 먼저 물어본다.
|
|
37
|
+
|
|
38
|
+
- 권장 질문: `찾을 마켓컬리 상품명이나 검색어를 알려주세요. 예: 우유, 딸기, 닭가슴살`
|
|
39
|
+
- 너무 넓으면: `검색어가 너무 넓어요. 브랜드나 용량까지 같이 알려주시면 가격 후보를 더 정확히 추릴 수 있어요.`
|
|
40
|
+
|
|
41
|
+
### 2. Confirm which candidate they want when the query is ambiguous
|
|
42
|
+
|
|
43
|
+
검색 결과가 여러 개면 상위 2~3개만 보여주고 다시 확인받는다.
|
|
44
|
+
|
|
45
|
+
- 권장 질문: `후보가 여러 개예요. 아래 상품 중 어떤 상품 가격을 볼까요?`
|
|
46
|
+
- 응답에는 상품명 + 현재 가격 + 품절 여부 + 링크를 같이 붙인다.
|
|
47
|
+
|
|
48
|
+
## Official Market Kurly surfaces
|
|
49
|
+
|
|
50
|
+
- search list: `https://api.kurly.com/search/v4/sites/market/normal-search?keyword=<keyword>&page=1`
|
|
51
|
+
- search count: `https://api.kurly.com/search/v3/sites/market/normal-search/count?keyword=<keyword>&filters=&allow_replace=true`
|
|
52
|
+
- product detail page: `https://www.kurly.com/goods/<productNo>`
|
|
53
|
+
|
|
54
|
+
## Workflow
|
|
55
|
+
|
|
56
|
+
### 1. Search by keyword first
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
const { searchProducts } = require("market-kurly-search")
|
|
60
|
+
|
|
61
|
+
const result = await searchProducts("우유")
|
|
62
|
+
console.log(result.items.slice(0, 3))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
검색 결과에서는 아래 필드를 우선 본다.
|
|
66
|
+
|
|
67
|
+
- 상품명
|
|
68
|
+
- 현재 가격 (`discountedPrice` 우선, 없으면 `salesPrice`)
|
|
69
|
+
- 할인율
|
|
70
|
+
- 품절 여부
|
|
71
|
+
- 배송 타입
|
|
72
|
+
- 상품 링크
|
|
73
|
+
|
|
74
|
+
### 2. Use the count endpoint when the result set is broad
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
const { countProducts } = require("market-kurly-search")
|
|
78
|
+
|
|
79
|
+
const count = await countProducts("우유")
|
|
80
|
+
console.log(count)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
후보가 너무 많으면 `count` 를 먼저 보여 주고 검색어를 좁히라고 안내한다.
|
|
84
|
+
|
|
85
|
+
### 3. Use the goods page detail as a fallback or follow-up lookup
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
const { getProductDetail } = require("market-kurly-search")
|
|
89
|
+
|
|
90
|
+
const detail = await getProductDetail(5063110)
|
|
91
|
+
console.log(detail)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`goods/<productNo>` HTML 안의 `__NEXT_DATA__` 에서 상품명, 가격, 품절 여부, 배송 타입을 추출한다.
|
|
95
|
+
|
|
96
|
+
### 4. Respond conservatively
|
|
97
|
+
|
|
98
|
+
응답은 짧고 보수적으로 정리한다.
|
|
99
|
+
|
|
100
|
+
- 상품명
|
|
101
|
+
- 현재 가격
|
|
102
|
+
- 필요하면 원가/할인가 여부
|
|
103
|
+
- 품절 여부 또는 판매 가능 여부
|
|
104
|
+
- 상품 링크
|
|
105
|
+
- **가격/품절/노출 정보는 시점에 따라 달라질 수 있으니 조회 시각 기준 참고값이라고 분명히 말한다.**
|
|
106
|
+
|
|
107
|
+
## Done when
|
|
108
|
+
|
|
109
|
+
- 상품 키워드를 확인했다.
|
|
110
|
+
- 검색 결과에서 후보와 현재 가격을 최소 1개 이상 반환했다.
|
|
111
|
+
- 필요하면 상품 상세 페이지로 보조 확인했다.
|
|
112
|
+
- 주문/장바구니 같은 범위 밖 액션은 하지 않았다.
|
|
113
|
+
|
|
114
|
+
## Failure modes
|
|
115
|
+
|
|
116
|
+
- 검색어가 너무 넓으면 후보가 과도하게 많아질 수 있다.
|
|
117
|
+
- 가격/품절/배송 문구는 시점에 따라 달라질 수 있다.
|
|
118
|
+
- 현재 확인한 표면은 **공식 개발자 Open API가 아니라 웹이 쓰는 공개 표면** 이므로 스키마가 바뀌면 깨질 수 있다.
|
|
119
|
+
- 회원 전용/주소 전용 정보는 비로그인 조회만으로 확정할 수 없다.
|
|
120
|
+
|
|
121
|
+
## Notes
|
|
122
|
+
|
|
123
|
+
- 조회형 스킬이다.
|
|
124
|
+
- 비로그인 공개 표면 우선 원칙을 유지한다.
|
|
125
|
+
- 주문/장바구니/로그인 요구 기능은 시도하지 않는다.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "market-kurly-search",
|
|
3
|
+
"description": "로그인 없이 접근 가능한 마켓컬리 검색/상품 상세 표면으로 상품 후보, 현재 가격, 할인 여부, 품절 여부를 조회한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"action:commerce"
|
|
6
|
+
],
|
|
7
|
+
"frontmatter": "name: market-kurly-search\ndescription: 로그인 없이 접근 가능한 마켓컬리 검색/상품 상세 표면으로 상품 후보, 현재 가격, 할인 여부, 품절 여부를 조회한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: retail\n locale: ko-KR\n phase: v1"
|
|
8
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# 의약품 안전 체크
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
식약처 공식 OpenAPI를 **`k-skill-proxy` 경유**로 조회해 **의약품개요정보(e약은요)** 와 **안전상비의약품 정보**를 확인한다.
|
|
6
|
+
|
|
7
|
+
하지만 사용자가 증상이나 복용 상황을 말하면 **바로 단정하지 말고 먼저 되묻는다.**
|
|
8
|
+
|
|
9
|
+
- 본인/아이/임산부/고령자 여부
|
|
10
|
+
- 어떤 약을 이미 먹었는지 / 지금 먹으려는지
|
|
11
|
+
- 언제부터 얼마나 복용했는지
|
|
12
|
+
- 현재 증상, 기저질환, 알레르기, 복용 중인 다른 약
|
|
13
|
+
- red flag (`호흡곤란`, `의식저하`, `심한 발진`, `지속되는 구토/흉통`)
|
|
14
|
+
|
|
15
|
+
red flag 가 있으면 API 조회보다 **즉시 119·응급실·의료진 연결**을 우선한다.
|
|
16
|
+
|
|
17
|
+
## When to use
|
|
18
|
+
|
|
19
|
+
- "이 약이랑 이 약 같이 먹어도 되니?"
|
|
20
|
+
- "타이레놀 먹는 중인데 판콜 같이 먹어도 돼?"
|
|
21
|
+
- "두드러기가 있는데 이 약 계속 먹어도 되나?"
|
|
22
|
+
- "식약처 공식 약 정보로 효능/주의사항 확인해줘"
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- 인터넷 연결
|
|
27
|
+
- `python3`
|
|
28
|
+
- 설치된 skill payload 안에 `scripts/mfds_drug_safety.py` helper 포함
|
|
29
|
+
- `k-skill-proxy`의 `/v1/mfds/drug-safety/lookup` route가 있는 hosted/self-host 프록시에 접근 가능할 것
|
|
30
|
+
|
|
31
|
+
## Credential requirements
|
|
32
|
+
|
|
33
|
+
- 사용자 측 **필수** 시크릿 없음.
|
|
34
|
+
- `KSKILL_PROXY_BASE_URL` — self-host·별도 프록시를 쓸 때만 설정. 비우면 기본 hosted `https://k-skill-proxy.nomadamas.org` 를 사용한다.
|
|
35
|
+
- `DATA_GO_KR_API_KEY` 는 **프록시 운영 서버** 환경에만 둔다.
|
|
36
|
+
|
|
37
|
+
## Mandatory interview first
|
|
38
|
+
|
|
39
|
+
증상/복용상황이 언급되면 바로 결론을 말하지 말고 먼저 되묻는다.
|
|
40
|
+
|
|
41
|
+
권장 첫 질문 예시:
|
|
42
|
+
|
|
43
|
+
- `누가 복용하려는지(본인/아이/임산부/고령자), 이미 먹은 약 이름, 언제 얼마나 복용했는지, 지금 있는 증상을 먼저 알려주세요.`
|
|
44
|
+
- `호흡곤란, 의식저하, 입술·혀 붓기, 심한 전신 발진이 있으면 즉시 119 또는 응급실로 가야 합니다.`
|
|
45
|
+
|
|
46
|
+
## Official surfaces
|
|
47
|
+
|
|
48
|
+
- 공공데이터포털 문서: `https://www.data.go.kr/data/15075057/openapi.do`
|
|
49
|
+
- e약은요 endpoint: `https://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList`
|
|
50
|
+
- 공공데이터포털 문서: `https://www.data.go.kr/data/15097208/openapi.do`
|
|
51
|
+
- 안전상비의약품 endpoint: `https://apis.data.go.kr/1471000/SafeStadDrugService/getSafeStadDrugInq`
|
|
52
|
+
- 프록시 route: `GET /v1/mfds/drug-safety/lookup`
|
|
53
|
+
|
|
54
|
+
## Workflow
|
|
55
|
+
|
|
56
|
+
1. 증상/복용상황이 있으면 인터뷰를 먼저 진행한다.
|
|
57
|
+
2. red flag 가 하나라도 있으면 즉시 응급 안내로 전환한다.
|
|
58
|
+
3. 약 이름이 확인되면 `k-skill-proxy`의 `/v1/mfds/drug-safety/lookup` 으로 공식 정보를 조회한다.
|
|
59
|
+
4. 효능, 사용법, 주의사항, 상호작용, 이상반응, 보관법을 짧게 정리한다.
|
|
60
|
+
5. `같이 먹어도 되나?` 질문에는 공식 상호작용 문구만 근거로 제시하고, 최종 판단은 약사·의료진 확인이 필요하다고 명시한다.
|
|
61
|
+
|
|
62
|
+
## CLI examples
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python3 scripts/mfds_drug_safety.py interview \
|
|
66
|
+
--question "타이레놀이랑 판콜 같이 먹어도 되나요?" \
|
|
67
|
+
--symptoms "두드러기와 어지러움"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 scripts/mfds_drug_safety.py lookup --item-name "타이레놀" --item-name "판콜"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Response policy
|
|
75
|
+
|
|
76
|
+
- 이 스킬은 **진단/처방/복용 지시**를 하지 않는다.
|
|
77
|
+
- 공식 문서에 있는 효능/주의/상호작용 문구만 근거로 요약한다.
|
|
78
|
+
- 상호작용 문구가 모호하거나 red flag 가 있으면 약사·의사 상담으로 넘긴다.
|
|
79
|
+
- 증상이 있는 질문은 인터뷰 없이 바로 답하지 않는다.
|
|
80
|
+
|
|
81
|
+
## Done when
|
|
82
|
+
|
|
83
|
+
- 증상 또는 복용상황을 먼저 되물었다.
|
|
84
|
+
- red flag 여부를 확인했다.
|
|
85
|
+
- 프록시 route를 통해 공식 endpoint 조회 결과를 JSON으로 정리했다.
|
|
86
|
+
- 최소한 제품명, 업체명, 효능/주의/상호작용이 포함된 요약을 제공했다.
|
|
@@ -0,0 +1,184 @@
|
|
|
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_drug_interview(question: str | None = None, symptoms: str | None = None) -> dict[str, Any]:
|
|
47
|
+
return {
|
|
48
|
+
"domain": "drug",
|
|
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
|
+
EASY_FIELD_MAP = {
|
|
70
|
+
"item_name": "item_name",
|
|
71
|
+
"company_name": "company_name",
|
|
72
|
+
"efficacy": "efficacy",
|
|
73
|
+
"how_to_use": "how_to_use",
|
|
74
|
+
"warnings": "warnings",
|
|
75
|
+
"cautions": "cautions",
|
|
76
|
+
"interactions": "interactions",
|
|
77
|
+
"side_effects": "side_effects",
|
|
78
|
+
"storage": "storage",
|
|
79
|
+
"item_seq": "item_seq",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
SAFE_STAD_FIELD_MAP = {
|
|
83
|
+
"item_name": "item_name",
|
|
84
|
+
"company_name": "company_name",
|
|
85
|
+
"efficacy": "efficacy",
|
|
86
|
+
"how_to_use": "how_to_use",
|
|
87
|
+
"warnings": "warnings",
|
|
88
|
+
"cautions": "cautions",
|
|
89
|
+
"interactions": "interactions",
|
|
90
|
+
"side_effects": "side_effects",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def normalize_easy_drug_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
95
|
+
normalized = {key: summarize_text(item.get(source_key)) for key, source_key in EASY_FIELD_MAP.items()}
|
|
96
|
+
normalized["source"] = "drug_easy_info"
|
|
97
|
+
return normalized
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def normalize_safe_stad_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
101
|
+
normalized = {key: summarize_text(item.get(source_key)) for key, source_key in SAFE_STAD_FIELD_MAP.items()}
|
|
102
|
+
normalized["source"] = "safe_standby_medicine"
|
|
103
|
+
return normalized
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def read_json_response(request: urllib.request.Request | str) -> dict[str, Any]:
|
|
107
|
+
try:
|
|
108
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
109
|
+
return json.loads(response.read().decode("utf-8"))
|
|
110
|
+
except urllib.error.HTTPError as error:
|
|
111
|
+
body = error.read().decode("utf-8", errors="replace")
|
|
112
|
+
try:
|
|
113
|
+
payload = json.loads(body)
|
|
114
|
+
except json.JSONDecodeError:
|
|
115
|
+
payload = None
|
|
116
|
+
if error.code == 503 and isinstance(payload, dict) and payload.get("error") == "upstream_not_configured":
|
|
117
|
+
raise ApiError(PROXY_KEY_NOT_CONFIGURED_MSG, status_code=error.code, url=getattr(error, "url", None)) from error
|
|
118
|
+
|
|
119
|
+
if isinstance(payload, dict) and payload.get("message"):
|
|
120
|
+
raise ApiError(str(payload["message"]), status_code=error.code, url=getattr(error, "url", None)) from error
|
|
121
|
+
raise ApiError(
|
|
122
|
+
f"MFDS drug proxy request failed with HTTP {error.code}",
|
|
123
|
+
status_code=error.code,
|
|
124
|
+
url=getattr(error, "url", None),
|
|
125
|
+
) from error
|
|
126
|
+
except urllib.error.URLError as error:
|
|
127
|
+
raise ApiError(f"{PROXY_DOWN_MSG} (상세: {error.reason})") from error
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def lookup_drugs(
|
|
131
|
+
item_names: list[str],
|
|
132
|
+
*,
|
|
133
|
+
limit: int = 5,
|
|
134
|
+
base_url: str | None = None,
|
|
135
|
+
request_json: Any = read_json_response,
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
resolved_base_url = resolve_proxy_base_url(base_url)
|
|
138
|
+
url = f"{resolved_base_url}/v1/mfds/drug-safety/lookup"
|
|
139
|
+
params: list[tuple[str, str]] = [("itemName", item_name) for item_name in item_names]
|
|
140
|
+
params.append(("limit", str(limit)))
|
|
141
|
+
query = urllib.parse.urlencode(params)
|
|
142
|
+
request = urllib.request.Request(
|
|
143
|
+
f"{url}?{query}",
|
|
144
|
+
headers={"Accept": "application/json", "User-Agent": "k-skill-mfds/1.0"},
|
|
145
|
+
)
|
|
146
|
+
return request_json(request)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
150
|
+
parser = argparse.ArgumentParser(description="MFDS drug-safety helper")
|
|
151
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
152
|
+
|
|
153
|
+
interview = subparsers.add_parser("interview", help="print the mandatory symptom follow-up interview")
|
|
154
|
+
interview.add_argument("--question", default="")
|
|
155
|
+
interview.add_argument("--symptoms", default="")
|
|
156
|
+
|
|
157
|
+
lookup = subparsers.add_parser("lookup", help="look up official MFDS drug safety records through k-skill-proxy")
|
|
158
|
+
lookup.add_argument("--item-name", action="append", required=True)
|
|
159
|
+
lookup.add_argument("--limit", type=int, default=5)
|
|
160
|
+
lookup.add_argument("--proxy-base-url")
|
|
161
|
+
return parser
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main(argv: list[str] | None = None) -> int:
|
|
165
|
+
args = build_parser().parse_args(argv)
|
|
166
|
+
|
|
167
|
+
if args.command == "interview":
|
|
168
|
+
print(json.dumps(build_drug_interview(question=args.question, symptoms=args.symptoms), ensure_ascii=False, indent=2))
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
if args.command == "lookup":
|
|
172
|
+
try:
|
|
173
|
+
payload = lookup_drugs(args.item_name, limit=args.limit, base_url=args.proxy_base_url)
|
|
174
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
175
|
+
return 0
|
|
176
|
+
except (ValueError, ApiError) as error:
|
|
177
|
+
print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
|
|
178
|
+
return 1
|
|
179
|
+
|
|
180
|
+
return 1
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mfds-drug-safety",
|
|
3
|
+
"description": "식약처 공공 OpenAPI를 k-skill-proxy 경유로 조회하기 전에 증상·복용상황을 반드시 되묻는 인터뷰형 의약품 안전 체크 스킬.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"proxy",
|
|
6
|
+
"vault",
|
|
7
|
+
"lookup"
|
|
8
|
+
],
|
|
9
|
+
"frontmatter": "name: mfds-drug-safety\ndescription: 식약처 공공 OpenAPI를 k-skill-proxy 경유로 조회하기 전에 증상·복용상황을 반드시 되묻는 인터뷰형 의약품 안전 체크 스킬.\nlicense: MIT\nmetadata:\n category: public-health\n locale: ko-KR\n phase: v1"
|
|
10
|
+
}
|