
Localdata Business Status
- 1.2k installs
- 7k repo stars
- Updated August 2, 2026
- nomadamas/k-skill
Helps with ai & agent building tasks.
About
localdata-business-status is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- localdata-business-status
- AI & Agent Building
- AI-coding skill
Localdata Business Status by the numbers
- 1,154 all-time installs (skills.sh)
- +242 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #950 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nomadamas/k-skill --skill localdata-business-statusAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.2k |
|---|---|
| repo stars | ★ 7k |
| Last updated | August 2, 2026 |
| Repository | nomadamas/k-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
지방행정 인허가 영업상태 조회 (동네 사업장)
What this skill does
행정안전부 지방행정 인허가데이터(LOCALDATA)의 지역별 CSV를 file.localdata.go.kr에서 직접 받아, 동네 사업장의 영업상태를 조회한다.
- 영업상태(영업/휴업/폐업), 상세영업상태, 인허가일자(업력), 폐업일자, 업태구분, 도로명/지번 주소, 데이터갱신시점
- 인허가 업종 208종 전체 지원 — 한글명("약국", "숙박업", "일반음식점")으로 지정 가능
전국 통파일이 업종당 수백 MB라 시군구 단위 지역 지정(--region)이 필요하다. 받은 파일은 1일 로컬 캐시한다.
이 자료에는 사업자등록번호가 수록되지 않는다. 상호(사업장명) 문자열 일치 후보의 사실만 나열하며, 동명 상호 가능성은 사용자가 판단한다. 자료는 매일 갱신되며 2일 전 기준으로 현행화된다.
Design principles
- 점수·등급·해석 라벨을 만들지 않는다. 조회된 사실 + 출처 + 조회시각만 담는다.
- 인증 없이 동작하는 공개 파일 서버이므로 프록시를 거치지 않고 사용자 머신에서 직접 호출한다.
When to use
- "제주시 ○○호텔 지금 영업 중이야? 오래된 곳이야?" — 사업자번호를 몰라도 상호+시군구로 조회
- "이 동네 가게 폐업했어?", "이 식당 인허가가 언제야(업력)?"
Prerequisites
- 인터넷 연결,
python3(stdlib만 사용 — 추가 의존성 없음) scripts/localdata_business_status.pyhelperdata/localdata_industries.json(업종 208종),data/localdata_orgcodes.json(지자체 245종)
Credential requirements
- 없음. 무인증 공개 파일 다운로드다.
Inputs
--name: 상호(사업장명) — 필수--region: 시군구 — 필수 (예:제주제주시,서울종로구,경기수원시)--industry: 업종 slug 또는 한글명 (여러 번 지정 가능). 생략 시 일반음식점·휴게음식점·숙박업
Privacy boundary
- 입력한 상호·지역은 LOCALDATA 파일 서버로 전송된다(다운로드 요청 파라미터).
- 자료에 사업자등록번호가 없어 상호 문자열 매칭이며 동일성을 단정하지 않는다.
CLI examples
python3 localdata-business-status/scripts/localdata_business_status.py \
--name "호텔샬롬" --region 제주제주시 --industry 숙박업
# 업종 여러 개
python3 localdata-business-status/scripts/localdata_business_status.py \
--name "○○약국" --region 서울종로구 --industry 약국Failure modes
unavailable+ 안내: 상호/지역 미입력, 지역·업종 특정 실패(후보 나열), 다운로드 실패 — 수동 확인 URL 제공.- 0건: 매치 없음 (
total_match_count: 0).
Official surfaces
- 인허가 영업상태:
https://file.localdata.go.kr/file/download/<업종slug>/info?orgCode=<지자체코드>(무인증, Referer 필요, CP949 CSV) - 본체: <https://www.localdata.go.kr>
{
"affiliated_medical_institutions": "건강_부속의료기관",
"air_pollution_facility_installation": "자원환경_대기오염물질배출시설설치사업장",
"amusement_facilities_other": "문화_테마파크업(기타)",
"animal_boarding": "동물_동물위탁관리업",
"animal_breeding": "동물_동물생산업",
"animal_cremation": "동물_동물장묘업",
"animal_exhibition": "동물_동물전시업",
"animal_hospitals": "동물_동물병원",
"animal_import": "동물_동물수입업",
"animal_pharmacies": "동물_동물약국",
"animal_sales": "동물_동물판매업",
"animal_transport": "동물_동물운송업",
"artificial_insemination_centers": "동물_가축인공수정소",
"auto_campgrounds": "문화_자동차야영장업",
"bakeries": "식품_제과점영업",
"barber_shops": "생활_이용업",
"beauty_salons": "생활_미용업",
"bicycle_parking_info": "자전거보관소정보",
"billiard_halls": "생활_당구장업",
"breeding_stock_businesses": "동물_종축업",
"briquette_manufacturers": "자원환경_석연탄제조업",
"building_sanitation": "자원환경_건물위생관리업",
"car_wash_info": "세차장정보",
"caregiver_training": "기타_요양보호사교육기관",
"cctv_info": "CCTV정보",
"city_gas_companies": "자원환경_일반도시가스업체",
"city_tour_businesses": "문화_시내순환관광업",
"civil_defense_shelter_info": "민방위대피시설",
"civil_defense_water_facilities": "기타_민방위급수시설",
"clinics": "건강_의원",
"comprehensive_amusement_facilities": "문화_종합테마파크업",
"comprehensive_resorts": "문화_종합휴양업",
"comprehensive_sports_facilities": "생활_종합체육시설업",
"comprehensive_travel_agencies": "문화_종합여행업",
"construction_waste_disposal": "자원환경_건설폐기물처리업",
"container_packaging_manufacturers": "식품_용기및포장지제조업",
"container_refrigeration_equipment": "식품_용기냉동기특정설비",
"contract_catering": "식품_위탁급식영업",
"cultural_art_corporations": "문화_문화예술법인",
"dance_academies": "생활_무도학원업",
"dance_halls": "생활_무도장업",
"dental_labs": "건강_치과기공소",
"disinfection_companies": "자원환경_소독업",
"distribution_specialty_retailers": "식품_유통전문판매업",
"domestic_international_travel_agencies": "문화_국내외여행업",
"domestic_travel_agencies": "문화_국내여행업",
"door_to_door_sales": "생활_방문판매업",
"dust_emission_business_info": "비산먼지발생사업정보",
"ecommerce_businesses": "생활_통신판매업",
"edible_ice_retailers": "식품_식용얼음판매업",
"elevator_maintenance": "기타_승강기유지관리업체",
"elevator_manufacturers_importers": "기타_승강기제조및수입업체",
"emergency_call_box_info": "안전비상벨위치정보",
"emergency_patient_transport": "건강_응급환자이송업",
"emission_inspection_agencies": "자원환경_배출가스전문정비사업자(확인검사대행자)",
"entertainment_bars": "식품_유흥주점영업",
"environment_consulting_companies": "자원환경_환경컨설팅회사",
"environment_contractors": "자원환경_환경전문공사업",
"environment_management_agencies": "자원환경_환경관리대행기관",
"environment_measurement_agencies": "자원환경_환경측정대행업",
"excellent_restaurant_info": "모범음식점정보",
"feed_manufacturers": "동물_사료제조업",
"film_distributors": "문화_영화배급업",
"film_importers": "문화_영화수입업",
"film_producers": "문화_영화제작업",
"film_screenings": "문화_영화상영업",
"fishing_spot_info": "낚시터정보",
"fitness_centers": "생활_체력단련장업",
"food_additive_manufacturers": "식품_식품첨가물제조업",
"food_freezing_refrigeration": "식품_식품냉동냉장업",
"food_manufacturing_processors": "식품_식품제조가공업",
"food_repackagers": "식품_식품소분업",
"food_transporters": "식품_식품운반업",
"food_vending_machines": "식품_식품자동판매기업",
"foreigner_city_homestays": "문화_외국인관광도시민박업",
"foreigners_entertainment_restaurants": "식품_외국인전용유흥음식점업",
"free_job_centers": "기타_무료직업소개소",
"free_wifi_info": "무료와이파이정보",
"funeral_director_training": "기타_장례지도사 교육기관",
"funeral_service_providers": "기타_상조업",
"game_distributors": "문화_게임물배급업",
"game_producers": "문화_게임물제작업",
"general_amusement_facilities": "문화_일반테마파크업",
"general_campgrounds": "문화_일반야영장업",
"general_game_providers": "문화_일반게임제공업",
"general_restaurants": "식품_일반음식점",
"golf_courses": "생활_골프장",
"golf_practice_ranges": "생활_골프연습장업",
"groundwater_construction": "자원환경_지하수시공업체",
"groundwater_impact_assessment": "자원환경_지하수영향조사기관",
"groundwater_remediation": "자원환경_지하수정화업체",
"group_meal_facilities": "식품_집단급식소",
"group_meal_food_retailers": "식품_집단급식소식품판매업",
"hanok_experience": "문화_한옥체험업",
"hatcheries": "동물_부화업",
"health_functional_food_general_retailers": "식품_건강기능식품일반판매업",
"health_functional_food_specialty_retailers": "식품_건강기능식품유통전문판매업",
"high_pressure_gas": "자원환경_고압가스업",
"horse_riding": "생활_승마장업",
"hospitals": "건강_병원",
"household_waste_info": "생활쓰레기배출정보",
"ice_rinks": "생활_빙상장업",
"instant_food_processors": "식품_즉석판매제조가공업",
"international_convention_facilities": "문화_국제회의시설업",
"international_convention_planners": "문화_국제회의기획업",
"international_logistics_forwarders": "기타_국제물류주선업",
"karaoke_rooms": "문화_노래연습장업",
"large_scale_retail_stores": "생활_대규모점포",
"laundries": "생활_세탁업",
"livestock_farming": "동물_가축사육업",
"livestock_processing": "식품_축산가공업",
"livestock_retail": "식품_축산판매업",
"livestock_storage": "식품_축산물보관업",
"livestock_transport": "식품_축산물운반업",
"local_culture_centers": "문화_지방문화원",
"lodgings": "문화_숙박업",
"log_production": "자원환경_원목생산업",
"logistics_warehouses": "기타_물류창고업체",
"lpg_equipment_manufacturers": "자원환경_액화석유가스용품제조업체",
"lumber_import_distribution": "자원환경_목재수입유통업",
"manure_collection_transport": "자원환경_가축분뇨수집운반업",
"manure_facility_management": "자원환경_가축분뇨배출시설관리업(사업장)",
"martial_arts_dojo": "생활_체육도장업",
"meat_packers": "식품_식육포장처리업",
"medical_corporations": "건강_의료법인",
"medical_device_repair": "건강_의료기기수리업",
"medical_device_sales_rental": "건강_의료기기판매(임대)업",
"medical_laundry": "생활_의료기관세탁물처리업",
"medical_related_businesses": "건강_의료유사업",
"milk_collection": "식품_집유업",
"mixed_game_providers": "문화_복합유통게임제공업",
"mixed_video_content_providers": "문화_복합영상물제공업",
"movie_theaters": "문화_영화상영관",
"multilevel_marketing": "생활_다단계판매업체",
"museums_and_art_galleries": "문화_박물관 및 미술관",
"music_video_distributors": "문화_음반및음악영상물배급업",
"music_video_producers": "문화_음반및음악영상물제작업",
"night_soil_collection_transport": "자원환경_분뇨수집운반업",
"oil_retailers": "자원환경_석유판매업",
"onggi_manufacturers": "식품_옹기류제조업",
"online_music_services": "문화_온라인음악서비스제공업",
"optical_shops": "건강_안경업",
"other_food_retailers": "식품_식품판매업(기타)",
"outdoor_advertising_companies": "기타_옥외광고업",
"over_the_counter_medicine_stores": "건강_안전상비의약품 판매업소",
"paid_job_centers": "기타_유료직업소개소",
"pay_as_you_throw_bag_retailers": "자원환경_쓰레기종량제봉투판매업",
"pc_bangs": "문화_인터넷컴퓨터게임시설제공업",
"performance_halls": "문화_공연장",
"pet_grooming": "동물_동물미용업",
"petroleum_alt_fuel_retailers": "자원환경_석유및석유대체연료판매업체",
"pharmacies": "건강_약국",
"pop_culture_art_planners": "문화_대중문화예술기획업",
"postpartum_care": "건강_산후조리업",
"power_design_companies": "자원환경_전력기술설계업체",
"power_supervision_companies": "자원환경_전력기술감리업체",
"printing_shops": "기타_인쇄사",
"protected_tree_info": "보호수정보",
"public_baths": "생활_목욕장업",
"public_restroom_info": "공중화장실정보",
"publishers": "기타_출판사",
"record_distributors": "문화_음반물배급업",
"record_producers": "문화_음반물제작업",
"registered_sports_facilities": "생활_등록체육시설업",
"rest_cafes": "식품_휴게음식점",
"rural_homestays": "문화_농어촌민박업",
"sawmills": "자원환경_제재업",
"septic_sewage_design_build": "자원환경_단독정화조 및 오수처리시설설계시공업",
"singing_bars": "식품_단란주점영업",
"ski_resorts": "생활_스키장",
"slaughterhouses": "동물_도축업",
"sledding": "생활_썰매장업",
"small_sewage_facility_management": "자원환경_개인하수처리시설관리업(사업장)",
"special_resorts": "문화_전문휴양업",
"specific_high_pressure_gas": "자원환경_특정고압가스업",
"speed_bump_info": "과속방지턱정보",
"sponsored_door_to_door_sales": "생활_후원방문판매업체",
"swimming_pools": "생활_수영장업",
"telemarketing_sales": "생활_전화권유판매업",
"tobacco_import_retailers": "기타_담배수입판매업체",
"tobacco_retailers": "기타_담배소매업",
"tobacco_wholesalers": "기타_담배도매업",
"tourism_businesses": "문화_관광사업자",
"tourist_accommodations": "문화_관광숙박업",
"tourist_cruises": "문화_관광유람선업",
"tourist_entertainment_restaurants": "식품_관광유흥음식점업",
"tourist_pensions": "문화_관광펜션업",
"tourist_performance_halls": "문화_관광공연장업",
"tourist_railways": "문화_관광궤도업",
"tourist_restaurants": "식품_관광식당",
"tourist_theater_entertainment": "문화_관광극장유흥업",
"traditional_temples": "문화_전통사찰",
"veterinary_drug_wholesalers": "동물_동물용의약품도매상",
"veterinary_medical_equipment_sales": "동물_동물용의료용구판매업",
"video_distributors": "문화_비디오물배급업",
"video_mini_theaters": "문화_비디오물소극장업",
"video_producers": "문화_비디오물제작업",
"video_streaming_providers": "문화_비디오물시청제공업",
"video_viewing_rooms": "문화_비디오물감상실업",
"water_pollution_source_other": "자원환경_수질오염원설치시설(기타)",
"water_supply_agents": "자원환경_급수공사대행업",
"water_tank_cleaning": "자원환경_저수조청소업",
"weighing_instrument_certification": "자원환경_계량기증명업",
"weighing_instrument_import": "자원환경_계량기수입업",
"weighing_instrument_manufacturing": "자원환경_계량기제조업",
"weighing_instrument_repair": "자원환경_계량기수리업",
"yacht_marinas": "생활_요트장업",
"youth_game_providers": "문화_청소년게임제공업"
}{
"서울특별시 본청": "6110000",
"서울종로구": "3000000",
"서울중구": "3010000",
"서울용산구": "3020000",
"서울성동구": "3030000",
"서울광진구": "3040000",
"서울동대문구": "3050000",
"서울중랑구": "3060000",
"서울성북구": "3070000",
"서울강북구": "3080000",
"서울도봉구": "3090000",
"서울노원구": "3100000",
"서울은평구": "3110000",
"서울서대문구": "3120000",
"서울마포구": "3130000",
"서울양천구": "3140000",
"서울강서구": "3150000",
"서울구로구": "3160000",
"서울금천구": "3170000",
"서울영등포구": "3180000",
"서울동작구": "3190000",
"서울관악구": "3200000",
"서울서초구": "3210000",
"서울강남구": "3220000",
"서울송파구": "3230000",
"서울강동구": "3240000",
"부산광역시 본청": "6260000",
"부산중구": "3250000",
"부산서구": "3260000",
"부산동구": "3270000",
"부산영도구": "3280000",
"부산진구": "3290000",
"부산동래구": "3300000",
"부산남구": "3310000",
"부산북구": "3320000",
"부산해운대구": "3330000",
"부산사하구": "3340000",
"부산금정구": "3350000",
"부산강서구": "3360000",
"부산연제구": "3370000",
"부산수영구": "3380000",
"부산사상구": "3390000",
"부산기장군": "3400000",
"대구광역시 본청": "6270000",
"대구중구": "3410000",
"대구동구": "3420000",
"대구서구": "3430000",
"대구남구": "3440000",
"대구북구": "3450000",
"대구수성구": "3460000",
"대구달서구": "3470000",
"대구달성군": "3480000",
"대구군위군": "5141000",
"인천광역시 본청": "6280000",
"인천중구": "3490000",
"인천동구": "3500000",
"인천미추홀구": "3510500",
"인천연수구": "3520000",
"인천남동구": "3530000",
"인천부평구": "3540000",
"인천계양구": "3550000",
"인천서구": "3560000",
"인천강화군": "3570000",
"인천옹진군": "3580000",
"광주광역시 본청": "6290000",
"광주동구": "3590000",
"광주서구": "3600000",
"광주남구": "3610000",
"광주북구": "3620000",
"광주광산구": "3630000",
"대전광역시 본청": "6300000",
"대전동구": "3640000",
"대전중구": "3650000",
"대전서구": "3660000",
"대전유성구": "3670000",
"대전대덕구": "3680000",
"울산광역시 본청": "6310000",
"울산중구": "3690000",
"울산남구": "3700000",
"울산동구": "3710000",
"울산북구": "3720000",
"울산울주군": "3730000",
"세종특별자치시 본청": "5690000",
"경기도 본청": "6410000",
"경기평택시": "3910000",
"경기동두천시": "3920000",
"경기안산시": "3930000",
"경기고양시": "3940000",
"경기과천시": "3970000",
"경기구리시": "3980000",
"경기남양주시": "3990000",
"경기수원시": "3740000",
"경기성남시": "3780000",
"경기의정부시": "3820000",
"경기안양시": "3830000",
"경기부천시": "3860000",
"경기광명시": "3900000",
"경기오산시": "4000000",
"경기시흥시": "4010000",
"경기군포시": "4020000",
"경기의왕시": "4030000",
"경기하남시": "4040000",
"경기용인시": "4050000",
"경기파주시": "4060000",
"경기이천시": "4070000",
"경기안성시": "4080000",
"경기김포시": "4090000",
"경기여주시": "5700000",
"경기연천군": "4140000",
"경기가평군": "4160000",
"경기양평군": "4170000",
"경기화성시": "5530000",
"경기광주시": "5540000",
"경기양주시": "5590000",
"경기포천시": "5600000",
"강원특별자치도 본청": "6530000",
"강원춘천시": "4181000",
"강원원주시": "4191000",
"강원강릉시": "4201000",
"강원동해시": "4211000",
"강원태백시": "4221000",
"강원속초시": "4231000",
"강원삼척시": "4241000",
"강원홍천군": "4251000",
"강원횡성군": "4261000",
"강원영월군": "4271000",
"강원평창군": "4281000",
"강원정선군": "4291000",
"강원철원군": "4301000",
"강원화천군": "4311000",
"강원양구군": "4321000",
"강원인제군": "4331000",
"강원고성군": "4341000",
"강원양양군": "4351000",
"충청북도 본청": "6430000",
"충북청주시": "5710000",
"충북충주시": "4390000",
"충북제천시": "4400000",
"충북보은군": "4420000",
"충북옥천군": "4430000",
"충북영동군": "4440000",
"충북진천군": "4450000",
"충북괴산군": "4460000",
"충북음성군": "4470000",
"충북단양군": "4480000",
"충북증평군": "5570000",
"충청남도 본청": "6440000",
"충남당진시": "5680000",
"충남천안시": "4490000",
"충남공주시": "4500000",
"충남보령시": "4510000",
"충남아산시": "4520000",
"충남서산시": "4530000",
"충남논산시": "4540000",
"충남금산군": "4550000",
"충남부여군": "4570000",
"충남서천군": "4580000",
"충남청양군": "4590000",
"충남홍성군": "4600000",
"충남예산군": "4610000",
"충남태안군": "4620000",
"충남계룡시": "5580000",
"전북특별자치도 본청": "6540000",
"전북전주시": "4641000",
"전북군산시": "4671000",
"전북익산시": "4681000",
"전북정읍시": "4691000",
"전북남원시": "4701000",
"전북김제시": "4711000",
"전북완주군": "4721000",
"전북진안군": "4731000",
"전북무주군": "4741000",
"전북장수군": "4751000",
"전북임실군": "4761000",
"전북순창군": "4771000",
"전북고창군": "4781000",
"전북부안군": "4791000",
"전라남도 본청": "6460000",
"전남목포시": "4800000",
"전남여수시": "4810000",
"전남순천시": "4820000",
"전남나주시": "4830000",
"전남광양시": "4840000",
"전남담양군": "4850000",
"전남곡성군": "4860000",
"전남구례군": "4870000",
"전남고흥군": "4880000",
"전남보성군": "4890000",
"전남화순군": "4900000",
"전남장흥군": "4910000",
"전남강진군": "4920000",
"전남해남군": "4930000",
"전남영암군": "4940000",
"전남무안군": "4950000",
"전남함평군": "4960000",
"전남영광군": "4970000",
"전남장성군": "4980000",
"전남완도군": "4990000",
"전남진도군": "5000000",
"전남신안군": "5010000",
"경상북도 본청": "6470000",
"경북포항시": "5020000",
"경북경주시": "5050000",
"경북김천시": "5060000",
"경북안동시": "5070000",
"경북구미시": "5080000",
"경북영주시": "5090000",
"경북영천시": "5100000",
"경북상주시": "5110000",
"경북문경시": "5120000",
"경북경산시": "5130000",
"경북의성군": "5150000",
"경북청송군": "5160000",
"경북영양군": "5170000",
"경북영덕군": "5180000",
"경북청도군": "5190000",
"경북고령군": "5200000",
"경북성주군": "5210000",
"경북칠곡군": "5220000",
"경북예천군": "5230000",
"경북봉화군": "5240000",
"경북울진군": "5250000",
"경북울릉군": "5260000",
"경상남도 본청": "6480000",
"경남창원시": "5670000",
"경남진주시": "5310000",
"경남통영시": "5330000",
"경남사천시": "5340000",
"경남김해시": "5350000",
"경남밀양시": "5360000",
"경남거제시": "5370000",
"경남양산시": "5380000",
"경남의령군": "5390000",
"경남함안군": "5400000",
"경남창녕군": "5410000",
"경남고성군": "5420000",
"경남남해군": "5430000",
"경남하동군": "5440000",
"경남산청군": "5450000",
"경남함양군": "5460000",
"경남거창군": "5470000",
"경남합천군": "5480000",
"제주특별자치도 본청": "6500000",
"제주제주시": "6510000",
"제주서귀포시": "6520000"
}"""LOCALDATA (지방행정 인허가) business operating-status lookup (unauthenticated).
행정안전부 지방행정 인허가데이터를 file.localdata.go.kr 지역별 CSV로 직접 받아
동네 사업장(식당·카페·숙박·약국 등 인허가 업종 208종)의 영업/휴업/폐업 상태를
조회한다. 인증키가 필요 없는 공개 파일 서버이므로 프록시를 거치지 않는다.
The data does NOT contain business registration numbers, so this is a trade-name
(사업장명) string match only — it cannot assert identity against a given number.
전국 통파일이 업종당 수백 MB라 시군구 단위 파일을 받으려면 --region 이 필요하다.
"""
from __future__ import annotations
import argparse
import csv
import datetime as dt
import io
import json
import pathlib
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
BASE = "https://file.localdata.go.kr"
LANDING = f"{BASE}/file/general_restaurants/info"
SOURCE = ("지방행정 인허가데이터(LOCALDATA) 업종별 영업상태 — 행정안전부 "
"(file.localdata.go.kr 지역별 CSV, 매일 갱신·2일 전 기준 현행화)")
USER_AGENT = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0 Safari/537.36")
KST = dt.timezone(dt.timedelta(hours=9))
_DATA_DIR = pathlib.Path(__file__).resolve().parent.parent / "data"
INDUSTRIES: dict = json.loads((_DATA_DIR / "localdata_industries.json").read_text(encoding="utf-8"))
DEFAULT_INDUSTRIES = ("general_restaurants", "rest_cafes", "lodgings")
RESULT_COLUMNS = ("사업장명", "영업상태명", "상세영업상태명", "인허가일자", "폐업일자",
"업태구분명", "도로명주소", "지번주소", "데이터갱신시점")
CACHE_DIR = pathlib.Path.home() / ".cache" / "k-skill" / "localdata-business-status"
CACHE_TTL_SECONDS = 24 * 3600 # 원천이 일 단위 갱신이므로 1일 캐시
IDENTITY_NOTE = ("인허가 자료에는 사업자등록번호가 수록되지 않아 입력 사업자번호와의 "
"동일성은 확인할 수 없다 — 상호(사업장명) 문자열 일치 후보의 사실만 "
"나열하며, 동명 상호 가능성은 사용자가 판단한다. 자료는 매일 갱신되며 "
"2일 전 기준으로 현행화된다.")
def _now_iso() -> str:
return dt.datetime.now(KST).isoformat(timespec="seconds")
def _envelope(status: str, *, result: dict | None = None, note: str | None = None) -> dict:
return {
"source": SOURCE,
"looked_up_at": _now_iso(),
"status": status,
"result": result,
"origin": "unauthenticated-public",
"note": note,
}
def org_codes() -> dict:
return json.loads((_DATA_DIR / "localdata_orgcodes.json").read_text(encoding="utf-8"))
def resolve_industry(token: str) -> tuple[str | None, list[str]]:
"""업종 지정 해석 — slug 정확 일치 또는 한글명 일치. (slug, 후보들)."""
token = token.strip()
if token in INDUSTRIES:
return token, [INDUSTRIES[token]]
squeezed = token.replace(" ", "")
exact = [(slug, nm) for slug, nm in INDUSTRIES.items()
if nm.replace(" ", "") == squeezed
or nm.split("_", 1)[-1].replace(" ", "") == squeezed]
if len(exact) == 1:
return exact[0][0], [exact[0][1]]
hits = exact or [(slug, nm) for slug, nm in INDUSTRIES.items()
if squeezed in nm.replace(" ", "")]
if len(hits) == 1:
return hits[0][0], [hits[0][1]]
return None, [nm for _, nm in hits]
def _resolve_region(region: str) -> tuple[str | None, list[str]]:
table = org_codes()
region = region.strip()
if region in table:
return table[region], [region]
squeezed = region.replace(" ", "")
hits = [nm for nm in table if squeezed in nm.replace(" ", "")]
if len(hits) == 1:
return table[hits[0]], hits
return None, hits
def _fetch_csv(slug: str, org_code: str, *, opener: Any = None) -> str:
CACHE_DIR.mkdir(parents=True, exist_ok=True)
cache = CACHE_DIR / f"{slug}_{org_code}.csv"
if cache.exists() and time.time() - cache.stat().st_mtime < CACHE_TTL_SECONDS:
return cache.read_text(encoding="utf-8")
params = urllib.parse.urlencode({"orgCode": org_code})
request = urllib.request.Request(
f"{BASE}/file/download/{slug}/info?{params}",
headers={"User-Agent": USER_AGENT, "Referer": LANDING},
method="GET",
)
open_fn = opener or urllib.request.urlopen
with open_fn(request, timeout=120) as response:
status = getattr(response, "status", 200)
content_type = response.headers.get("Content-Type", "") if hasattr(response, "headers") else ""
if status != 200 or "csv" not in (content_type or ""):
raise RuntimeError(f"HTTP {status} ({content_type or '?'})")
text = response.read().decode("cp949", errors="replace")
cache.write_text(text, encoding="utf-8")
return text
def _search_rows(csv_text: str, name: str) -> list[dict]:
needle = name.replace(" ", "")
out = []
for row in csv.DictReader(io.StringIO(csv_text)):
biz_name = (row.get("사업장명") or "").strip()
if needle and needle in biz_name.replace(" ", ""):
out.append({col: (row.get(col) or "").strip() for col in RESULT_COLUMNS})
return out
def lookup(name: str, region: str, industries: list[str] | None = None, *, opener: Any = None) -> dict:
"""인허가 영업상태 조회 — 상호+지역 필수 (자료에 사업자번호 없음)."""
if not (name or "").strip():
return _envelope("unavailable",
note="인허가 자료에 사업자등록번호가 수록되지 않아 상호 없이 검색할 수 "
"없습니다. --name 으로 상호를 지정하세요.")
if not (region or "").strip():
return _envelope("unavailable",
note="전국 통파일이 업종당 수백 MB라 시군구 지역 지정이 필요합니다. "
"--region 으로 지정하세요 (예: 제주제주시, 서울종로구, 경기수원시).")
name = name.strip()
code, hits = _resolve_region(region)
if code is None:
return _envelope("unavailable",
note=(f"지역 '{region}' 특정 실패 — "
+ (f"후보 {len(hits)}곳: {', '.join(hits[:8])}. 하나로 지정하세요."
if hits else "등록 지자체명과 일치하지 않습니다 (예: 서울종로구).")))
selected, bad = [], []
for token in (industries or DEFAULT_INDUSTRIES):
slug, cand = resolve_industry(token)
if slug:
selected.append(slug)
else:
bad.append(f"'{token}'" + (f" (후보 {len(cand)}종: {', '.join(cand[:6])})" if cand
else " (일치 업종 없음)"))
if bad:
return _envelope("unavailable",
note=(f"업종 특정 실패: {'; '.join(bad)}. slug 또는 한글명(예: 약국, "
"일반음식점, 숙박업)으로 하나씩 지정하세요. 총 208종 지원."))
searched, failures = {}, []
try:
for slug in selected:
try:
rows = _search_rows(_fetch_csv(slug, code, opener=opener), name)
searched[slug] = {"industry": INDUSTRIES[slug], "match_count": len(rows), "matches": rows}
except (urllib.error.URLError, RuntimeError) as err:
failures.append(f"{INDUSTRIES[slug]}({type(err).__name__})")
except Exception as err: # 경계 계약: 어떤 오류든 강등
return _envelope("unavailable", note=f"예상 외 오류({type(err).__name__}).")
if not searched:
return _envelope("unavailable",
note=f"전 업종 다운로드 실패: {', '.join(failures)}. "
f"수동 확인: https://www.localdata.go.kr")
result = {
"query": {"name": name, "region": hits[0], "org_code": code},
"industries_searched": searched,
"total_match_count": sum(v["match_count"] for v in searched.values()),
"identity_note": IDENTITY_NOTE,
}
note = (f"일부 업종 다운로드 실패: {', '.join(failures)}" if failures else None)
return _envelope("ok", result=result, note=note)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="지방행정 인허가 영업상태 조회 (무인증)")
parser.add_argument("--name", required=True, help="상호(사업장명) — 필수")
parser.add_argument("--region", required=True, help="시군구 (예: 제주제주시, 서울종로구)")
parser.add_argument("--industry", action="append", dest="industries",
help="업종 slug 또는 한글명(예: 약국, 숙박업). 여러 번 지정 가능. 생략 시 음식점·카페·숙박")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
print(json.dumps(lookup(args.name, args.region, args.industries), ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
Related skills
AI & Agent Buildingagents