@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.
Files changed (350) hide show
  1. package/bin/k-skill.js +60 -0
  2. package/package.json +37 -0
  3. package/skills/assembly-bill-vote-search/instruction.md +111 -0
  4. package/skills/assembly-bill-vote-search/skill.json +10 -0
  5. package/skills/biz-health-check/instruction.md +69 -0
  6. package/skills/biz-health-check/scripts/biz_health_check.py +163 -0
  7. package/skills/biz-health-check/skill.json +10 -0
  8. package/skills/bok-ecos-stats/instruction.md +95 -0
  9. package/skills/bok-ecos-stats/scripts/bok_ecos.py +302 -0
  10. package/skills/bok-ecos-stats/skill.json +10 -0
  11. package/skills/building-register-search/instruction.md +69 -0
  12. package/skills/building-register-search/scripts/building_register.py +309 -0
  13. package/skills/building-register-search/scripts/building_register_xml.py +32 -0
  14. package/skills/building-register-search/skill.json +9 -0
  15. package/skills/bunjang-search/instruction.md +154 -0
  16. package/skills/bunjang-search/skill.json +10 -0
  17. package/skills/catchtable-sniper/instruction.md +270 -0
  18. package/skills/catchtable-sniper/skill.json +9 -0
  19. package/skills/cheap-gas-nearby/instruction.md +107 -0
  20. package/skills/cheap-gas-nearby/skill.json +10 -0
  21. package/skills/corporate-registration-consulting/instruction.md +117 -0
  22. package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +106 -0
  23. package/skills/corporate-registration-consulting/skill.json +8 -0
  24. package/skills/coupang-product-search/instruction.md +219 -0
  25. package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +146 -0
  26. package/skills/coupang-product-search/skill.json +10 -0
  27. package/skills/court-auction-notice-search/instruction.md +201 -0
  28. package/skills/court-auction-notice-search/skill.json +9 -0
  29. package/skills/court-payment-order-assistant/instruction.md +120 -0
  30. package/skills/court-payment-order-assistant/skill.json +10 -0
  31. package/skills/d2b-notice-search/instruction.md +117 -0
  32. package/skills/d2b-notice-search/skill.json +10 -0
  33. package/skills/daangn-cars-search/instruction.md +91 -0
  34. package/skills/daangn-cars-search/scripts/daangn_cars.py +72 -0
  35. package/skills/daangn-cars-search/skill.json +9 -0
  36. package/skills/daangn-jobs-search/instruction.md +90 -0
  37. package/skills/daangn-jobs-search/scripts/daangn_jobs.py +98 -0
  38. package/skills/daangn-jobs-search/skill.json +9 -0
  39. package/skills/daangn-realty-search/instruction.md +114 -0
  40. package/skills/daangn-realty-search/scripts/daangn_detail_ld.py +76 -0
  41. package/skills/daangn-realty-search/scripts/daangn_realty.py +216 -0
  42. package/skills/daangn-realty-search/scripts/daangn_relay_store.py +174 -0
  43. package/skills/daangn-realty-search/skill.json +8 -0
  44. package/skills/daangn-used-goods-search/instruction.md +90 -0
  45. package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +80 -0
  46. package/skills/daangn-used-goods-search/skill.json +9 -0
  47. package/skills/daishin-report-search/instruction.md +138 -0
  48. package/skills/daishin-report-search/skill.json +10 -0
  49. package/skills/daiso-product-search/instruction.md +172 -0
  50. package/skills/daiso-product-search/skill.json +8 -0
  51. package/skills/danawa-price-search/instruction.md +183 -0
  52. package/skills/danawa-price-search/scripts/danawa_search.py +354 -0
  53. package/skills/danawa-price-search/skill.json +8 -0
  54. package/skills/delivery-tracking/instruction.md +358 -0
  55. package/skills/delivery-tracking/skill.json +8 -0
  56. package/skills/donation-place-search/instruction.md +129 -0
  57. package/skills/donation-place-search/skill.json +9 -0
  58. package/skills/emergency-room-beds/instruction.md +82 -0
  59. package/skills/emergency-room-beds/skill.json +8 -0
  60. package/skills/ev-charger-nearby/instruction.md +80 -0
  61. package/skills/ev-charger-nearby/scripts/ev_charger.py +222 -0
  62. package/skills/ev-charger-nearby/skill.json +10 -0
  63. package/skills/ev-subsidy-status/instruction.md +165 -0
  64. package/skills/ev-subsidy-status/skill.json +10 -0
  65. package/skills/express-bus-booking/instruction.md +207 -0
  66. package/skills/express-bus-booking/references/kobus-http-flow.md +159 -0
  67. package/skills/express-bus-booking/scripts/kobus_express_booking.py +243 -0
  68. package/skills/express-bus-booking/skill.json +8 -0
  69. package/skills/fine-dust-location/instruction.md +89 -0
  70. package/skills/fine-dust-location/skill.json +9 -0
  71. package/skills/flight-ticket-search/instruction.md +237 -0
  72. package/skills/flight-ticket-search/scripts/flight_ticket_search.py +501 -0
  73. package/skills/flight-ticket-search/skill.json +9 -0
  74. package/skills/foresttrip-vacancy/instruction.md +167 -0
  75. package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +549 -0
  76. package/skills/foresttrip-vacancy/skill.json +10 -0
  77. package/skills/fsc-corporate-info/instruction.md +57 -0
  78. package/skills/fsc-corporate-info/scripts/fsc_corporate_info.py +113 -0
  79. package/skills/fsc-corporate-info/skill.json +10 -0
  80. package/skills/g2b-order-plan-search/instruction.md +131 -0
  81. package/skills/g2b-order-plan-search/scripts/g2b_order_plan.py +158 -0
  82. package/skills/g2b-order-plan-search/skill.json +10 -0
  83. package/skills/g2b-sanctioned-supplier/instruction.md +61 -0
  84. package/skills/g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py +114 -0
  85. package/skills/g2b-sanctioned-supplier/skill.json +10 -0
  86. package/skills/gangnamunni-clinic-search/instruction.md +113 -0
  87. package/skills/gangnamunni-clinic-search/skill.json +9 -0
  88. package/skills/geeknews-search/instruction.md +69 -0
  89. package/skills/geeknews-search/scripts/geeknews_search.py +296 -0
  90. package/skills/geeknews-search/skill.json +8 -0
  91. package/skills/gongsijiga-search/instruction.md +128 -0
  92. package/skills/gongsijiga-search/skill.json +9 -0
  93. package/skills/gov-overseas-trip-report/instruction.md +488 -0
  94. package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +1206 -0
  95. package/skills/gov-overseas-trip-report/skill.json +9 -0
  96. package/skills/han-river-water-level/instruction.md +83 -0
  97. package/skills/han-river-water-level/skill.json +10 -0
  98. package/skills/highway-traffic-status/instruction.md +88 -0
  99. package/skills/highway-traffic-status/scripts/highway_traffic.py +315 -0
  100. package/skills/highway-traffic-status/skill.json +11 -0
  101. package/skills/hipass-receipt/instruction.md +97 -0
  102. package/skills/hipass-receipt/skill.json +10 -0
  103. package/skills/hola-poke-yeoksam/instruction.md +247 -0
  104. package/skills/hola-poke-yeoksam/skill.json +8 -0
  105. package/skills/household-waste-info/instruction.md +117 -0
  106. package/skills/household-waste-info/skill.json +10 -0
  107. package/skills/housing-official-price/instruction.md +177 -0
  108. package/skills/housing-official-price/skill.json +10 -0
  109. package/skills/hwp/instruction.md +206 -0
  110. package/skills/hwp/skill.json +8 -0
  111. package/skills/intercity-bus-booking/instruction.md +189 -0
  112. package/skills/intercity-bus-booking/references/tmoney-intercity-http-flow.md +126 -0
  113. package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +381 -0
  114. package/skills/intercity-bus-booking/skill.json +8 -0
  115. package/skills/iros-registry-automation/instruction.md +229 -0
  116. package/skills/iros-registry-automation/scripts/iros_pdf_summary.py +249 -0
  117. package/skills/iros-registry-automation/scripts/upstream.pin +1 -0
  118. package/skills/iros-registry-automation/skill.json +9 -0
  119. package/skills/job-posting-match/instruction.md +130 -0
  120. package/skills/job-posting-match/scripts/job_posting_match.py +396 -0
  121. package/skills/job-posting-match/scripts/test_job_posting_match.py +54 -0
  122. package/skills/job-posting-match/skill.json +8 -0
  123. package/skills/jobkorea-talent-search/instruction.md +118 -0
  124. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_models.py +27 -0
  125. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_parse.py +186 -0
  126. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +94 -0
  127. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search_condition.py +136 -0
  128. package/skills/jobkorea-talent-search/scripts/test_jobkorea_talent_search.py +76 -0
  129. package/skills/jobkorea-talent-search/skill.json +9 -0
  130. package/skills/joseon-sillok-search/instruction.md +76 -0
  131. package/skills/joseon-sillok-search/scripts/sillok_search.py +552 -0
  132. package/skills/joseon-sillok-search/skill.json +8 -0
  133. package/skills/k-dart/instruction.md +406 -0
  134. package/skills/k-dart/skill.json +8 -0
  135. package/skills/k-schoollunch-menu/instruction.md +109 -0
  136. package/skills/k-schoollunch-menu/skill.json +9 -0
  137. package/skills/k-skill-cleaner/instruction.md +80 -0
  138. package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +410 -0
  139. package/skills/k-skill-cleaner/skill.json +8 -0
  140. package/skills/k-skill-setup/instruction.md +253 -0
  141. package/skills/k-skill-setup/skill.json +11 -0
  142. package/skills/kakao-bar-nearby/instruction.md +76 -0
  143. package/skills/kakao-bar-nearby/skill.json +8 -0
  144. package/skills/kakao-map/instruction.md +176 -0
  145. package/skills/kakao-map/skill.json +10 -0
  146. package/skills/kakaotalk-mac/instruction.md +189 -0
  147. package/skills/kakaotalk-mac/skill.json +8 -0
  148. package/skills/kbl-results/instruction.md +89 -0
  149. package/skills/kbl-results/skill.json +9 -0
  150. package/skills/kbo-results/instruction.md +82 -0
  151. package/skills/kbo-results/skill.json +8 -0
  152. package/skills/keris-academic-search/instruction.md +81 -0
  153. package/skills/keris-academic-search/scripts/keris_academic.py +210 -0
  154. package/skills/keris-academic-search/skill.json +10 -0
  155. package/skills/kleague-results/instruction.md +92 -0
  156. package/skills/kleague-results/skill.json +8 -0
  157. package/skills/kopis-performance-search/instruction.md +109 -0
  158. package/skills/kopis-performance-search/skill.json +10 -0
  159. package/skills/korea-weather/instruction.md +93 -0
  160. package/skills/korea-weather/skill.json +10 -0
  161. package/skills/korean-character-count/instruction.md +87 -0
  162. package/skills/korean-character-count/scripts/korean_character_count.js +268 -0
  163. package/skills/korean-character-count/skill.json +8 -0
  164. package/skills/korean-cinema-search/instruction.md +177 -0
  165. package/skills/korean-cinema-search/skill.json +8 -0
  166. package/skills/korean-heritage-search/instruction.md +106 -0
  167. package/skills/korean-heritage-search/scripts/korean_heritage_search.py +321 -0
  168. package/skills/korean-heritage-search/skill.json +8 -0
  169. package/skills/korean-holiday-calendar/instruction.md +97 -0
  170. package/skills/korean-holiday-calendar/skill.json +9 -0
  171. package/skills/korean-humanizer/instruction.md +389 -0
  172. package/skills/korean-humanizer/references/ai-tell-taxonomy.md +147 -0
  173. package/skills/korean-humanizer/skill.json +8 -0
  174. package/skills/korean-jangbu-for/instruction.md +133 -0
  175. package/skills/korean-jangbu-for/scripts/install.sh +237 -0
  176. package/skills/korean-jangbu-for/scripts/upstream.pin +1 -0
  177. package/skills/korean-jangbu-for/skill.json +8 -0
  178. package/skills/korean-law-search/instruction.md +126 -0
  179. package/skills/korean-law-search/skill.json +10 -0
  180. package/skills/korean-marathon-schedule/instruction.md +111 -0
  181. package/skills/korean-marathon-schedule/skill.json +9 -0
  182. package/skills/korean-middle-korean/instruction.md +79 -0
  183. package/skills/korean-middle-korean/scripts/korean_middle_korean.js +214 -0
  184. package/skills/korean-middle-korean/skill.json +8 -0
  185. package/skills/korean-patent-search/instruction.md +79 -0
  186. package/skills/korean-patent-search/scripts/patent_search.py +409 -0
  187. package/skills/korean-patent-search/skill.json +9 -0
  188. package/skills/korean-privacy-terms/instruction.md +128 -0
  189. package/skills/korean-privacy-terms/scripts/install.sh +108 -0
  190. package/skills/korean-privacy-terms/scripts/upstream.pin +1 -0
  191. package/skills/korean-privacy-terms/skill.json +8 -0
  192. package/skills/korean-scholarship-search/instruction.md +317 -0
  193. package/skills/korean-scholarship-search/references/report-format.md +40 -0
  194. package/skills/korean-scholarship-search/references/school-discovery.md +61 -0
  195. package/skills/korean-scholarship-search/references/search-clues.md +58 -0
  196. package/skills/korean-scholarship-search/references/source-patterns.md +67 -0
  197. package/skills/korean-scholarship-search/scripts/scholarship_filter.py +811 -0
  198. package/skills/korean-scholarship-search/scripts/test_scholarship_filter.py +224 -0
  199. package/skills/korean-scholarship-search/scripts/university_search_plan.py +148 -0
  200. package/skills/korean-scholarship-search/skill.json +8 -0
  201. package/skills/korean-slang-writing/instruction.md +181 -0
  202. package/skills/korean-slang-writing/scripts/_slang_http.py +91 -0
  203. package/skills/korean-slang-writing/scripts/slang_lookup.py +291 -0
  204. package/skills/korean-slang-writing/scripts/slang_search.py +284 -0
  205. package/skills/korean-slang-writing/skill.json +8 -0
  206. package/skills/korean-spell-check/instruction.md +105 -0
  207. package/skills/korean-spell-check/scripts/korean_spell_check.py +523 -0
  208. package/skills/korean-spell-check/skill.json +9 -0
  209. package/skills/korean-stock-search/instruction.md +194 -0
  210. package/skills/korean-stock-search/skill.json +10 -0
  211. package/skills/korean-transit-route/instruction.md +113 -0
  212. package/skills/korean-transit-route/skill.json +10 -0
  213. package/skills/kosis-stats/instruction.md +232 -0
  214. package/skills/kosis-stats/references/kosis-openapi-guide.md +171 -0
  215. package/skills/kosis-stats/scripts/run_kosis_stats.py +896 -0
  216. package/skills/kosis-stats/skill.json +9 -0
  217. package/skills/kr-whois-lookup/instruction.md +107 -0
  218. package/skills/kr-whois-lookup/skill.json +10 -0
  219. package/skills/kstartup-search/instruction.md +186 -0
  220. package/skills/kstartup-search/scripts/run_kstartup.py +424 -0
  221. package/skills/kstartup-search/skill.json +11 -0
  222. package/skills/ktx-booking/instruction.md +244 -0
  223. package/skills/ktx-booking/skill.json +10 -0
  224. package/skills/lck-analytics/instruction.md +192 -0
  225. package/skills/lck-analytics/scripts/_lib.js +103 -0
  226. package/skills/lck-analytics/scripts/analyze-live-game.js +52 -0
  227. package/skills/lck-analytics/scripts/build-match-report.js +44 -0
  228. package/skills/lck-analytics/scripts/sync-oracle.js +50 -0
  229. package/skills/lck-analytics/skill.json +9 -0
  230. package/skills/lh-notice-search/instruction.md +206 -0
  231. package/skills/lh-notice-search/skill.json +10 -0
  232. package/skills/library-book-search/instruction.md +139 -0
  233. package/skills/library-book-search/skill.json +9 -0
  234. package/skills/local-election-candidate-search/instruction.md +77 -0
  235. package/skills/local-election-candidate-search/skill.json +8 -0
  236. package/skills/localdata-business-status/instruction.md +64 -0
  237. package/skills/localdata-business-status/scripts/localdata_business_status.py +206 -0
  238. package/skills/localdata-business-status/skill.json +8 -0
  239. package/skills/lotto-results/instruction.md +80 -0
  240. package/skills/lotto-results/skill.json +8 -0
  241. package/skills/lovebug-report/instruction.md +185 -0
  242. package/skills/lovebug-report/skill.json +9 -0
  243. package/skills/market-kurly-search/instruction.md +125 -0
  244. package/skills/market-kurly-search/skill.json +8 -0
  245. package/skills/mfds-drug-safety/instruction.md +86 -0
  246. package/skills/mfds-drug-safety/scripts/mfds_drug_safety.py +184 -0
  247. package/skills/mfds-drug-safety/skill.json +10 -0
  248. package/skills/mfds-food-safety/instruction.md +126 -0
  249. package/skills/mfds-food-safety/scripts/mfds_food_safety.py +281 -0
  250. package/skills/mfds-food-safety/skill.json +10 -0
  251. package/skills/myrealtrip-search/instruction.md +239 -0
  252. package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +194 -0
  253. package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +99 -0
  254. package/skills/myrealtrip-search/skill.json +9 -0
  255. package/skills/naming-house/instruction.md +146 -0
  256. package/skills/naming-house/skill.json +8 -0
  257. package/skills/national-pension-workplace/instruction.md +64 -0
  258. package/skills/national-pension-workplace/scripts/national_pension_workplace.py +113 -0
  259. package/skills/national-pension-workplace/skill.json +10 -0
  260. package/skills/naver-ad-performance/instruction.md +108 -0
  261. package/skills/naver-ad-performance/scripts/naver_ad_performance.py +240 -0
  262. package/skills/naver-ad-performance/skill.json +9 -0
  263. package/skills/naver-blog-research/instruction.md +128 -0
  264. package/skills/naver-blog-research/scripts/_naver_http.py +58 -0
  265. package/skills/naver-blog-research/scripts/naver_download_images.py +233 -0
  266. package/skills/naver-blog-research/scripts/naver_read.py +256 -0
  267. package/skills/naver-blog-research/scripts/naver_search.py +192 -0
  268. package/skills/naver-blog-research/skill.json +8 -0
  269. package/skills/naver-news-search/instruction.md +103 -0
  270. package/skills/naver-news-search/skill.json +10 -0
  271. package/skills/naver-shopping-search/instruction.md +94 -0
  272. package/skills/naver-shopping-search/skill.json +11 -0
  273. package/skills/nhis-care-checkup-search/instruction.md +116 -0
  274. package/skills/nhis-care-checkup-search/skill.json +10 -0
  275. package/skills/nts-business-registration/instruction.md +115 -0
  276. package/skills/nts-business-registration/scripts/nts_business_registration.py +215 -0
  277. package/skills/nts-business-registration/skill.json +10 -0
  278. package/skills/nts-tax-delinquency/instruction.md +54 -0
  279. package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +150 -0
  280. package/skills/nts-tax-delinquency/skill.json +8 -0
  281. package/skills/ohou-today-deal/instruction.md +182 -0
  282. package/skills/ohou-today-deal/scripts/ohou_today_deal.py +369 -0
  283. package/skills/ohou-today-deal/skill.json +9 -0
  284. package/skills/olive-young-search/instruction.md +154 -0
  285. package/skills/olive-young-search/skill.json +8 -0
  286. package/skills/parking-lot-search/instruction.md +96 -0
  287. package/skills/parking-lot-search/skill.json +9 -0
  288. package/skills/popbill/instruction.md +146 -0
  289. package/skills/popbill/scripts/popbill_cli.py +284 -0
  290. package/skills/popbill/scripts/popbill_registry.py +54 -0
  291. package/skills/popbill/scripts/popbill_safety.py +37 -0
  292. package/skills/popbill/scripts/popbill_templates.py +71 -0
  293. package/skills/popbill/skill.json +9 -0
  294. package/skills/public-restroom-nearby/instruction.md +89 -0
  295. package/skills/public-restroom-nearby/skill.json +9 -0
  296. package/skills/real-estate-search/instruction.md +172 -0
  297. package/skills/real-estate-search/skill.json +10 -0
  298. package/skills/rhwp-advanced/instruction.md +145 -0
  299. package/skills/rhwp-advanced/skill.json +8 -0
  300. package/skills/rhwp-edit/instruction.md +153 -0
  301. package/skills/rhwp-edit/skill.json +8 -0
  302. package/skills/s2b-notice-search/instruction.md +66 -0
  303. package/skills/s2b-notice-search/skill.json +9 -0
  304. package/skills/saju-fortune/instruction.md +177 -0
  305. package/skills/saju-fortune/skill.json +8 -0
  306. package/skills/saramin-talent-search/instruction.md +119 -0
  307. package/skills/saramin-talent-search/skill.json +9 -0
  308. package/skills/seoul-bike/instruction.md +83 -0
  309. package/skills/seoul-bike/scripts/seoul_bike.py +247 -0
  310. package/skills/seoul-bike/skill.json +10 -0
  311. package/skills/seoul-density/instruction.md +109 -0
  312. package/skills/seoul-density/scripts/seoul_density.py +271 -0
  313. package/skills/seoul-density/skill.json +10 -0
  314. package/skills/seoul-subway-arrival/instruction.md +85 -0
  315. package/skills/seoul-subway-arrival/skill.json +9 -0
  316. package/skills/sh-notice-search/instruction.md +150 -0
  317. package/skills/sh-notice-search/skill.json +9 -0
  318. package/skills/srt-booking/instruction.md +181 -0
  319. package/skills/srt-booking/scripts/srt_booking.py +272 -0
  320. package/skills/srt-booking/scripts/srt_seats.py +156 -0
  321. package/skills/srt-booking/skill.json +20 -0
  322. package/skills/subway-lost-property/instruction.md +93 -0
  323. package/skills/subway-lost-property/scripts/subway_lost_property.py +244 -0
  324. package/skills/subway-lost-property/skill.json +8 -0
  325. package/skills/ticket-availability/instruction.md +175 -0
  326. package/skills/ticket-availability/scripts/ticket_availability.py +430 -0
  327. package/skills/ticket-availability/skill.json +9 -0
  328. package/skills/toss-securities/instruction.md +116 -0
  329. package/skills/toss-securities/skill.json +9 -0
  330. package/skills/used-car-price-search/instruction.md +109 -0
  331. package/skills/used-car-price-search/skill.json +8 -0
  332. package/skills/yebigun-training/instruction.md +177 -0
  333. package/skills/yebigun-training/skill.json +9 -0
  334. package/skills/zipcode-search/instruction.md +159 -0
  335. package/skills/zipcode-search/scripts/zipcode_search.py +150 -0
  336. package/skills/zipcode-search/skill.json +8 -0
  337. package/src/assemble.js +134 -0
  338. package/src/detect.js +18 -0
  339. package/templates/action-account.md +4 -0
  340. package/templates/action-booking.md +5 -0
  341. package/templates/action-commerce.md +5 -0
  342. package/templates/action-communication.md +5 -0
  343. package/templates/action-submission.md +5 -0
  344. package/templates/browser.md +4 -0
  345. package/templates/core.md +5 -0
  346. package/templates/hard-boundary.md +3 -0
  347. package/templates/local.md +2 -0
  348. package/templates/lookup.md +2 -0
  349. package/templates/proxy.md +2 -0
  350. package/templates/vault.md +4 -0
@@ -0,0 +1,113 @@
1
+ """FSC corporate-outline lookup via k-skill-proxy.
2
+
3
+ The proxy holds DATA_GO_KR_API_KEY server-side; this helper only builds the
4
+ query and reads the structured response. No user secret is required.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import os
12
+ import re
13
+ import sys
14
+ import urllib.error
15
+ import urllib.parse
16
+ import urllib.request
17
+ from typing import Any
18
+
19
+ PROXY_BASE_URL_ENV_VAR = "KSKILL_PROXY_BASE_URL"
20
+ DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
21
+ PROXY_DOWN_MSG = "설정된 k-skill-proxy 서버가 응답하지 않습니다. 잠시 후 재시도하거나 운영자에게 문의하세요."
22
+ PROXY_KEY_NOT_CONFIGURED_MSG = "k-skill-proxy에 필요한 API 키가 설정되어 있지 않습니다. 운영자에게 문의하세요."
23
+ ROUTE = "/v1/fsc/corp-outline"
24
+
25
+
26
+ class ApiError(RuntimeError):
27
+ def __init__(self, message: str, *, status_code: int | None = None):
28
+ super().__init__(message)
29
+ self.status_code = status_code
30
+
31
+
32
+ def _text_or_none(value: Any) -> str | None:
33
+ if value is None:
34
+ return None
35
+ text = str(value).strip()
36
+ return text or None
37
+
38
+
39
+ def resolve_proxy_base_url(explicit: str | None = None, env: dict[str, str] | None = None) -> str:
40
+ env = os.environ if env is None else env
41
+ candidate = _text_or_none(explicit or env.get(PROXY_BASE_URL_ENV_VAR))
42
+ if candidate and candidate.casefold() in {"off", "false", "0", "disable", "disabled", "none"}:
43
+ raise ValueError("KSKILL_PROXY_BASE_URL 가 비활성화되어 있습니다.")
44
+ if candidate and candidate != "replace-me":
45
+ return candidate.rstrip("/")
46
+ return DEFAULT_PROXY_BASE_URL
47
+
48
+
49
+ def read_json_response(request: urllib.request.Request) -> dict[str, Any]:
50
+ try:
51
+ with urllib.request.urlopen(request, timeout=30) as response:
52
+ try:
53
+ payload = json.loads(response.read().decode("utf-8"))
54
+ except json.JSONDecodeError as error:
55
+ raise ApiError("fsc corp-outline proxy returned invalid JSON.") from error
56
+ if not isinstance(payload, dict):
57
+ raise ApiError("fsc corp-outline proxy returned a non-object JSON payload.")
58
+ return payload
59
+ except urllib.error.HTTPError as error:
60
+ body = error.read().decode("utf-8", errors="replace")
61
+ try:
62
+ payload = json.loads(body)
63
+ except json.JSONDecodeError:
64
+ payload = None
65
+ if error.code == 503 and isinstance(payload, dict) and payload.get("error") == "upstream_not_configured":
66
+ raise ApiError(PROXY_KEY_NOT_CONFIGURED_MSG, status_code=error.code) from error
67
+ if isinstance(payload, dict) and payload.get("message"):
68
+ raise ApiError(str(payload["message"]), status_code=error.code) from error
69
+ raise ApiError(f"fsc corp-outline proxy request failed with HTTP {error.code}", status_code=error.code) from error
70
+ except urllib.error.URLError as error:
71
+ raise ApiError(f"{PROXY_DOWN_MSG} (상세: {error.reason})") from error
72
+
73
+
74
+ def query_corp_outline(name: str, b_no: str | None = None, *, base_url: str | None = None,
75
+ read_json: Any = read_json_response) -> dict[str, Any]:
76
+ name = _text_or_none(name)
77
+ if not name:
78
+ raise ValueError("법인명(corpNm)을 입력하세요. 이 API는 사업자번호 단독 조회가 불가합니다.")
79
+ params = {"name": name}
80
+ if _text_or_none(b_no):
81
+ digits = re.sub(r"\D", "", str(b_no))
82
+ if not re.fullmatch(r"\d{10}", digits):
83
+ raise ValueError("사업자등록번호는 숫자 10자리여야 합니다 (하이픈 허용).")
84
+ params["b_no"] = digits
85
+ url = f"{resolve_proxy_base_url(base_url)}{ROUTE}?{urllib.parse.urlencode(params)}"
86
+ request = urllib.request.Request(url, headers={
87
+ "Accept": "application/json",
88
+ "User-Agent": "k-skill-fsc-corporate-info/1.0",
89
+ }, method="GET")
90
+ return read_json(request)
91
+
92
+
93
+ def build_parser() -> argparse.ArgumentParser:
94
+ parser = argparse.ArgumentParser(description="금융위 기업기본정보(법인 개요) 조회 (k-skill-proxy 경유)")
95
+ parser.add_argument("--name", required=True, help="법인명(corpNm) — 필수")
96
+ parser.add_argument("--b-no", help="사업자등록번호 — 응답에 bzno가 있을 때 교차검증에만 사용")
97
+ parser.add_argument("--proxy-base-url")
98
+ return parser
99
+
100
+
101
+ def main(argv: list[str] | None = None) -> int:
102
+ args = build_parser().parse_args(argv)
103
+ try:
104
+ result = query_corp_outline(args.name, args.b_no, base_url=args.proxy_base_url)
105
+ print(json.dumps(result, ensure_ascii=False, indent=2))
106
+ return 0
107
+ except (ValueError, ApiError) as error:
108
+ print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
109
+ return 1
110
+
111
+
112
+ if __name__ == "__main__":
113
+ raise SystemExit(main())
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "fsc-corporate-info",
3
+ "description": "금융위원회 기업기본정보(법인 개요)를 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 법인명으로 대표자·설립일·업종 등 법인 개요를 확인하고, 응답에 사업자번호가 있으면 입력 번호와 교차검증한다.",
4
+ "profiles": [
5
+ "proxy",
6
+ "vault",
7
+ "lookup"
8
+ ],
9
+ "frontmatter": "name: fsc-corporate-info\ndescription: 금융위원회 기업기본정보(법인 개요)를 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 법인명으로 대표자·설립일·업종 등 법인 개요를 확인하고, 응답에 사업자번호가 있으면 입력 번호와 교차검증한다.\nlicense: MIT\nmetadata:\n category: business\n locale: ko-KR\n phase: v1"
10
+ }
@@ -0,0 +1,131 @@
1
+ # 나라장터 발주계획 검색
2
+
3
+ ## What this skill does
4
+
5
+ `g2b-order-plan-search`는 공공데이터포털의 **조달청_나라장터 발주계획현황서비스**(data.go.kr 15129462, `OrderPlanSttusService`)를 `k-skill-proxy` 경유로 호출해 나라장터에 등록된 발주계획 목록을 조회한다.
6
+
7
+ - 대상 업무: 물품, 공사, 용역, 외자
8
+ - 검색 조건: 발주년월 범위, 게시일시 범위, 발주기관명/코드, 사업명 키워드, 조달방식, 기관소재지, 세부품명번호, 업무유형, 공종 등
9
+ - 반환: 발주년도/월, 발주기관, 사업명, 계약방법, 발주금액, 담당부서/담당자/전화번호, 입찰공고번호목록 등 upstream 원문 필드
10
+
11
+ 입찰 참가, 로그인, 입찰서 제출, 인증서 작업, 결제, 낙찰/계약 처리 자동화는 하지 않는다.
12
+
13
+ ## Public access path discovered
14
+
15
+ ### Primary source: 공공데이터포털 API through k-skill-proxy
16
+
17
+ - data.go.kr dataset: <https://www.data.go.kr/data/15129462/openapi.do>
18
+ - upstream base: `https://apis.data.go.kr/1230000/ao/OrderPlanSttusService`
19
+ - proxy route: `GET /v1/g2b/order-plans`
20
+ - API key location: proxy server only (`DATA_GO_KR_API_KEY`)
21
+
22
+ Operations used:
23
+
24
+ | kind | endpoint |
25
+ | --- | --- |
26
+ | `goods` / `물품` | `getOrderPlanSttusListThngPPSSrch` |
27
+ | `construction` / `공사` | `getOrderPlanSttusListCnstwkPPSSrch` |
28
+ | `service` / `용역` | `getOrderPlanSttusListServcPPSSrch` |
29
+ | `foreign` / `외자` | `getOrderPlanSttusListFrgcptPPSSrch` |
30
+ | `all` / `전체` | above four endpoints, merged with `_order_plan_kind` |
31
+
32
+ The API is a free data.go.kr service that requires an application key, so it follows the repository's free-API proxy policy and does not expose the key to users.
33
+
34
+ ## When to use
35
+
36
+ - "나라장터 발주목록에서 청소 용역 찾아줘"
37
+ - "조달청 물품 발주계획 2025년 1월 목록 조회"
38
+ - "서울 기관 공사 발주계획 검색"
39
+ - "발주기관명으로 나라장터 발주계획 보여줘"
40
+
41
+ ## Prerequisites
42
+
43
+ - 인터넷 연결, `python3`
44
+ - hosted/self-host `k-skill-proxy`의 `/v1/g2b/order-plans` route 접근 가능
45
+ - proxy 운영 서버의 `DATA_GO_KR_API_KEY`가 data.go.kr 15129462에 활용신청되어 있어야 함
46
+
47
+ 사용자 측 필수 시크릿은 없다. Self-host proxy를 쓸 때만 `KSKILL_PROXY_BASE_URL`을 설정한다.
48
+
49
+ ## Inputs
50
+
51
+ CLI flags:
52
+
53
+ - `--kind`: `goods`/`물품`, `construction`/`공사`, `service`/`용역`, `foreign`/`외자`, `all`/`전체` (default `goods`)
54
+ - `--keyword`, `-q`: 사업명 검색어 (`bizNm`)
55
+ - `--order-from`, `--order-to`: 발주년월 범위 (`YYYY-MM` 또는 `YYYYMM`)
56
+ - `--posted-from`, `--posted-to`: 게시일시 범위 (`YYYY-MM-DD`, `YYYYMMDD`, `YYYYMMDDHHMM`)
57
+ - `--institution`: 발주기관명
58
+ - `--institution-code`: 발주기관코드
59
+ - `--region`: 기관소재지명
60
+ - `--procurement-method`: 조달방식
61
+ - `--product-code`: 세부품명번호(물품)
62
+ - `--business-type`: 업무유형명/업무유형코드(공사·용역·외자)
63
+ - `--construction-type`: 공종구분명(공사)
64
+ - `--page`, `--limit`: 페이지와 한 페이지 결과 수(최대 100)
65
+
66
+ ## Workflow
67
+
68
+ ### 1. Search order plans
69
+
70
+ ```bash
71
+ python3 g2b-order-plan-search/scripts/g2b_order_plan.py \
72
+ --kind service \
73
+ --keyword 청소 \
74
+ --order-from 2025-01 \
75
+ --order-to 2025-03 \
76
+ --posted-from 2025-01-01 \
77
+ --posted-to 2025-01-31 \
78
+ --limit 10
79
+ ```
80
+
81
+ ### 2. Narrow by institution or location
82
+
83
+ ```bash
84
+ python3 g2b-order-plan-search/scripts/g2b_order_plan.py \
85
+ --kind goods \
86
+ --institution 조달청 \
87
+ --region 대전 \
88
+ --order-from 2025-01 \
89
+ --order-to 2025-01
90
+ ```
91
+
92
+ ### 3. Read output conservatively
93
+
94
+ The result is the proxy JSON response. Important fields:
95
+
96
+ - `query`: normalized upstream parameters and selected operation
97
+ - `total_count`, `page`, `page_size`
98
+ - `items[]`: upstream order-plan rows
99
+ - `items[].orderPlanUntyNo`: 발주계획통합번호
100
+ - `items[].bizNm`: 사업명
101
+ - `items[].orderInsttNm`: 발주기관명
102
+ - `items[].orderMnth`: 발주월
103
+ - `items[].sumOrderAmt`, `items[].orderContrctAmt`: 발주금액 fields when upstream provides them
104
+ - `items[].bidNtceNoList`: linked bid notice numbers when upstream provides them
105
+
106
+ When answering, show the official source (data.go.kr dataset and g2b.go.kr manual verification URL) and state that 발주계획은 사전계획이며 실제 사전규격/입찰공고/계약과 1:1로 보장되지 않는다.
107
+
108
+ ## Done when
109
+
110
+ - The proxy route `/v1/g2b/order-plans` was queried.
111
+ - The selected `kind` mapped to the correct PPS order-plan endpoint.
112
+ - Date/month filters and keyword/institution filters are reflected in `query`.
113
+ - Results include source fields and manual verification guidance.
114
+ - No login, bidding, certificate, submission, or payment flow was automated.
115
+
116
+ ## Failure modes
117
+
118
+ - `503 upstream_not_configured`: proxy server has no `DATA_GO_KR_API_KEY`.
119
+ - `502 upstream_forbidden`: proxy key is not approved for data.go.kr service 15129462.
120
+ - `400 bad_request`: invalid kind, date/month, page, or limit input.
121
+ - `total_count = 0`: no order plans matched the selected conditions.
122
+ - Upstream resultCode `03`: data.go.kr reports no data.
123
+ - API changed endpoint names, parameters, or envelope shape.
124
+ - 발주계획은 실제 입찰공고 등록의 필수 선행조건이 아니며, 발주계획에 없는 사전규격·입찰공고·계약도 있을 수 있다.
125
+
126
+ ## Official surfaces
127
+
128
+ - 공공데이터포털: <https://www.data.go.kr/data/15129462/openapi.do>
129
+ - upstream: `https://apis.data.go.kr/1230000/ao/OrderPlanSttusService`
130
+ - 수동 대조: 나라장터 <https://www.g2b.go.kr>
131
+ - proxy route: `GET /v1/g2b/order-plans`
@@ -0,0 +1,158 @@
1
+ """나라장터 발주계획현황 조회 via k-skill-proxy.
2
+
3
+ The helper calls k-skill-proxy /v1/g2b/order-plans so DATA_GO_KR_API_KEY stays
4
+ server-side. It is read-only and does not automate g2b.go.kr login flows.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import os
12
+ import sys
13
+ import urllib.error
14
+ import urllib.parse
15
+ import urllib.request
16
+ from collections.abc import Mapping
17
+ from typing import Any
18
+
19
+ PROXY_BASE_URL_ENV_VAR = "KSKILL_PROXY_BASE_URL"
20
+ DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
21
+ PROXY_DOWN_MSG = "설정된 k-skill-proxy 서버가 응답하지 않습니다. 잠시 후 재시도하거나 운영자에게 문의하세요."
22
+ PROXY_KEY_NOT_CONFIGURED_MSG = "k-skill-proxy에 필요한 API 키가 설정되어 있지 않습니다. 운영자에게 문의하세요."
23
+ ROUTE = "/v1/g2b/order-plans"
24
+ USER_AGENT = "k-skill-g2b-order-plan-search/0.1 (+https://github.com/NomaDamas/k-skill)"
25
+
26
+
27
+ class ApiError(RuntimeError):
28
+ def __init__(self, message: str, *, status_code: int | None = None):
29
+ super().__init__(message)
30
+ self.status_code = status_code
31
+
32
+
33
+ def _text_or_none(value: Any) -> str | None:
34
+ if value is None:
35
+ return None
36
+ text = str(value).strip()
37
+ return text or None
38
+
39
+
40
+ def resolve_proxy_base_url(explicit: str | None = None, env: Mapping[str, str] | None = None) -> str:
41
+ env = os.environ if env is None else env
42
+ raw = _text_or_none(explicit) or _text_or_none(env.get(PROXY_BASE_URL_ENV_VAR))
43
+ if raw:
44
+ parsed = urllib.parse.urlparse(raw)
45
+ if parsed.scheme not in {"http", "https"} or not parsed.netloc:
46
+ raise ValueError(f"{PROXY_BASE_URL_ENV_VAR} must be an http(s) URL")
47
+ return raw.rstrip("/")
48
+ return DEFAULT_PROXY_BASE_URL
49
+
50
+
51
+ def _normalize_month(value: str | None, label: str) -> str | None:
52
+ raw = _text_or_none(value)
53
+ if raw is None:
54
+ return None
55
+ digits = "".join(ch for ch in raw if ch.isdigit())
56
+ if len(digits) != 6 or not 1 <= int(digits[4:6]) <= 12:
57
+ raise ValueError(f"{label} must be YYYY-MM or YYYYMM")
58
+ return digits
59
+
60
+
61
+ def _normalize_date(value: str | None, label: str) -> str | None:
62
+ raw = _text_or_none(value)
63
+ if raw is None:
64
+ return None
65
+ digits = "".join(ch for ch in raw if ch.isdigit())
66
+ if len(digits) not in {8, 12}:
67
+ raise ValueError(f"{label} must be YYYY-MM-DD, YYYYMMDD, or YYYYMMDDHHMM")
68
+ return digits
69
+
70
+
71
+ def build_query(args: argparse.Namespace) -> dict[str, str]:
72
+ query: dict[str, str] = {
73
+ "kind": args.kind,
74
+ "page": str(args.page),
75
+ "limit": str(args.limit),
76
+ }
77
+ optional = {
78
+ "keyword": args.keyword,
79
+ "orderFrom": _normalize_month(args.order_from, "--order-from"),
80
+ "orderTo": _normalize_month(args.order_to, "--order-to"),
81
+ "postedFrom": _normalize_date(args.posted_from, "--posted-from"),
82
+ "postedTo": _normalize_date(args.posted_to, "--posted-to"),
83
+ "institution": args.institution,
84
+ "institutionCode": args.institution_code,
85
+ "region": args.region,
86
+ "procurementMethod": args.procurement_method,
87
+ "productCode": args.product_code,
88
+ "businessType": args.business_type,
89
+ "constructionType": args.construction_type,
90
+ }
91
+ for key, value in optional.items():
92
+ text = _text_or_none(value)
93
+ if text is not None:
94
+ query[key] = text
95
+ return query
96
+
97
+
98
+ def read_json_response(request: urllib.request.Request) -> dict[str, Any]:
99
+ try:
100
+ with urllib.request.urlopen(request, timeout=30) as response:
101
+ charset = response.headers.get_content_charset() or "utf-8"
102
+ return json.loads(response.read().decode(charset))
103
+ except urllib.error.HTTPError as error:
104
+ body = error.read().decode("utf-8", errors="replace")
105
+ try:
106
+ parsed = json.loads(body)
107
+ except json.JSONDecodeError:
108
+ parsed = {"message": body}
109
+ if error.code == 503 and parsed.get("error") == "upstream_not_configured":
110
+ raise ApiError(PROXY_KEY_NOT_CONFIGURED_MSG, status_code=error.code) from error
111
+ message = parsed.get("message") or parsed.get("error") or body or str(error)
112
+ raise ApiError(f"g2b order-plan proxy returned HTTP {error.code}: {message}", status_code=error.code) from error
113
+ except urllib.error.URLError as error:
114
+ raise ApiError(f"{PROXY_DOWN_MSG} (상세: {error.reason})") from error
115
+
116
+
117
+ def search_order_plans(query: dict[str, str], *, base_url: str | None = None,
118
+ read_json: Any = read_json_response) -> dict[str, Any]:
119
+ resolved_base = resolve_proxy_base_url(base_url)
120
+ url = f"{resolved_base}{ROUTE}?{urllib.parse.urlencode(query)}"
121
+ request = urllib.request.Request(url, headers={"Accept": "application/json", "User-Agent": USER_AGENT})
122
+ return read_json(request)
123
+
124
+
125
+ def build_parser() -> argparse.ArgumentParser:
126
+ parser = argparse.ArgumentParser(description="나라장터 발주계획현황 조회 (k-skill-proxy 경유)")
127
+ parser.add_argument("--kind", default="goods", help="goods/물품, construction/공사, service/용역, foreign/외자, all/전체")
128
+ parser.add_argument("--keyword", "-q", help="사업명 검색어(bizNm)")
129
+ parser.add_argument("--order-from", help="발주시작년월 YYYY-MM 또는 YYYYMM")
130
+ parser.add_argument("--order-to", help="발주종료년월 YYYY-MM 또는 YYYYMM")
131
+ parser.add_argument("--posted-from", help="게시 시작일 YYYY-MM-DD 또는 YYYYMMDD[HHMM]")
132
+ parser.add_argument("--posted-to", help="게시 종료일 YYYY-MM-DD 또는 YYYYMMDD[HHMM]")
133
+ parser.add_argument("--institution", help="발주기관명")
134
+ parser.add_argument("--institution-code", help="발주기관코드")
135
+ parser.add_argument("--region", help="기관소재지명")
136
+ parser.add_argument("--procurement-method", help="조달방식")
137
+ parser.add_argument("--product-code", help="세부품명번호(물품)")
138
+ parser.add_argument("--business-type", help="업무유형명/업무유형코드(공사/용역/외자)")
139
+ parser.add_argument("--construction-type", help="공종구분명(공사)")
140
+ parser.add_argument("--page", type=int, default=1)
141
+ parser.add_argument("--limit", type=int, default=10)
142
+ parser.add_argument("--proxy-base-url", help=f"override {PROXY_BASE_URL_ENV_VAR}")
143
+ return parser
144
+
145
+
146
+ def main(argv: list[str] | None = None) -> int:
147
+ args = build_parser().parse_args(argv)
148
+ try:
149
+ result = search_order_plans(build_query(args), base_url=args.proxy_base_url)
150
+ print(json.dumps(result, ensure_ascii=False, indent=2))
151
+ return 0
152
+ except (ApiError, ValueError) as error:
153
+ print(json.dumps({"error": error.__class__.__name__, "message": str(error)}, ensure_ascii=False), file=sys.stderr)
154
+ return 1
155
+
156
+
157
+ if __name__ == "__main__":
158
+ raise SystemExit(main())
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "g2b-order-plan-search",
3
+ "description": "조달청 나라장터 발주계획현황서비스로 물품·공사·용역·외자 발주계획 목록을 검색한다. 돌쇠에서는 가능한 가장 가까운 합법적 공식 단계까지 준비한다.",
4
+ "profiles": [
5
+ "proxy",
6
+ "vault",
7
+ "hard-boundary"
8
+ ],
9
+ "frontmatter": "name: g2b-order-plan-search\ndescription: 조달청 나라장터 발주계획현황서비스로 물품·공사·용역·외자 발주계획 목록을 검색한다. 돌쇠에서는 가능한 가장 가까운 합법적 공식 단계까지 준비한다.\nlicense: MIT\nmetadata:\n category: business\n locale: ko-KR\n phase: v1"
10
+ }
@@ -0,0 +1,61 @@
1
+ # 나라장터 부정당제재업체정보 조회
2
+
3
+ ## What this skill does
4
+
5
+ 공공데이터포털의 **조달청 나라장터 사용자정보 서비스**(data.go.kr 15129466, `getUnptRsttCorpInfo02`)를 `k-skill-proxy` 경유로 호출해, 사업자등록번호 정확 일치(`inqryDiv=1`)로 **조회시점 현재 유효한** 부정당제재를 조회한다.
6
+
7
+ - 반환: 제재 시작/종료일자, 제재기관명, 계약법구분, 제재근거법률 등 upstream 필드 원문
8
+
9
+ ## Coverage boundary
10
+
11
+ upstream 명세상 다음은 **제공되지 않는다** — 과거 이력 조회가 아니다.
12
+
13
+ - 조회시점에 제재만료·해제된 건
14
+ - 나라장터 미등록업체·개인에 대한 제재
15
+
16
+ 만료 이력까지 보려면 나라장터(<https://www.g2b.go.kr>)에서 수동 확인이 필요하다.
17
+
18
+ ## Design principles
19
+
20
+ - 점수·등급·해석 라벨을 만들지 않는다. upstream 사실 + 출처 + 적용범위 한계만 담는다.
21
+
22
+ ## When to use
23
+
24
+ - "이 회사 입찰 제재(부정당제재) 이력 있어?"
25
+ - "거래/계약 전에 부정당업자 제재 여부 확인해줘"
26
+
27
+ ## Prerequisites
28
+
29
+ - 인터넷 연결, `python3`
30
+ - `scripts/g2b_sanctioned_supplier.py` helper
31
+ - hosted/self-host `k-skill-proxy`의 `/v1/g2b/sanctioned-supplier` route 접근 가능
32
+
33
+ ## Credential requirements
34
+
35
+ - 사용자 측 필수 시크릿 없음.
36
+ - `KSKILL_PROXY_BASE_URL` — self-host 프록시를 쓸 때만 설정. 비우면 hosted `https://k-skill-proxy.nomadamas.org` 사용.
37
+ - `DATA_GO_KR_API_KEY` 는 프록시 운영 서버 환경에만 둔다. 공공데이터포털에서 `조달청_나라장터 사용자정보 서비스`(부정당제재업체정보조회 포함) 활용신청이 되어 있어야 한다.
38
+
39
+ ## Inputs
40
+
41
+ - `--bizno`: 사업자등록번호 10자리(하이픈 허용) — 필수
42
+
43
+ ## CLI examples
44
+
45
+ ```bash
46
+ python3 g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py --bizno 124-81-00998
47
+ ```
48
+
49
+ ## Failure modes
50
+
51
+ - `400 bad_request`: 사업자번호가 10자리가 아님.
52
+ - `503 upstream_not_configured`: 프록시 서버에 `DATA_GO_KR_API_KEY` 없음.
53
+ - `502 upstream_forbidden`: 프록시 키가 15129466에 활용신청되지 않음.
54
+ - `total_count = 0`: 조회시점 현재 유효한 제재 없음 (만료·미등록업체는 미제공임에 유의).
55
+
56
+ ## Official surfaces
57
+
58
+ - 공공데이터포털: <https://www.data.go.kr/data/15129466/openapi.do>
59
+ - upstream: `https://apis.data.go.kr/1230000/ao/UsrInfoService02/getUnptRsttCorpInfo02`
60
+ - 수동 대조: 나라장터 <https://www.g2b.go.kr>
61
+ - 프록시 route: `GET /v1/g2b/sanctioned-supplier`
@@ -0,0 +1,114 @@
1
+ """Procurement (나라장터) sanctioned-supplier lookup via k-skill-proxy.
2
+
3
+ The proxy holds DATA_GO_KR_API_KEY server-side; this helper only builds the
4
+ query and reads the structured response. No user secret is required.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import os
12
+ import re
13
+ import sys
14
+ import urllib.error
15
+ import urllib.parse
16
+ import urllib.request
17
+ from typing import Any
18
+
19
+ PROXY_BASE_URL_ENV_VAR = "KSKILL_PROXY_BASE_URL"
20
+ DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
21
+ PROXY_DOWN_MSG = "설정된 k-skill-proxy 서버가 응답하지 않습니다. 잠시 후 재시도하거나 운영자에게 문의하세요."
22
+ PROXY_KEY_NOT_CONFIGURED_MSG = "k-skill-proxy에 필요한 API 키가 설정되어 있지 않습니다. 운영자에게 문의하세요."
23
+ ROUTE = "/v1/g2b/sanctioned-supplier"
24
+
25
+
26
+ class ApiError(RuntimeError):
27
+ def __init__(self, message: str, *, status_code: int | None = None):
28
+ super().__init__(message)
29
+ self.status_code = status_code
30
+
31
+
32
+ def _text_or_none(value: Any) -> str | None:
33
+ if value is None:
34
+ return None
35
+ text = str(value).strip()
36
+ return text or None
37
+
38
+
39
+ def resolve_proxy_base_url(explicit: str | None = None, env: dict[str, str] | None = None) -> str:
40
+ env = os.environ if env is None else env
41
+ candidate = _text_or_none(explicit or env.get(PROXY_BASE_URL_ENV_VAR))
42
+ if candidate and candidate.casefold() in {"off", "false", "0", "disable", "disabled", "none"}:
43
+ raise ValueError("KSKILL_PROXY_BASE_URL 가 비활성화되어 있습니다.")
44
+ if candidate and candidate != "replace-me":
45
+ return candidate.rstrip("/")
46
+ return DEFAULT_PROXY_BASE_URL
47
+
48
+
49
+ def normalize_bizno(value: Any) -> str:
50
+ raw = _text_or_none(value)
51
+ if not raw:
52
+ raise ValueError("사업자등록번호(bizno)를 입력하세요.")
53
+ normalized = re.sub(r"\D", "", raw)
54
+ if not re.fullmatch(r"\d{10}", normalized):
55
+ raise ValueError("사업자등록번호는 숫자 10자리여야 합니다.")
56
+ return normalized
57
+
58
+
59
+ def read_json_response(request: urllib.request.Request) -> dict[str, Any]:
60
+ try:
61
+ with urllib.request.urlopen(request, timeout=30) as response:
62
+ try:
63
+ payload = json.loads(response.read().decode("utf-8"))
64
+ except json.JSONDecodeError as error:
65
+ raise ApiError("g2b sanction proxy returned invalid JSON.") from error
66
+ if not isinstance(payload, dict):
67
+ raise ApiError("g2b sanction proxy returned a non-object JSON payload.")
68
+ return payload
69
+ except urllib.error.HTTPError as error:
70
+ body = error.read().decode("utf-8", errors="replace")
71
+ try:
72
+ payload = json.loads(body)
73
+ except json.JSONDecodeError:
74
+ payload = None
75
+ if error.code == 503 and isinstance(payload, dict) and payload.get("error") == "upstream_not_configured":
76
+ raise ApiError(PROXY_KEY_NOT_CONFIGURED_MSG, status_code=error.code) from error
77
+ if isinstance(payload, dict) and payload.get("message"):
78
+ raise ApiError(str(payload["message"]), status_code=error.code) from error
79
+ raise ApiError(f"g2b sanction proxy request failed with HTTP {error.code}", status_code=error.code) from error
80
+ except urllib.error.URLError as error:
81
+ raise ApiError(f"{PROXY_DOWN_MSG} (상세: {error.reason})") from error
82
+
83
+
84
+ def query_sanctions(bizno: str, *, base_url: str | None = None,
85
+ read_json: Any = read_json_response) -> dict[str, Any]:
86
+ normalized = normalize_bizno(bizno)
87
+ url = f"{resolve_proxy_base_url(base_url)}{ROUTE}?{urllib.parse.urlencode({'bizno': normalized})}"
88
+ request = urllib.request.Request(url, headers={
89
+ "Accept": "application/json",
90
+ "User-Agent": "k-skill-g2b-sanctioned-supplier/1.0",
91
+ }, method="GET")
92
+ return read_json(request)
93
+
94
+
95
+ def build_parser() -> argparse.ArgumentParser:
96
+ parser = argparse.ArgumentParser(description="나라장터 부정당제재업체정보 조회 (k-skill-proxy 경유)")
97
+ parser.add_argument("--bizno", required=True, help="사업자등록번호 10자리(하이픈 허용)")
98
+ parser.add_argument("--proxy-base-url")
99
+ return parser
100
+
101
+
102
+ def main(argv: list[str] | None = None) -> int:
103
+ args = build_parser().parse_args(argv)
104
+ try:
105
+ result = query_sanctions(args.bizno, base_url=args.proxy_base_url)
106
+ print(json.dumps(result, ensure_ascii=False, indent=2))
107
+ return 0
108
+ except (ValueError, ApiError) as error:
109
+ print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
110
+ return 1
111
+
112
+
113
+ if __name__ == "__main__":
114
+ raise SystemExit(main())
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "g2b-sanctioned-supplier",
3
+ "description": "조달청 나라장터 부정당제재업체정보를 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 사업자등록번호 정확 일치로 조회시점 현재 유효한 입찰참가자격 제한(부정당제재)의 기간·제재기관·근거법률을 확인한다.",
4
+ "profiles": [
5
+ "proxy",
6
+ "vault",
7
+ "lookup"
8
+ ],
9
+ "frontmatter": "name: g2b-sanctioned-supplier\ndescription: 조달청 나라장터 부정당제재업체정보를 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 사업자등록번호 정확 일치로 조회시점 현재 유효한 입찰참가자격 제한(부정당제재)의 기간·제재기관·근거법률을 확인한다.\nlicense: MIT\nmetadata:\n category: business\n locale: ko-KR\n phase: v1"
10
+ }