import os import io import random import requests import openpyxl from flask import Flask, render_template, request, jsonify, send_file app = Flask(__name__) app.config['UPLOAD_FOLDER'] = 'uploads' app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) YONGSAN_CENTER = (37.5326, 126.9906) store = { 'workers': [], 'workplaces': [], 'assignments': {}, 'filename': 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 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 jitter = random.uniform(-0.005, 0.005) return YONGSAN_CENTER[0] + jitter, YONGSAN_CENTER[1] + jitter @app.route('/') def index(): return render_template('index.html') @app.route('/upload', methods=['POST']) def upload(): file = request.files.get('file') if not file: return jsonify({'error': '파일이 없습니다.'}), 400 path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) file.save(path) wb = openpyxl.load_workbook(path, data_only=True) workers = [] workplaces = [] 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() hope = str(row[idx.get('희망사항', 1)] or '').strip() addr = str(row[idx.get('주소', 2)] or '').strip() dob = str(row[idx.get('생년월일', 3)] or '').strip() phone = str(row[idx.get('연락처', 4)] or '').strip() if not name: continue lat, lng = geocode_with_fallback(addr) workers.append({ 'id': f'w{len(workers)}', 'name': name, 'hope': hope, 'address': addr, 'dob': dob, 'phone': phone, 'lat': lat, 'lng': lng, }) 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() addr = str(row[idx.get('주소', 1)] or '').strip() if not name: continue lat, lng = geocode_with_fallback(addr) workplaces.append({ 'id': f'p{len(workplaces)}', 'name': name, 'address': addr, 'lat': lat, 'lng': lng, }) store['workers'] = workers store['workplaces'] = workplaces store['assignments'] = {} store['filename'] = file.filename return jsonify({ 'workers': workers, 'workplaces': workplaces, 'assignments': {}, }) @app.route('/data') def get_data(): return jsonify({ 'workers': store['workers'], 'workplaces': store['workplaces'], 'assignments': store['assignments'], }) @app.route('/assign', methods=['POST']) def assign(): body = request.get_json() worker_id = body.get('workerId') workplace_id = body.get('workplaceId') if not worker_id or not workplace_id: return jsonify({'error': '잘못된 요청입니다.'}), 400 store['assignments'][worker_id] = workplace_id return jsonify({'assignments': store['assignments']}) @app.route('/unassign', methods=['POST']) def unassign(): body = request.get_json() worker_id = body.get('workerId') store['assignments'].pop(worker_id, None) return jsonify({'assignments': store['assignments']}) @app.route('/reset', methods=['POST']) def reset(): store['assignments'] = {} return jsonify({'assignments': {}}) @app.route('/export') def export(): wb = openpyxl.Workbook() ws1 = wb.active ws1.title = '배치 현황' ws1.append(['이름', '연락처', '생년월일', '희망사항', '주소', '배치근무지', '근무지주소']) wp_lookup = {wp['id']: wp for wp in store['workplaces']} for w in store['workers']: assigned = store['assignments'].get(w['id']) wp_name = '' wp_addr = '' if assigned and assigned in wp_lookup: wp_name = wp_lookup[assigned]['name'] wp_addr = wp_lookup[assigned]['address'] ws1.append([w['name'], w['phone'], w['dob'], w['hope'], w['address'], wp_name, wp_addr]) ws2 = wb.create_sheet('근무지별 배치') ws2.append(['근무지명', '주소', '배치인원', '배치된근무자']) for wp in store['workplaces']: assigned_workers = [ w['name'] for w in store['workers'] if store['assignments'].get(w['id']) == wp['id'] ] count = len(assigned_workers) ws2.append([wp['name'], wp['address'], count, ', '.join(assigned_workers)]) buf = io.BytesIO() wb.save(buf) buf.seek(0) name = store.get('filename', 'export.xlsx') base, ext = os.path.splitext(name) download_name = f'{base}_배치결과{ext}' return send_file( buf, as_attachment=True, download_name=download_name, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ) if __name__ == '__main__': app.run(debug=True)