# ============================================================ # MODULE: Milieu & Omgeving Dienst Transformatie # HSEQ Intelligence Dashboard — FASE 4 # Emissie monitoring, afvalstroom, MER screening, vergunningen # ============================================================ import sqlite3 import json import os from datetime import datetime from flask import jsonify, request, render_template from search import get_db, DB_PATH # ─── Style constants ──────────────────────────────────────────────────────── DARK_BG = '#0A1628' NAVY = '#1B2A4A' ACCENT = '#3B82F6' CARD_BG = '#1B2A4A' TEXT_LIGHT = '#E2E8F0' BORDER_COLOR = '#334155' ENTERPRISE_CSS = f""" """ def _d(row): return dict(row) if row else None # ─── DB Init ──────────────────────────────────────────────────────────────── def init_milieu_db(): """Create FASE 4 tables with mil_ prefix.""" conn = sqlite3.connect(DB_PATH) c = conn.cursor() # 1. Milieueffectrapporten c.execute('''CREATE TABLE IF NOT EXISTS mil_reports ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, permit_type TEXT, activities TEXT, emission_sources TEXT, impact_assessment TEXT, measures TEXT, status TEXT DEFAULT 'draft', lifecycle_id INTEGER, created_by TEXT DEFAULT 'system', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (lifecycle_id) REFERENCES dlv_lifecycle(id) ON DELETE SET NULL )''') # 2. Emissie monitoring (extends environmental_metrics with aggregation) c.execute('''CREATE TABLE IF NOT EXISTS mil_emission_alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, metric_id INTEGER, alert_type TEXT DEFAULT 'threshold', severity TEXT DEFAULT 'warning', message TEXT, acknowledged INTEGER DEFAULT 0, acknowledged_by TEXT, acknowledged_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (metric_id) REFERENCES environmental_metrics(id) ON DELETE SET NULL )''') # 3. Afvalstroom detail (extends waste_streams with shipment tracking) c.execute('''CREATE TABLE IF NOT EXISTS mil_waste_shipments ( id INTEGER PRIMARY KEY AUTOINCREMENT, waste_stream_id INTEGER, shipment_date TEXT NOT NULL, quantity REAL NOT NULL, unit TEXT DEFAULT 'ton', contractor TEXT, permit_number TEXT, transport_doc TEXT, destination_facility TEXT, receiving_country TEXT DEFAULT 'NL', status TEXT DEFAULT 'planned', lifecycle_id INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (waste_stream_id) REFERENCES waste_streams(id) ON DELETE SET NULL, FOREIGN KEY (lifecycle_id) REFERENCES dlv_lifecycle(id) ON DELETE SET NULL )''') # 4. MER Screening c.execute('''CREATE TABLE IF NOT EXISTS mil_mer_screenings ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_name TEXT NOT NULL, project_description TEXT, activity_categories TEXT, location_type TEXT, surface_area REAL, threshold_exceeded INTEGER DEFAULT 0, mer_required TEXT, mer_type TEXT, reasoning TEXT, status TEXT DEFAULT 'screening', lifecycle_id INTEGER, created_by TEXT DEFAULT 'system', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (lifecycle_id) REFERENCES dlv_lifecycle(id) ON DELETE SET NULL )''') # 5. Vergunningsaanvragen c.execute('''CREATE TABLE IF NOT EXISTS mil_permits ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_name TEXT NOT NULL, permit_type TEXT NOT NULL, permit_status TEXT DEFAULT 'draft', checklist_data TEXT, document_requirements TEXT, submission_date TEXT, authority TEXT, reference_number TEXT, lifecycle_id INTEGER, created_by TEXT DEFAULT 'system', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (lifecycle_id) REFERENCES dlv_lifecycle(id) ON DELETE SET NULL )''') c.execute('CREATE INDEX IF NOT EXISTS idx_mil_reports_status ON mil_reports(status)') c.execute('CREATE INDEX IF NOT EXISTS idx_mil_alerts_ack ON mil_emission_alerts(acknowledged)') c.execute('CREATE INDEX IF NOT EXISTS idx_mil_waste_ship ON mil_waste_shipments(waste_stream_id)') c.execute('CREATE INDEX IF NOT EXISTS idx_mil_mer_status ON mil_mer_screenings(status)') c.execute('CREATE INDEX IF NOT EXISTS idx_mil_permits_status ON mil_permits(permit_status)') conn.commit() conn.close() # ─── 1. Milieueffectrapportage ───────────────────────────────────────────── def _milieu_reports_list(): db = get_db() try: rows = db.execute("SELECT * FROM mil_reports ORDER BY created_at DESC").fetchall() return jsonify([_d(r) for r in rows]) finally: db.close() def _milieu_reports_create(): data = request.get_json() if not data or not data.get('title'): return jsonify({'error': 'title required'}), 400 db = get_db() try: # Auto-create deliverable lifecycle lifecycle_id = None try: cur = db.execute( "INSERT INTO dlv_lifecycle (title, type, service, status, description) VALUES (?,?,?,?,?)", (data['title'], 'environmental_report', 'milieu', 'draft', data.get('activities',''))) lifecycle_id = cur.lastrowid except Exception: pass now = datetime.utcnow().isoformat() cur = db.execute( """INSERT INTO mil_reports (title, permit_type, activities, emission_sources, impact_assessment, measures, lifecycle_id, created_at) VALUES (?,?,?,?,?,?,?,?)""", (data['title'], data.get('permit_type'), json.dumps(data.get('activities', [])), json.dumps(data.get('emission_sources', [])), data.get('impact_assessment'), data.get('measures'), lifecycle_id, now)) db.commit() return jsonify({'id': cur.lastrowid, 'lifecycle_id': lifecycle_id}), 201 finally: db.close() def _milieu_reports_detail(rid): db = get_db() try: r = _d(db.execute("SELECT * FROM mil_reports WHERE id=?", (rid,)).fetchone()) if not r: return jsonify({'error': 'Not found'}), 404 if r.get('activities'): r['activities'] = json.loads(r['activities']) if r.get('emission_sources'): r['emission_sources'] = json.loads(r['emission_sources']) return jsonify(r) finally: db.close() # ─── 2. Emissie Monitoring Dashboard ─────────────────────────────────────── def _emission_dashboard(): db = get_db() try: # Aggregate from existing environmental_metrics total = db.execute('SELECT COUNT(*) as c FROM environmental_metrics').fetchone()['c'] compliant = db.execute("SELECT COUNT(*) as c FROM environmental_metrics WHERE compliance_status='compliant'").fetchone()['c'] warnings = db.execute("SELECT COUNT(*) as c FROM environmental_metrics WHERE compliance_status='warning'").fetchone()['c'] non_compliant = db.execute("SELECT COUNT(*) as c FROM environmental_metrics WHERE compliance_status IN ('non_compliant','exceedance')").fetchone()['c'] # By type by_type = {} for r in db.execute('SELECT metric_type, COUNT(*) as c, AVG(percentage_of_limit) as avg_pct FROM environmental_metrics GROUP BY metric_type'): by_type[r['metric_type']] = {'count': r['c'], 'avg_pct': round(r['avg_pct'] or 0, 1)} # Alerts unack_alerts = db.execute("SELECT COUNT(*) as c FROM mil_emission_alerts WHERE acknowledged=0").fetchone()['c'] top_violations = [_d(r) for r in db.execute( "SELECT * FROM environmental_metrics WHERE compliance_status IN ('warning','non_compliant','exceedance') ORDER BY percentage_of_limit DESC LIMIT 20").fetchall()] # Recent alerts alerts = [_d(r) for r in db.execute( "SELECT a.*, e.parameter_name, e.source_location FROM mil_emission_alerts a LEFT JOIN environmental_metrics e ON a.metric_id=e.id WHERE a.acknowledged=0 ORDER BY a.created_at DESC LIMIT 10").fetchall()] return jsonify({ 'total_metrics': total, 'compliant': compliant, 'warnings': warnings, 'non_compliant': non_compliant, 'by_type': by_type, 'unacknowledged_alerts': unack_alerts, 'top_violations': top_violations, 'recent_alerts': alerts }) finally: db.close() def _emission_alerts_list(): db = get_db() try: rows = db.execute("SELECT a.*, e.parameter_name, e.source_location, e.value, e.unit FROM mil_emission_alerts a LEFT JOIN environmental_metrics e ON a.metric_id=e.id ORDER BY a.created_at DESC").fetchall() return jsonify([_d(r) for r in rows]) finally: db.close() def _emission_alert_acknowledge(aid): db = get_db() try: db.execute("UPDATE mil_emission_alerts SET acknowledged=1, acknowledged_by=?, acknowledged_at=? WHERE id=?", (request.json.get('user','system'), datetime.utcnow().isoformat(), aid)) db.commit() return jsonify({'status': 'acknowledged'}) finally: db.close() def _emission_alerts_auto_generate(): """Auto-generate alerts for non-compliant metrics. Called on dashboard load.""" db = get_db() try: # Find metrics with exceedances that don't have alerts yet violations = db.execute(""" SELECT e.id, e.parameter_name, e.value, e.unit, e.percentage_of_limit, e.compliance_status, e.source_location, e.permit_limit FROM environmental_metrics e WHERE e.compliance_status IN ('non_compliant','exceedance') AND e.id NOT IN (SELECT metric_id FROM mil_emission_alerts WHERE metric_id IS NOT NULL) """).fetchall() count = 0 for v in violations: sev = 'critical' if v['compliance_status'] == 'exceedance' else 'warning' pct = v['percentage_of_limit'] or 0 msg = f"{v['parameter_name']} bij {v['source_location']}: {v['value']} {v['unit']} ({pct:.0f}% van limiet {v['permit_limit']})" db.execute("INSERT INTO mil_emission_alerts (metric_id, alert_type, severity, message) VALUES (?,?,?,?)", (v['id'], 'threshold', sev, msg)) count += 1 db.commit() return jsonify({'alerts_generated': count}) finally: db.close() # ─── 3. Afvalstroom Management ───────────────────────────────────────────── def _waste_dashboard_enhanced(): db = get_db() try: # Base stats from existing waste_streams total_streams = db.execute('SELECT COUNT(*) as c FROM waste_streams').fetchone()['c'] by_category = {} for r in db.execute('SELECT waste_category, COUNT(*) as c, SUM(quantity) as qty FROM waste_streams GROUP BY waste_category'): by_category[r['waste_category']] = {'count': r['c'], 'total_tons': round(r['qty'] or 0, 2)} # Shipment tracking total_shipments = db.execute('SELECT COUNT(*) as c FROM mil_waste_shipments').fetchone()['c'] pending_shipments = db.execute("SELECT COUNT(*) as c FROM mil_waste_shipments WHERE status='planned'").fetchone()['c'] completed_shipments = db.execute("SELECT COUNT(*) as c FROM mil_waste_shipments WHERE status='completed'").fetchone()['c'] # Recent shipments shipments = [_d(r) for r in db.execute( "SELECT s.*, w.waste_code, w.waste_name, w.waste_category FROM mil_waste_shipments s LEFT JOIN waste_streams w ON s.waste_stream_id=w.id ORDER BY s.shipment_date DESC LIMIT 20").fetchall()] # Waste balance: production vs disposal total_produced = db.execute('SELECT SUM(quantity) as q FROM waste_streams').fetchone()['q'] or 0 total_shipped = db.execute('SELECT SUM(quantity) as q FROM mil_waste_shipments WHERE status="completed"').fetchone()['q'] or 0 return jsonify({ 'total_streams': total_streams, 'by_category': by_category, 'total_shipments': total_shipments, 'pending_shipments': pending_shipments, 'completed_shipments': completed_shipments, 'recent_shipments': shipments, 'total_produced_tons': round(total_produced, 2), 'total_shipped_tons': round(total_shipped, 2), 'balance_pending_tons': round(total_produced - total_shipped, 2) }) finally: db.close() def _waste_shipments_list(): db = get_db() try: rows = db.execute( "SELECT s.*, w.waste_code, w.waste_name, w.waste_category FROM mil_waste_shipments s LEFT JOIN waste_streams w ON s.waste_stream_id=w.id ORDER BY s.shipment_date DESC").fetchall() return jsonify([_d(r) for r in rows]) finally: db.close() def _waste_shipments_create(): data = request.get_json() if not data or not data.get('waste_stream_id') or not data.get('shipment_date'): return jsonify({'error': 'waste_stream_id and shipment_date required'}), 400 db = get_db() try: cur = db.execute( """INSERT INTO mil_waste_shipments (waste_stream_id, shipment_date, quantity, unit, contractor, permit_number, transport_doc, destination_facility, receiving_country, status) VALUES (?,?,?,?,?,?,?,?,?,?)""", (data['waste_stream_id'], data['shipment_date'], data.get('quantity',0), data.get('unit','ton'), data.get('contractor'), data.get('permit_number'), data.get('transport_doc'), data.get('destination_facility'), data.get('receiving_country','NL'), data.get('status','planned'))) db.commit() return jsonify({'id': cur.lastrowid}), 201 finally: db.close() def _waste_shipments_update(sid): data = request.get_json() db = get_db() try: fields, vals = [], [] for k in ('quantity','unit','contractor','permit_number','transport_doc','destination_facility', 'receiving_country','status','shipment_date'): if k in data: fields.append(f"{k}=?"); vals.append(data[k]) if not fields: return jsonify({'error': 'No fields'}), 400 vals.append(sid) db.execute(f"UPDATE mil_waste_shipments SET {','.join(fields)} WHERE id=?", vals) db.commit() return jsonify({'status': 'updated'}) finally: db.close() # ─── 4. MER Screening ────────────────────────────────────────────────────── # MER decision matrix (simplified based on Wm art. 7.2 / Bijlage III) MER_CATEGORIES = { 'industriefabriek': {'threshold_area': 0.5, 'mer_type': 'volledige_mer', 'description': 'Industriefabrieken'}, 'chemische_productie': {'threshold_area': 0.5, 'mer_type': 'volledige_mer', 'description': 'Chemische productie'}, 'energiecentrale': {'threshold_area': 0.5, 'mer_type': 'volledige_mer', 'description': 'Energiecentrales'}, 'afvalverwerking': {'threshold_area': 0.5, 'mer_type': 'volledige_mer', 'description': 'Afvalverwerkingsinrichtingen'}, 'opslag_gevaarlijke_stoffen': {'threshold_area': 0.5, 'mer_type': 'volledige_mer', 'description': 'Opslag gevaarlijke stoffen'}, 'weg_aanleg': {'threshold_area': 10.0, 'mer_type': 'plan_mer', 'description': 'Wegaanleg/-uitbreiding'}, 'bouw_project': {'threshold_area': 5.0, 'mer_type': 'plan_mer', 'description': 'Bouwprojecten'}, 'grondverzet': {'threshold_area': 2.0, 'mer_type': 'plan_mer', 'description': 'Grootschalig grondverzet'}, 'waterzuivering': {'threshold_area': 1.0, 'mer_type': 'plan_mer', 'description': 'Waterzuiveringsinstallaties'}, 'windmolenpark': {'threshold_area': 2.0, 'mer_type': 'plan_mer', 'description': 'Windmolenparken'}, 'zonnepark': {'threshold_area': 5.0, 'mer_type': 'plan_mer', 'description': 'Zonneparken'}, 'logistiek_hub': {'threshold_area': 5.0, 'mer_type': 'plan_mer', 'description': 'Logistieke terminallen'}, 'tankstation': {'threshold_area': 0.3, 'mer_type': 'm.e.r.beoordeling', 'description': 'Tankstations'}, 'kantoorpand': {'threshold_area': 10.0, 'mer_type': 'geen_mer', 'description': 'Kantoorpanden'}, 'woningbouw': {'threshold_area': 10.0, 'mer_type': 'geen_mer', 'description': 'Woningbouw'}, 'agrarisch': {'threshold_area': 5.0, 'mer_type': 'm.e.r.beoordeling', 'description': 'Agrarische bedrijven'}, 'overig': {'threshold_area': 1.0, 'mer_type': 'm.e.r.beoordeling', 'description': 'Overige activiteiten'}, } def _mer_screen(): data = request.get_json() or {} db = get_db() try: project_name = data.get('project_name', 'Nieuw project') description = data.get('project_description', '') categories = data.get('activity_categories', []) location_type = data.get('location_type', 'binnenstedelijk') surface_area = float(data.get('surface_area', 0)) results = [] mer_required = False mer_type = None reasoning = [] for cat in categories: info = MER_CATEGORIES.get(cat, MER_CATEGORIES['overig']) threshold = info['threshold_area'] exceeds = surface_area >= threshold cat_result = { 'category': cat, 'description': info['description'], 'threshold_area': threshold, 'actual_area': surface_area, 'exceeds_threshold': exceeds, 'suggested_mer_type': info['mer_type'] } results.append(cat_result) if exceeds and info['mer_type'] in ('volledige_mer', 'plan_mer'): mer_required = True if info['mer_type'] == 'volledige_mer': mer_type = 'volledige_mer' elif mer_type != 'volledige_mer': mer_type = 'plan_mer' reasoning.append(f"✗ {info['description']}: {surface_area} ha ≥ {threshold} ha → {info['mer_type'].replace('_',' ').title()} verplicht") elif exceeds and info['mer_type'] == 'm.e.r.beoordeling': if not mer_required: mer_type = 'm.e.r.beoordeling' reasoning.append(f"⚠ {info['description']}: {surface_area} ha ≥ {threshold} ha → M.e.r.-beoordeling vereist") else: reasoning.append(f"✓ {info['description']}: {surface_area} ha < {threshold} ha → Geen MER") if not mer_type: mer_type = 'geen_mer' reasoning.append("Geen van de activiteiten overschrijdt de drempelwaarden.") # Location sensitivity bonus if location_type in ('natuurgebied', 'beschermde_stadsgezicht', 'waterwingebied'): if mer_type == 'geen_mer': mer_type = 'm.e.r.beoordeling' reasoning.append(f"⚠ Locatietype '{location_type}' verhoogt gevoeligheid → M.e.r.-beoordeling vereist despite drempel niet overschreden") elif mer_type == 'm.e.r.beoordeling': mer_type = 'plan_mer' reasoning.append(f"⚠ Locatietype '{location_type}' verhoogt gevoeligheid → Opwaardering naar Plan-MER") # Save screening now = datetime.utcnow().isoformat() cur = db.execute( """INSERT INTO mil_mer_screenings (project_name, project_description, activity_categories, location_type, surface_area, threshold_exceeded, mer_required, mer_type, reasoning, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)""", (project_name, description, json.dumps(categories), location_type, surface_area, 1 if mer_required else 0, 'ja' if mer_required else 'nee', mer_type, json.dumps(reasoning), now)) db.commit() return jsonify({ 'id': cur.lastrowid, 'project_name': project_name, 'categories_analysed': results, 'mer_required': mer_required, 'mer_type': mer_type, 'reasoning': reasoning }) finally: db.close() def _mer_screenings_list(): db = get_db() try: rows = db.execute("SELECT * FROM mil_mer_screenings ORDER BY created_at DESC").fetchall() results = [] for r in rows: d = _d(r) if d.get('activity_categories'): d['activity_categories'] = json.loads(d['activity_categories']) if d.get('reasoning'): d['reasoning'] = json.loads(d['reasoning']) results.append(d) return jsonify(results) finally: db.close() def _mer_screening_detail(sid): db = get_db() try: r = _d(db.execute("SELECT * FROM mil_mer_screenings WHERE id=?", (sid,)).fetchone()) if not r: return jsonify({'error': 'Not found'}), 404 if r.get('activity_categories'): r['activity_categories'] = json.loads(r['activity_categories']) if r.get('reasoning'): r['reasoning'] = json.loads(r['reasoning']) return jsonify(r) finally: db.close() # ─── 5. Vergunningsaanvraag Support ──────────────────────────────────────── PERMIT_CHECKLISTS = { 'wabo_milieu': { 'label': 'Wabo — Milieubelastende activiteiten (Wm)', 'checklist': [ 'Aanvraagformulier Omgevingsvergunning (digitaal via Aanvraagbericht Omgevingsloket)', 'Beschrijving van de activiteiten', 'Locatietekening (schaal 1:1000 of 1:500)', 'Situatietekening', 'Inrichtingstekening (blokschema, procesbeschrijving)', 'Emissiegegevens (lucht, water, geluid, bodem)', 'Energie-analyse', 'Afvalstoffenoverzicht (EURAL-codes, hoeveelheden)', 'Geluidsrapport (indien van toepassing)', 'Bodemkwaliteitsonderzoek (indien van toepassing)', 'Veiligheidsrapport (BRZO/Seveso, indien van toepassing)', 'Extern Veiligheidsrapport (indien vereist)', 'Brandweervoorschriften compliance', 'MIL-Effectrapportage (indien MER-plichtig)', 'Kwaliteitsborging plan (indien van toepassing)', ], 'authority': 'Omgevingsdienst / Provincie', 'processing_time': '6-26 weken' }, 'wm_besluit': { 'label': 'Wet milieubeheer — Vergunning', 'checklist': [ 'Aanvraagformulier Wm-vergunning', 'Beschrijving van de inrichting en processen', 'Emissiegegevens per emissiepunt', 'Monitoringsprogramma', 'Afvalstoffenbeheerplan', 'Energiebesparingsplan', 'Geluidsrapport', 'Luchtkwaliteitsberekening (indien van toepassing)', 'Bodembeschermingsmaatregelen', 'Hulpbronnenanalyse', ], 'authority': 'Omgevingsdienst', 'processing_time': '6-12 weken' }, 'watervergunning': { 'label': 'Watervergunning (lozing/onttrekking)', 'checklist': [ 'Aanvraagformulier watervergunning', 'Lozingsonderzoek / waterbalans', 'Kwaliteit van te lozen water (analyse-rapporten)', 'Lozingen per emissiepunt (debiet, concentraties)', 'Effect op oppervlaktewater / grondwater', 'Maatregelen ter voorkoming van verontreiniging', 'Monitoringsplan waterkwaliteit', 'Situatietekening waterlopen', ], 'authority': 'Waterschap / Rijkswaterstaat', 'processing_time': '8-16 weken' }, 'brzo_vergunning': { 'label': 'BRZO / Seveso — Veiligheidsrapport', 'checklist': [ 'Veiligheidsrapport (conform BRZO 2015 bijlage 2)', 'Bedrijfsrisicoanalyse (QRA)', 'Melding gevaarlijke stoffen (Aanvangsmelding)', 'Stoffenlijst met hoeveelheden en classificatie', 'Interne noodplan (Company Emergency Plan)', 'Externe noodplan bijdrage', 'Veiligheidsbeheerssysteem (VBS) beschrijving', 'Landinrichtingsrapport', 'DOM (Document Onderhoud Management)', ], 'authority': 'Provincie / DCMR', 'processing_time': '12-26 weken' } } def _permits_list(): db = get_db() try: rows = db.execute("SELECT * FROM mil_permits ORDER BY created_at DESC").fetchall() results = [] for r in rows: d = _d(r) if d.get('checklist_data'): d['checklist_data'] = json.loads(d['checklist_data']) if d.get('document_requirements'): d['document_requirements'] = json.loads(d['document_requirements']) results.append(d) return jsonify(results) finally: db.close() def _permits_create(): data = request.get_json() if not data or not data.get('project_name') or not data.get('permit_type'): return jsonify({'error': 'project_name and permit_type required'}), 400 permit_type = data['permit_type'] checklist_info = PERMIT_CHECKLISTS.get(permit_type, PERMIT_CHECKLISTS['wabo_milieu']) # Initialize checklist with all items unchecked checklist = {item: False for item in checklist_info['checklist']} db = get_db() try: now = datetime.utcnow().isoformat() cur = db.execute( """INSERT INTO mil_permits (project_name, permit_type, permit_status, checklist_data, document_requirements, authority, created_at) VALUES (?,?,?,?,?,?,?)""", (data['project_name'], permit_type, 'draft', json.dumps(checklist), json.dumps(checklist_info), checklist_info['authority'], now)) db.commit() return jsonify({'id': cur.lastrowid, 'checklist': checklist_info}), 201 finally: db.close() def _permits_detail(pid): db = get_db() try: r = _d(db.execute("SELECT * FROM mil_permits WHERE id=?", (pid,)).fetchone()) if not r: return jsonify({'error': 'Not found'}), 404 if r.get('checklist_data'): r['checklist_data'] = json.loads(r['checklist_data']) if r.get('document_requirements'): r['document_requirements'] = json.loads(r['document_requirements']) # Enrich with checklist metadata info = PERMIT_CHECKLISTS.get(r['permit_type'], {}) r['checklist_info'] = info return jsonify(r) finally: db.close() def _permits_update_checklist(pid): data = request.get_json() db = get_db() try: row = db.execute("SELECT checklist_data FROM mil_permits WHERE id=?", (pid,)).fetchone() if not row: return jsonify({'error': 'Not found'}), 404 checklist = json.loads(row['checklist_data'] or '{}') if data.get('action') == 'toggle' and 'item' in data: checklist[data['item']] = bool(data.get('checked', False)) elif 'checklist_data' in data: checklist = data['checklist_data'] db.execute("UPDATE mil_permits SET checklist_data=?, updated_at=? WHERE id=?", (json.dumps(checklist), datetime.utcnow().isoformat(), pid)) db.commit() return jsonify({'status': 'updated'}) finally: db.close() def _permits_checklist_templates(): """Return all available checklist templates.""" return jsonify(PERMIT_CHECKLISTS) # ─── UI: Main Milieu Page ────────────────────────────────────────────────── def _milieu_page(page, BASE_PATH): return page(f''' {ENTERPRISE_CSS}
Emissie monitoring · Afvalstroom management · MER screening · Vergunningsaanvragen
Laden...
Laden...
| Laden... |
Laden...
| Laden... |
Laden...
Laden...
Laden...