diff --git a/app.py b/app.py index fd8b799..0374782 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,4 @@ -import os, io, random, requests, openpyxl +import os, io, random, requests, openpyxl, concurrent.futures from flask import Flask, render_template, request, jsonify, send_file app = Flask(__name__) @@ -23,32 +23,28 @@ def tab_data(tab_id): return t, None -def geocode(address): - try: - url = 'https://nominatim.openstreetmap.org/search' - params = {'q': address.strip(), 'format': 'json', 'limit': 1} - headers = {'User-Agent': 'MapinDrag/1.0'} - resp = requests.get(url, params=params, headers=headers, timeout=10) - if resp.ok and resp.json(): - r = resp.json()[0] - return float(r['lat']), float(r['lon']) - except Exception: - pass - return None, None - +GEO_CACHE = {} def geocode_with_fallback(address): - lat, lng = geocode(f'{address}, 대한민국') - if lat is not None: - return lat, lng - lat, lng = geocode(f'서울특별시 용산구 {address}') - if lat is not None: - return lat, lng - lat, lng = geocode(f'용산구 {address}') - if lat is not None: - return lat, lng + if address in GEO_CACHE: + return GEO_CACHE[address] + try: + url = 'https://nominatim.openstreetmap.org/search' + params = {'q': address.strip(), 'format': 'json', 'limit': 1, 'countrycodes': 'kr'} + headers = {'User-Agent': 'MapinDrag/1.0'} + resp = requests.get(url, params=params, headers=headers, timeout=5) + if resp.ok: + data = resp.json() + if data: + lat, lng = float(data[0]['lat']), float(data[0]['lon']) + GEO_CACHE[address] = (lat, lng) + return lat, lng + except Exception: + pass jitter = random.uniform(-0.005, 0.005) - return YONGSAN_CENTER[0] + jitter, YONGSAN_CENTER[1] + jitter + result = (YONGSAN_CENTER[0] + jitter, YONGSAN_CENTER[1] + jitter) + GEO_CACHE[address] = result + return result def empty_tab(name): @@ -73,9 +69,8 @@ def add_tab(): tid = f'tab_{store["next_id"]}' store['next_id'] += 1 store['tabs'][tid] = empty_tab(name) - if store['active_tab'] is None: - store['active_tab'] = tid - return jsonify({'id': tid, 'name': name}) + store['active_tab'] = tid + return jsonify({'id': tid, 'name': name, 'activeTab': tid}) @app.route('/api/tabs/rename', methods=['POST']) @@ -149,7 +144,27 @@ def upload(): file.save(path) wb = openpyxl.load_workbook(path, data_only=True) - workers, workplaces = [], [] + workers, workplaces, all_addrs = [], [], set() + for sheet_name in wb.sheetnames: + ws = wb[sheet_name] + headers = [str(c.value or '').strip() for c in ws[1]] + if '희망사항' in headers: + idx = {h: i for i, h in enumerate(headers)} + for row in ws.iter_rows(min_row=2, values_only=True): + if not any(row): continue + name = str(row[idx.get('이름', 0)] or '').strip() + if name: all_addrs.add(str(row[idx.get('주소', 2)] or '').strip()) + if '희망사항' not in headers: + idx = {h: i for i, h in enumerate(headers)} + for row in ws.iter_rows(min_row=2, values_only=True): + if not any(row): continue + name = str(row[idx.get('이름', 0)] or '').strip() + if name: all_addrs.add(str(row[idx.get('주소', 1)] or '').strip()) + + with concurrent.futures.ThreadPoolExecutor(max_workers=5) as exc: + geo_results = {a: exc.submit(geocode_with_fallback, a) for a in all_addrs} + geo_results = {a: f.result() for a, f in geo_results.items()} + for sheet_name in wb.sheetnames: ws = wb[sheet_name] headers = [str(c.value or '').strip() for c in ws[1]] @@ -157,17 +172,15 @@ def upload(): if '희망사항' in headers: idx = {h: i for i, h in enumerate(headers)} for row in ws.iter_rows(min_row=2, values_only=True): - if not any(row): - continue + if not any(row): continue name = str(row[idx.get('이름', 0)] or '').strip() - if not name: - continue - lat, lng = geocode_with_fallback(str(row[idx.get('주소', 2)] or '').strip()) + if not name: continue + addr = str(row[idx.get('주소', 2)] or '').strip() + lat, lng = geo_results[addr] workers.append({ - 'id': f'w{len(workers)}', - 'name': name, + 'id': f'w{len(workers)}', 'name': name, 'hope': str(row[idx.get('희망사항', 1)] or '').strip(), - 'address': str(row[idx.get('주소', 2)] or '').strip(), + 'address': addr, 'dob': str(row[idx.get('생년월일', 3)] or '').strip(), 'phone': str(row[idx.get('연락처', 4)] or '').strip(), 'lat': lat, 'lng': lng, @@ -176,17 +189,14 @@ def upload(): if '희망사항' not in headers: idx = {h: i for i, h in enumerate(headers)} for row in ws.iter_rows(min_row=2, values_only=True): - if not any(row): - continue + if not any(row): continue name = str(row[idx.get('이름', 0)] or '').strip() - if not name: - continue - lat, lng = geocode_with_fallback(str(row[idx.get('주소', 1)] or '').strip()) + if not name: continue + addr = str(row[idx.get('주소', 1)] or '').strip() + lat, lng = geo_results[addr] workplaces.append({ - 'id': f'p{len(workplaces)}', - 'name': name, - 'address': str(row[idx.get('주소', 1)] or '').strip(), - 'lat': lat, 'lng': lng, + 'id': f'p{len(workplaces)}', 'name': name, + 'address': addr, 'lat': lat, 'lng': lng, }) t['workers'] = workers diff --git a/templates/index.html b/templates/index.html index b1bc14b..a261d61 100644 --- a/templates/index.html +++ b/templates/index.html @@ -115,8 +115,9 @@ async function addTab() { body: JSON.stringify({ name: name.trim() || undefined }), }); tabDataCache[r.id] = { workers: [], workplaces: [], assignments: {} }; - activeTabId = r.id; - await loadTabs(); + activeTabId = r.activeTab; + render(); + renderTabBar(); } async function renameTab(e, tid) {