File size: 9,982 Bytes
6b4bfdb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import io, re, time, requests, gc
import pandas as pd
import streamlit as st
from concurrent.futures import ThreadPoolExecutor, as_completed
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter

st.set_page_config(page_title="์‚ฌ์—…์žฅ ์ฃผ์†Œยท์ „ํ™”๋ฒˆํ˜ธ ๋ณด์™„", page_icon="๐Ÿ“‹", layout="centered")
st.markdown("""<style>
#MainMenu,footer,header{visibility:hidden}
.block-container{max-width:660px!important;padding:2rem 1rem!important}
div.stDownloadButton>button{background:#10b981!important;color:#fff!important;border:none!important;border-radius:8px!important;font-weight:600!important;width:100%!important}
</style>""", unsafe_allow_html=True)

FSC_KEY   = "2446da601452d231f1ff77a4c5ac9598896028fb96e8be586bf7f4be58b6231c"
KAKAO_KEY = "e8eef7246a2cd0f3d2d55af8a806e3ef"
NAV_HDR   = {"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15","Referer":"https://map.naver.com/","Accept-Language":"ko-KR,ko;q=0.9"}
WORKERS   = 20

def kakao_keyword(name, addr):
    try:
        r = requests.get("https://dapi.kakao.com/v2/local/search/keyword.json",
            headers={"Authorization":f"KakaoAK {KAKAO_KEY}"},
            params={"query":f"{name} {addr[:20]}","size":5}, timeout=5)
        docs = r.json().get("documents",[])
        gu = re.search(r'[\w]+๊ตฌ|[\w]+๊ตฐ', addr)
        gu = gu.group() if gu else ""
        if gu:
            local = [d for d in docs if gu in d.get("address_name","") or gu in d.get("road_address_name","")]
            if local:
                return next((d for d in local if name[:4] in d.get("place_name","")), local[0])
        return next((d for d in docs if name[:4] in d.get("place_name","")), docs[0] if docs else None)
    except: return None

def fsc_info(name, biz_no):
    try:
        r = requests.get(
            "https://apis.data.go.kr/1160100/service/GetCorpBasicInfoService_V2/getCorpOutline_V2",
            params={"serviceKey":FSC_KEY,"pageNo":1,"numOfRows":5,"resultType":"json","corpNm":name}, timeout=5)
        items = r.json().get("response",{}).get("body",{}).get("items",{}).get("item",[])
        if not items: return None
        if isinstance(items, dict): items = [items]
        bc = re.sub(r'\D','',str(biz_no))
        m = next((i for i in items if re.sub(r'\D','',str(i.get("bzno",""))) == bc), items[0])
        return {"phone":m.get("enpTlno",""), "addr":m.get("enpBsadr","") or m.get("enpDtadr","")}
    except: return None

def naver_phone(name, addr):
    clean = re.sub(r'์ฃผ์‹ํšŒ์‚ฌ|์œ ํ•œํšŒ์‚ฌ|\(์ฃผ\)|\(์œ \)|[(){}\[\]]','',name).strip()
    gu = re.search(r'[\w]+๊ตฌ', addr); gu = gu.group() if gu else ""
    try:
        r = requests.get("https://m.map.naver.com/search2/search.nhn",
            headers=NAV_HDR, params={"query":f"{clean} {gu}","type":"all"}, timeout=8)
        if r.status_code == 429: time.sleep(30); return naver_phone(name, addr)
        for pat in [r'tel:(0\d{1,2}-?\d{3,4}-?\d{4})',r'(?<!\d)(0\d{1,2}-\d{3,4}-\d{4})(?!\d)']:
            f = re.findall(pat, r.text)
            if f: return f[0]
    except: pass
    return ""

def process_one(row_data):
    idx, name, addr, biz_no, is_corp, gu = row_data
    phone=""; full_addr=addr; lat=None; lon=None

    if is_corp:
        info = fsc_info(name, biz_no)
        if info:
            phone = info.get("phone","")
            # FSC ์ฃผ์†Œ๋Š” ๋ณธ์‚ฌ ์ฃผ์†Œ๋ผ ๋ฎ์–ด์“ฐ์ง€ ์•Š์Œ
        time.sleep(0.1)

    kk = kakao_keyword(name, addr)
    if kk:
        if not phone: phone = kk.get("phone","")
        road = kk.get("road_address_name","") or kk.get("address_name","")
        # ์นด์นด์˜ค ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์€ ๊ตฌ/๊ตฐ์ผ ๋•Œ๋งŒ ์ฃผ์†Œ ์—…๋ฐ์ดํŠธ
        if road and gu and gu in road:
            full_addr = road
            lat = float(kk.get("y") or 0) or None
            lon = float(kk.get("x") or 0) or None
        elif not road or (gu and gu not in road):
            # ์—‰๋šฑํ•œ ์ง€์—ญ ๊ฒฐ๊ณผ โ†’ ์›๋ณธ ์ฃผ์†Œ ์œ ์ง€, ์ขŒํ‘œ๋งŒ ์ทจ๋“
            lat = float(kk.get("y") or 0) or None
            lon = float(kk.get("x") or 0) or None

    if not phone: phone = naver_phone(name, full_addr)
    return idx, {"phone":phone, "addr":full_addr, "lat":lat, "lon":lon}

def make_excel(df):
    wb = Workbook(); ws = wb.active; ws.title = "๋ณด์™„์™„๋ฃŒ"
    def thin():
        s = Side(style="thin",color="D0D0D0")
        return Border(left=s,right=s,top=s,bottom=s)
    hdrs = list(df.columns)
    widths = {"์‚ฌ์—…์žฅ๋ช…":26,"์—…์ข…":18,"๋ฒ•์ธ์—ฌ๋ถ€":8,"์ฃผ์†Œ":42,"์ „ํ™”๋ฒˆํ˜ธ":14,"๊ฐ€์ž…์ž์ˆ˜":8,"๋‹น์›”๊ณ ์ง€๊ธˆ์•ก":14,"์œ„๋„":12,"๊ฒฝ๋„":12}
    CENTER = {"๋ฒ•์ธ์—ฌ๋ถ€","๊ฐ€์ž…์ž์ˆ˜","๋‹น์›”๊ณ ์ง€๊ธˆ์•ก","์œ„๋„","๊ฒฝ๋„","์ „ํ™”๋ฒˆํ˜ธ"}
    for c,h in enumerate(hdrs,1):
        cell = ws.cell(1,c,h)
        cell.fill = PatternFill("solid",start_color="1F3864")
        cell.font = Font(name="Arial",size=10,bold=True,color="FFFFFF")
        cell.alignment = Alignment(horizontal="center",vertical="center")
        cell.border = thin()
        ws.column_dimensions[get_column_letter(c)].width = widths.get(h,12)
    ws.row_dimensions[1].height = 18
    ws.freeze_panes = "A2"
    ws.auto_filter.ref = f"A1:{get_column_letter(len(hdrs))}1"
    for ri,row in enumerate(df.itertuples(index=False),2):
        for ci,(h,v) in enumerate(zip(hdrs,row),1):
            if pd.isna(v) or str(v) in ("nan","None"): v=""
            cell = ws.cell(ri,ci,v)
            cell.font = Font(name="Arial",size=10)
            cell.border = thin()
            cell.fill = PatternFill("solid",start_color="FFFFFF" if ri%2==0 else "F8FAFC")
            cell.alignment = Alignment(horizontal="center" if h in CENTER else "left",vertical="center")
    buf = io.BytesIO(); wb.save(buf); return buf.getvalue()

# โ”€โ”€ UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
st.title("๐Ÿ“‹ ์‚ฌ์—…์žฅ ์ฃผ์†Œยท์ „ํ™”๋ฒˆํ˜ธ ๋ณด์™„")
st.caption("์ง€์—ญ ํŒŒ์ผ ์—…๋กœ๋“œ โ†’ ์ž๋™ ์ฒ˜๋ฆฌ โ†’ ๋‹ค์šด๋กœ๋“œ")
st.divider()

uploaded = st.file_uploader("ํŒŒ์ผ ์„ ํƒ (๊ตญ๋ฏผ์—ฐ๊ธˆ_XX์‹œ_XX๊ตฌ_์‚ฌ์—…์žฅ.xlsx)", type=["xlsx"])

if uploaded:
    df = pd.read_excel(uploaded)
    df.columns = [c.strip() for c in df.columns]
    gc.collect()

    name_col = next((c for c in df.columns if "์‚ฌ์—…์žฅ๋ช…" in c or "์ƒํ˜ธ" in c), None)
    addr_col = next((c for c in df.columns if "์ฃผ์†Œ" in c or "๋„๋กœ๋ช…" in c), None)
    biz_col  = next((c for c in df.columns if "์‚ฌ์—…์ž" in c and "๋ฒˆํ˜ธ" in c), None)
    type_col = next((c for c in df.columns if "ํ˜•ํƒœ" in c or "๋ฒ•์ธ" in c), None)
    emp_col  = next((c for c in df.columns if "๊ฐ€์ž…์ž" in c), None)
    pay_col  = next((c for c in df.columns if "๊ณ ์ง€" in c), None)
    ind_col  = next((c for c in df.columns if "์—…์ข…" in c), None)

    for col in ["์ „ํ™”๋ฒˆํ˜ธ"]:
        if col not in df.columns: df[col] = ""
        else: df[col] = df[col].fillna("").astype(str).replace("nan","")
    for col in ["์œ„๋„","๊ฒฝ๋„"]:
        if col not in df.columns: df[col] = None
        else: df[col] = pd.to_numeric(df[col], errors="coerce")

    total = len(df)
    fname = uploaded.name.replace(".xlsx","")
    st.success(f"โœ… {fname} โ€” {total:,}๊ฐœ ๋กœ๋“œ ์™„๋ฃŒ")

    prog   = st.progress(0, text="์ฒ˜๋ฆฌ ์ค‘...")
    phone_ok = coord_ok = completed = 0

    # ๊ตฌ/๊ตฐ ์ถ”์ถœ (์—‰๋šฑํ•œ ์ง€์—ญ ํ•„ํ„ฐ๋ง์šฉ)
    gu = ""
    if addr_col:
        sample = df[addr_col].dropna().iloc[0] if len(df) > 0 else ""
        m = re.search(r'[\w]+๊ตฌ|[\w]+๊ตฐ', str(sample))
        gu = m.group() if m else ""

    tasks = []
    for idx in df.index:
        row = df.loc[idx]
        tasks.append((idx,
            str(row.get(name_col,"")) if name_col else "",
            str(row.get(addr_col,"")) if addr_col else "",
            str(row.get(biz_col,""))  if biz_col  else "",
            str(row.get(type_col,"")) in ["๋ฒ•์ธ","1"] if type_col else False,
            gu))

    with ThreadPoolExecutor(max_workers=WORKERS) as executor:
        futures = {executor.submit(process_one, t): t[0] for t in tasks}
        for future in as_completed(futures):
            try:
                idx, res = future.result()
                df.loc[idx,"์ „ํ™”๋ฒˆํ˜ธ"] = str(res["phone"])
                df.loc[idx,"์œ„๋„"]    = res["lat"]
                df.loc[idx,"๊ฒฝ๋„"]    = res["lon"]
                if addr_col and res["addr"]: df.loc[idx,addr_col] = res["addr"]
                if res["phone"]: phone_ok += 1
                if res["lat"]:   coord_ok += 1
            except: pass
            completed += 1
            prog.progress(completed/total, text=f"๐Ÿ“ž{phone_ok} ๐Ÿ“{coord_ok} | {completed}/{total}")

    prog.empty()

    col_map = {}
    if name_col: col_map[name_col]="์‚ฌ์—…์žฅ๋ช…"
    if type_col: col_map[type_col]="๋ฒ•์ธ์—ฌ๋ถ€"
    if addr_col: col_map[addr_col]="์ฃผ์†Œ"
    col_map["์ „ํ™”๋ฒˆํ˜ธ"]="์ „ํ™”๋ฒˆํ˜ธ"
    if emp_col:  col_map[emp_col]="๊ฐ€์ž…์ž์ˆ˜"
    if pay_col:  col_map[pay_col]="๋‹น์›”๊ณ ์ง€๊ธˆ์•ก"
    if ind_col:  col_map[ind_col]="์—…์ข…"
    col_map["์œ„๋„"]="์œ„๋„"; col_map["๊ฒฝ๋„"]="๊ฒฝ๋„"

    out = df[[c for c in col_map if c in df.columns]].rename(columns=col_map)
    # ์ „ํ™”๋ฒˆํ˜ธ/์ฃผ์†Œ ์—†์œผ๋ฉด ์‚ญ์ œ + ํ•ด๋‹น ๊ตฌ/๊ตฐ ์•„๋‹Œ ์ฃผ์†Œ๋„ ์‚ญ์ œ
    mask = (out["์ „ํ™”๋ฒˆํ˜ธ"].notna() & (out["์ „ํ™”๋ฒˆํ˜ธ"]!="") &
            out["์ฃผ์†Œ"].notna()     & (out["์ฃผ์†Œ"]!=""))
    if gu:
        mask = mask & out["์ฃผ์†Œ"].str.contains(gu, na=False)
    out = out[mask].reset_index(drop=True)
    del df; gc.collect()

    st.success(f"โœ… ์™„๋ฃŒ! ๐Ÿ“ž{phone_ok:,}๊ฐœ | ๐Ÿ“{coord_ok:,}๊ฐœ | ์ตœ์ข… {len(out):,}๊ฐœ")
    st.balloons()
    st.download_button("โฌ‡๏ธ ๋ณด์™„ ์™„๋ฃŒ ์—‘์…€ ๋‹ค์šด๋กœ๋“œ",
        make_excel(out), fname+"_๋ณด์™„์™„๋ฃŒ.xlsx",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        use_container_width=True)