Add exclusions feature: map popup Exclude button, exclusions page, server API

This commit is contained in:
zyphlar
2026-04-24 01:02:39 -07:00
parent 67f038e7f0
commit 8150e2ca1f
6 changed files with 507 additions and 1 deletions
+77 -1
View File
@@ -426,7 +426,7 @@ def serve_data(filename):
# ── Config file download / upload ──────────────────────────────────────────────
_ALLOWED_CONFIGS = {'counties.yml', 'exceptions.yml'}
_ALLOWED_CONFIGS = {'counties.yml', 'exceptions.yml', 'exclusions.yml'}
@app.route('/api/config/<filename>', methods=['GET'])
@require_auth
@@ -450,6 +450,8 @@ def upload_config(filename):
return jsonify({'error': 'Invalid counties.yml: missing counties list'}), 400
if filename == 'exceptions.yml' and not isinstance((data or {}).get('corrections'), list):
return jsonify({'error': 'Invalid exceptions.yml: missing corrections list'}), 400
if filename == 'exclusions.yml' and not isinstance((data or {}).get('exclusions'), list):
return jsonify({'error': 'Invalid exclusions.yml: missing exclusions list'}), 400
except Exception as e:
return jsonify({'error': f'Invalid YAML: {e}'}), 400
dest = f'/data/{filename}'
@@ -459,6 +461,80 @@ def upload_config(filename):
os.replace(tmp, dest)
return jsonify({'success': True})
# ── Exclusions config ─────────────────────────────────────────────────────────
EXCLUSIONS_FILE = '/data/exclusions.yml'
def _ensure_exclusions_file():
if not os.path.exists(EXCLUSIONS_FILE):
os.makedirs(os.path.dirname(EXCLUSIONS_FILE), exist_ok=True)
with open(EXCLUSIONS_FILE, 'w') as f:
yaml.dump({'exclusions': []}, f, default_flow_style=False, allow_unicode=True)
_ensure_exclusions_file()
def get_exclusions():
try:
with open(EXCLUSIONS_FILE) as f:
data = yaml.safe_load(f) or {}
return data.get('exclusions', [])
except Exception:
return []
@app.route('/exclusions')
def exclusions_page():
return render_template('exclusions.html', current_user=session.get('username'))
@app.route('/api/exclusions', methods=['GET'])
def get_exclusions_api():
return jsonify({'exclusions': get_exclusions()})
@app.route('/api/exclusions', methods=['POST'])
@require_auth
def save_exclusions():
data = request.json
items = data.get('exclusions', [])
for item in items:
if not isinstance(item.get('field'), str) or not item['field'].strip():
return jsonify({'error': 'Each exclusion must have a non-empty "field"'}), 400
if not isinstance(item.get('value'), str) or not item['value'].strip():
return jsonify({'error': 'Each exclusion must have a non-empty "value"'}), 400
tmp = EXCLUSIONS_FILE + '.tmp'
with open(tmp, 'w') as f:
yaml.dump({'exclusions': items}, f, default_flow_style=False, allow_unicode=True)
os.replace(tmp, EXCLUSIONS_FILE)
return jsonify({'success': True})
@app.route('/api/exclusions/add', methods=['POST'])
@require_auth
def add_exclusions():
"""Append new exclusion entries, skipping duplicates."""
data = request.json
new_items = data.get('exclusions', [])
existing = get_exclusions()
existing_keys = {(e['field'], e['value']) for e in existing}
added = 0
for item in new_items:
field = (item.get('field') or '').strip()
value = (item.get('value') or '').strip()
if not field or not value:
continue
if (field, value) not in existing_keys:
existing.append({'field': field, 'value': value})
existing_keys.add((field, value))
added += 1
tmp = EXCLUSIONS_FILE + '.tmp'
with open(tmp, 'w') as f:
yaml.dump({'exclusions': existing}, f, default_flow_style=False, allow_unicode=True)
os.replace(tmp, EXCLUSIONS_FILE)
return jsonify({'success': True, 'added': added})
# ── Counties config ────────────────────────────────────────────────────────────
COUNTIES_FILE = '/data/counties.yml'