""" FastAPI routes for Compliance Process Manager — recurring compliance tasks. Endpoints: GET /process-tasks — list with filters (status, category, frequency, overdue, etc.) GET /process-tasks/stats — counts by status, category, due windows GET /process-tasks/upcoming — tasks due in next N days POST /process-tasks — create task GET /process-tasks/{id} — single task PUT /process-tasks/{id} — update task DELETE /process-tasks/{id} — delete task POST /process-tasks/{id}/complete — complete a task (with history + next_due recalc) POST /process-tasks/{id}/skip — skip a task (with reason + next_due recalc) GET /process-tasks/{id}/history — task history entries POST /process-tasks/seed — seed ~50 standard tasks (idempotent) """ import json import logging from datetime import datetime from typing import Optional, List, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query, Header from pydantic import BaseModel from sqlalchemy import text from sqlalchemy.orm import Session from classroom_engine.database import get_db from .tenant_utils import get_tenant_id as _get_tenant_id from .db_utils import row_to_dict as _row_to_dict logger = logging.getLogger(__name__) router = APIRouter(prefix="/process-tasks", tags=["process-tasks"]) # ============================================================================= # Constants # ============================================================================= VALID_CATEGORIES = {"dsgvo", "nis2", "bsi", "iso27001", "ai_act", "internal"} VALID_PRIORITIES = {"critical", "high", "medium", "low"} VALID_FREQUENCIES = {"weekly", "monthly", "quarterly", "semi_annual", "yearly", "once"} VALID_STATUSES = {"pending", "in_progress", "completed", "overdue", "skipped"} FREQUENCY_DAYS = { "weekly": 7, "monthly": 30, "quarterly": 90, "semi_annual": 182, "yearly": 365, "once": None, } # ============================================================================= # Pydantic Schemas # ============================================================================= class ProcessTaskCreate(BaseModel): task_code: str title: str description: Optional[str] = None category: str priority: str = "medium" frequency: str = "yearly" assigned_to: Optional[str] = None responsible_team: Optional[str] = None linked_control_ids: Optional[List] = [] linked_module: Optional[str] = None next_due_date: Optional[str] = None due_reminder_days: int = 14 notes: Optional[str] = None tags: Optional[List] = [] class ProcessTaskUpdate(BaseModel): title: Optional[str] = None description: Optional[str] = None category: Optional[str] = None priority: Optional[str] = None frequency: Optional[str] = None assigned_to: Optional[str] = None responsible_team: Optional[str] = None linked_control_ids: Optional[List] = None linked_module: Optional[str] = None next_due_date: Optional[str] = None due_reminder_days: Optional[int] = None status: Optional[str] = None notes: Optional[str] = None tags: Optional[List] = None class ProcessTaskComplete(BaseModel): completed_by: Optional[str] = None result: Optional[str] = None evidence_id: Optional[str] = None notes: Optional[str] = None class ProcessTaskSkip(BaseModel): reason: str # ============================================================================= # Routes # ============================================================================= @router.get("") async def list_tasks( status: Optional[str] = Query(None), category: Optional[str] = Query(None), frequency: Optional[str] = Query(None), assigned_to: Optional[str] = Query(None), overdue: Optional[bool] = Query(None), limit: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0), db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), ): """List process tasks with optional filters.""" where_clauses = ["tenant_id = :tenant_id"] params: Dict[str, Any] = {"tenant_id": tenant_id, "limit": limit, "offset": offset} if status: where_clauses.append("status = :status") params["status"] = status if category: where_clauses.append("category = :category") params["category"] = category if frequency: where_clauses.append("frequency = :frequency") params["frequency"] = frequency if assigned_to: where_clauses.append("assigned_to ILIKE :assigned_to") params["assigned_to"] = f"%{assigned_to}%" if overdue: where_clauses.append("next_due_date < CURRENT_DATE AND status NOT IN ('completed','skipped')") where_sql = " AND ".join(where_clauses) total_row = db.execute( text(f"SELECT COUNT(*) FROM compliance_process_tasks WHERE {where_sql}"), params, ).fetchone() total = total_row[0] if total_row else 0 rows = db.execute( text(f""" SELECT * FROM compliance_process_tasks WHERE {where_sql} ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, next_due_date ASC NULLS LAST, created_at DESC LIMIT :limit OFFSET :offset """), params, ).fetchall() return { "tasks": [_row_to_dict(r) for r in rows], "total": total, } @router.get("/stats") async def get_stats( db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), ): """Return task counts by status, category, and due windows.""" row = db.execute(text(""" SELECT COUNT(*) AS total, COUNT(*) FILTER (WHERE status = 'pending') AS pending, COUNT(*) FILTER (WHERE status = 'in_progress') AS in_progress, COUNT(*) FILTER (WHERE status = 'completed') AS completed, COUNT(*) FILTER (WHERE status = 'overdue') AS overdue, COUNT(*) FILTER (WHERE status = 'skipped') AS skipped, COUNT(*) FILTER (WHERE next_due_date < CURRENT_DATE AND status NOT IN ('completed','skipped')) AS overdue_count, COUNT(*) FILTER (WHERE next_due_date <= CURRENT_DATE + 7 AND next_due_date >= CURRENT_DATE AND status NOT IN ('completed','skipped')) AS due_7_days, COUNT(*) FILTER (WHERE next_due_date <= CURRENT_DATE + 14 AND next_due_date >= CURRENT_DATE AND status NOT IN ('completed','skipped')) AS due_14_days, COUNT(*) FILTER (WHERE next_due_date <= CURRENT_DATE + 30 AND next_due_date >= CURRENT_DATE AND status NOT IN ('completed','skipped')) AS due_30_days FROM compliance_process_tasks WHERE tenant_id = :tenant_id """), {"tenant_id": tenant_id}).fetchone() by_status = {} by_category = {} if row: d = dict(row._mapping) by_status = { "pending": d.get("pending", 0) or 0, "in_progress": d.get("in_progress", 0) or 0, "completed": d.get("completed", 0) or 0, "overdue": d.get("overdue", 0) or 0, "skipped": d.get("skipped", 0) or 0, } else: d = {} # Category counts cat_rows = db.execute(text(""" SELECT category, COUNT(*) AS cnt FROM compliance_process_tasks WHERE tenant_id = :tenant_id GROUP BY category """), {"tenant_id": tenant_id}).fetchall() by_category = {r._mapping["category"]: r._mapping["cnt"] for r in cat_rows} return { "total": (d.get("total", 0) or 0), "by_status": by_status, "by_category": by_category, "overdue_count": (d.get("overdue_count", 0) or 0), "due_7_days": (d.get("due_7_days", 0) or 0), "due_14_days": (d.get("due_14_days", 0) or 0), "due_30_days": (d.get("due_30_days", 0) or 0), } @router.get("/upcoming") async def get_upcoming( days: int = Query(30, ge=1, le=365), db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), ): """Tasks due in next N days.""" rows = db.execute(text(""" SELECT * FROM compliance_process_tasks WHERE tenant_id = :tenant_id AND next_due_date IS NOT NULL AND next_due_date <= CURRENT_DATE + :days AND next_due_date >= CURRENT_DATE AND status NOT IN ('completed','skipped') ORDER BY next_due_date ASC """), {"tenant_id": tenant_id, "days": days}).fetchall() return {"tasks": [_row_to_dict(r) for r in rows]} @router.post("", status_code=201) async def create_task( payload: ProcessTaskCreate, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), x_user_id: Optional[str] = Header(None), ): """Create a new process task.""" logger.info("create_task user_id=%s tenant_id=%s code=%s", x_user_id, tenant_id, payload.task_code) if payload.category not in VALID_CATEGORIES: raise HTTPException(status_code=400, detail=f"Invalid category. Must be one of: {', '.join(sorted(VALID_CATEGORIES))}") if payload.priority not in VALID_PRIORITIES: raise HTTPException(status_code=400, detail=f"Invalid priority. Must be one of: {', '.join(sorted(VALID_PRIORITIES))}") if payload.frequency not in VALID_FREQUENCIES: raise HTTPException(status_code=400, detail=f"Invalid frequency. Must be one of: {', '.join(sorted(VALID_FREQUENCIES))}") row = db.execute(text(""" INSERT INTO compliance_process_tasks (tenant_id, task_code, title, description, category, priority, frequency, assigned_to, responsible_team, linked_control_ids, linked_module, next_due_date, due_reminder_days, notes, tags) VALUES (:tenant_id, :task_code, :title, :description, :category, :priority, :frequency, :assigned_to, :responsible_team, CAST(:linked_control_ids AS jsonb), :linked_module, :next_due_date, :due_reminder_days, :notes, CAST(:tags AS jsonb)) RETURNING * """), { "tenant_id": tenant_id, "task_code": payload.task_code, "title": payload.title, "description": payload.description, "category": payload.category, "priority": payload.priority, "frequency": payload.frequency, "assigned_to": payload.assigned_to, "responsible_team": payload.responsible_team, "linked_control_ids": json.dumps(payload.linked_control_ids or []), "linked_module": payload.linked_module, "next_due_date": payload.next_due_date, "due_reminder_days": payload.due_reminder_days, "notes": payload.notes, "tags": json.dumps(payload.tags or []), }).fetchone() db.commit() return _row_to_dict(row) @router.get("/{task_id}") async def get_task( task_id: str, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), ): """Get single process task.""" row = db.execute(text(""" SELECT * FROM compliance_process_tasks WHERE id = :id AND tenant_id = :tenant_id """), {"id": task_id, "tenant_id": tenant_id}).fetchone() if not row: raise HTTPException(status_code=404, detail="Task not found") return _row_to_dict(row) @router.put("/{task_id}") async def update_task( task_id: str, payload: ProcessTaskUpdate, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), x_user_id: Optional[str] = Header(None), ): """Update a process task.""" logger.info("update_task user_id=%s tenant_id=%s id=%s", x_user_id, tenant_id, task_id) updates: Dict[str, Any] = {"id": task_id, "tenant_id": tenant_id, "updated_at": datetime.utcnow()} set_clauses = ["updated_at = :updated_at"] data = payload.model_dump(exclude_unset=True) # Validate enum fields if provided if "category" in data and data["category"] not in VALID_CATEGORIES: raise HTTPException(status_code=400, detail=f"Invalid category. Must be one of: {', '.join(sorted(VALID_CATEGORIES))}") if "priority" in data and data["priority"] not in VALID_PRIORITIES: raise HTTPException(status_code=400, detail=f"Invalid priority. Must be one of: {', '.join(sorted(VALID_PRIORITIES))}") if "frequency" in data and data["frequency"] not in VALID_FREQUENCIES: raise HTTPException(status_code=400, detail=f"Invalid frequency. Must be one of: {', '.join(sorted(VALID_FREQUENCIES))}") if "status" in data and data["status"] not in VALID_STATUSES: raise HTTPException(status_code=400, detail=f"Invalid status. Must be one of: {', '.join(sorted(VALID_STATUSES))}") for field, value in data.items(): if field in ("linked_control_ids", "tags", "follow_up_actions"): updates[field] = json.dumps(value or []) set_clauses.append(f"{field} = CAST(:{field} AS jsonb)") else: updates[field] = value set_clauses.append(f"{field} = :{field}") if len(set_clauses) == 1: raise HTTPException(status_code=400, detail="No fields to update") row = db.execute(text(f""" UPDATE compliance_process_tasks SET {', '.join(set_clauses)} WHERE id = :id AND tenant_id = :tenant_id RETURNING * """), updates).fetchone() db.commit() if not row: raise HTTPException(status_code=404, detail="Task not found") return _row_to_dict(row) @router.delete("/{task_id}", status_code=204) async def delete_task( task_id: str, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), x_user_id: Optional[str] = Header(None), ): """Delete a process task.""" logger.info("delete_task user_id=%s tenant_id=%s id=%s", x_user_id, tenant_id, task_id) result = db.execute(text(""" DELETE FROM compliance_process_tasks WHERE id = :id AND tenant_id = :tenant_id """), {"id": task_id, "tenant_id": tenant_id}) db.commit() if result.rowcount == 0: raise HTTPException(status_code=404, detail="Task not found") @router.post("/{task_id}/complete") async def complete_task( task_id: str, payload: ProcessTaskComplete, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), x_user_id: Optional[str] = Header(None), ): """Complete a task: insert history, update task, calculate next due date.""" logger.info("complete_task user_id=%s tenant_id=%s id=%s", x_user_id, tenant_id, task_id) # Fetch current task task_row = db.execute(text(""" SELECT * FROM compliance_process_tasks WHERE id = :id AND tenant_id = :tenant_id """), {"id": task_id, "tenant_id": tenant_id}).fetchone() if not task_row: raise HTTPException(status_code=404, detail="Task not found") task = dict(task_row._mapping) # Insert history entry db.execute(text(""" INSERT INTO compliance_process_task_history (task_id, completed_by, completed_at, result, evidence_id, notes, status) VALUES (:task_id, :completed_by, NOW(), :result, :evidence_id, :notes, 'completed') """), { "task_id": task_id, "completed_by": payload.completed_by, "result": payload.result, "evidence_id": payload.evidence_id, "notes": payload.notes, }) # Calculate next due date freq = task.get("frequency", "yearly") days_delta = FREQUENCY_DAYS.get(freq) if days_delta is not None: # Recurring task — set next due and reset to pending row = db.execute(text(""" UPDATE compliance_process_tasks SET last_completed_at = NOW(), status = 'pending', completion_date = NOW(), completion_result = :result, completion_evidence_id = :evidence_id, next_due_date = CURRENT_DATE + :days_delta, updated_at = NOW() WHERE id = :id AND tenant_id = :tenant_id RETURNING * """), { "id": task_id, "tenant_id": tenant_id, "result": payload.result, "evidence_id": payload.evidence_id, "days_delta": days_delta, }).fetchone() else: # One-time task — mark completed, no next due row = db.execute(text(""" UPDATE compliance_process_tasks SET last_completed_at = NOW(), status = 'completed', completion_date = NOW(), completion_result = :result, completion_evidence_id = :evidence_id, next_due_date = NULL, updated_at = NOW() WHERE id = :id AND tenant_id = :tenant_id RETURNING * """), { "id": task_id, "tenant_id": tenant_id, "result": payload.result, "evidence_id": payload.evidence_id, }).fetchone() db.commit() return _row_to_dict(row) @router.post("/{task_id}/skip") async def skip_task( task_id: str, payload: ProcessTaskSkip, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), x_user_id: Optional[str] = Header(None), ): """Skip a task with reason: insert history, calculate next due date.""" logger.info("skip_task user_id=%s tenant_id=%s id=%s", x_user_id, tenant_id, task_id) # Fetch current task task_row = db.execute(text(""" SELECT * FROM compliance_process_tasks WHERE id = :id AND tenant_id = :tenant_id """), {"id": task_id, "tenant_id": tenant_id}).fetchone() if not task_row: raise HTTPException(status_code=404, detail="Task not found") task = dict(task_row._mapping) # Insert history entry db.execute(text(""" INSERT INTO compliance_process_task_history (task_id, completed_by, completed_at, result, notes, status) VALUES (:task_id, :completed_by, NOW(), :reason, :reason, 'skipped') """), { "task_id": task_id, "completed_by": x_user_id, "reason": payload.reason, }) # Calculate next due date freq = task.get("frequency", "yearly") days_delta = FREQUENCY_DAYS.get(freq) if days_delta is not None: row = db.execute(text(""" UPDATE compliance_process_tasks SET status = 'pending', next_due_date = CURRENT_DATE + :days_delta, updated_at = NOW() WHERE id = :id AND tenant_id = :tenant_id RETURNING * """), { "id": task_id, "tenant_id": tenant_id, "days_delta": days_delta, }).fetchone() else: row = db.execute(text(""" UPDATE compliance_process_tasks SET status = 'skipped', next_due_date = NULL, updated_at = NOW() WHERE id = :id AND tenant_id = :tenant_id RETURNING * """), { "id": task_id, "tenant_id": tenant_id, }).fetchone() db.commit() return _row_to_dict(row) @router.get("/{task_id}/history") async def get_task_history( task_id: str, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), ): """Return history entries for a task.""" # Verify task belongs to tenant task_row = db.execute(text(""" SELECT id FROM compliance_process_tasks WHERE id = :id AND tenant_id = :tenant_id """), {"id": task_id, "tenant_id": tenant_id}).fetchone() if not task_row: raise HTTPException(status_code=404, detail="Task not found") rows = db.execute(text(""" SELECT * FROM compliance_process_task_history WHERE task_id = :task_id ORDER BY completed_at DESC """), {"task_id": task_id}).fetchall() return {"history": [_row_to_dict(r) for r in rows]} @router.post("/seed") async def seed_tasks( db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), x_user_id: Optional[str] = Header(None), ): """Seed ~50 standard compliance tasks. Idempotent via ON CONFLICT.""" logger.info("seed_tasks user_id=%s tenant_id=%s", x_user_id, tenant_id) seeds = _get_seed_tasks() inserted = 0 for s in seeds: result = db.execute(text(""" INSERT INTO compliance_process_tasks (tenant_id, task_code, title, description, category, priority, frequency, linked_module, is_seed, next_due_date) VALUES (:tenant_id, :task_code, :title, :description, :category, :priority, :frequency, :linked_module, TRUE, CURRENT_DATE + :initial_days) ON CONFLICT (tenant_id, project_id, task_code) DO NOTHING """), { "tenant_id": tenant_id, "task_code": s["task_code"], "title": s["title"], "description": s["description"], "category": s["category"], "priority": s["priority"], "frequency": s["frequency"], "linked_module": s.get("linked_module"), "initial_days": FREQUENCY_DAYS.get(s["frequency"], 365) or 365, }) if result.rowcount > 0: inserted += 1 db.commit() return {"seeded": inserted, "total_available": len(seeds)} # ============================================================================= # Seed Data # ============================================================================= def _get_seed_tasks() -> List[Dict[str, Any]]: """Return ~50 standard compliance tasks for seeding.""" return [ # ─── DSGVO (~15) ───────────────────────────────────────────── { "task_code": "DSGVO-VVT-REVIEW", "title": "VVT-Review und Aktualisierung", "description": "Jaehrliche Ueberpruefung und Aktualisierung des Verzeichnisses von Verarbeitungstaetigkeiten gemaess Art. 30 DSGVO.", "category": "dsgvo", "priority": "high", "frequency": "yearly", "linked_module": "vvt", }, { "task_code": "DSGVO-TOM-REVIEW", "title": "TOM-Review und Aktualisierung", "description": "Jaehrliche Ueberpruefung der technischen und organisatorischen Massnahmen gemaess Art. 32 DSGVO.", "category": "dsgvo", "priority": "high", "frequency": "yearly", "linked_module": "tom", }, { "task_code": "DSGVO-LOESCHFRISTEN", "title": "Loeschfristen-Pruefung", "description": "Quartalspruefung aller Loeschfristen und Durchfuehrung faelliger Loeschungen.", "category": "dsgvo", "priority": "high", "frequency": "quarterly", "linked_module": "loeschfristen", }, { "task_code": "DSGVO-DSB-BERICHT", "title": "DSB-Taetigkeitsbericht", "description": "Jaehrlicher Taetigkeitsbericht des Datenschutzbeauftragten an die Geschaeftsfuehrung.", "category": "dsgvo", "priority": "medium", "frequency": "yearly", "linked_module": None, }, { "task_code": "DSGVO-DSFA-UPDATE", "title": "DSFA-Aktualisierung", "description": "Jaehrliche Ueberpruefung und Aktualisierung der Datenschutz-Folgenabschaetzungen.", "category": "dsgvo", "priority": "high", "frequency": "yearly", "linked_module": "dsfa", }, { "task_code": "DSGVO-SCHULUNG", "title": "Datenschutz-Schulung Mitarbeiter", "description": "Jaehrliche Datenschutz-Schulung fuer alle Mitarbeiter mit Nachweis.", "category": "dsgvo", "priority": "high", "frequency": "yearly", "linked_module": "training", }, { "task_code": "DSGVO-BETROFFENENRECHTE", "title": "Betroffenenrechte-Prozess testen", "description": "Quartalstest der Prozesse fuer Auskunft, Berichtigung, Loeschung und Datenportabilitaet.", "category": "dsgvo", "priority": "medium", "frequency": "quarterly", "linked_module": "dsr", }, { "task_code": "DSGVO-AV-VERTRAEGE", "title": "AV-Vertraege pruefen", "description": "Jaehrliche Pruefung aller Auftragsverarbeitungsvertraege auf Aktualitaet und Vollstaendigkeit.", "category": "dsgvo", "priority": "medium", "frequency": "yearly", "linked_module": "vendor-compliance", }, { "task_code": "DSGVO-DATENPANNE-TEST", "title": "Datenpannen-Meldeprozess testen", "description": "Halbjahrestest des Meldeprozesses fuer Datenschutzverletzungen gemaess Art. 33/34 DSGVO.", "category": "dsgvo", "priority": "high", "frequency": "semi_annual", "linked_module": "incident-response", }, { "task_code": "DSGVO-COOKIE-PRUEFUNG", "title": "Cookie-Banner und Consent pruefen", "description": "Quartalspruefung des Cookie-Banners und der Einwilligungsmechanismen.", "category": "dsgvo", "priority": "medium", "frequency": "quarterly", "linked_module": "consent", }, { "task_code": "DSGVO-DSE-REVIEW", "title": "Datenschutzerklaerung Review", "description": "Halbjaehrliche Ueberpruefung der Datenschutzerklaerung auf Aktualitaet.", "category": "dsgvo", "priority": "medium", "frequency": "semi_annual", "linked_module": "document-generator", }, { "task_code": "DSGVO-EINWILLIGUNG-REVIEW", "title": "Einwilligungen Review", "description": "Quartalspruefung der eingeholten Einwilligungen auf Gueltigkeit und Widerrufbarkeit.", "category": "dsgvo", "priority": "medium", "frequency": "quarterly", "linked_module": "consent", }, { "task_code": "DSGVO-DRITTLAND", "title": "Drittlandsuebermittlung pruefen", "description": "Jaehrliche Pruefung aller Datenuebermittlungen in Drittlaender und deren Rechtsgrundlage.", "category": "dsgvo", "priority": "medium", "frequency": "yearly", "linked_module": None, }, { "task_code": "DSGVO-VERPFLICHTUNG", "title": "Mitarbeiter-Verpflichtung Datengeheimnis", "description": "Jaehrliche Pruefung, ob alle Mitarbeiter auf das Datengeheimnis verpflichtet wurden.", "category": "dsgvo", "priority": "medium", "frequency": "yearly", "linked_module": None, }, { "task_code": "DSGVO-DATENKATEGORIEN", "title": "Datenkategorien-Review", "description": "Jaehrliche Ueberpruefung der erfassten Datenkategorien und deren Zuordnung.", "category": "dsgvo", "priority": "low", "frequency": "yearly", "linked_module": "vvt", }, # ─── NIS2 (~10) ────────────────────────────────────────────── { "task_code": "NIS2-RISIKOBEWERTUNG", "title": "NIS2 Risikobewertung", "description": "Jaehrliche umfassende Risikobewertung der Netz- und Informationssicherheit.", "category": "nis2", "priority": "critical", "frequency": "yearly", "linked_module": "risks", }, { "task_code": "NIS2-LIEFERKETTE", "title": "Lieferketten-Sicherheitspruefung", "description": "Jaehrliche Ueberpruefung der Sicherheitsmassnahmen bei Zulieferern und Dienstleistern.", "category": "nis2", "priority": "high", "frequency": "yearly", "linked_module": "vendor-compliance", }, { "task_code": "NIS2-INCIDENT-UEBUNG", "title": "Incident-Response-Uebung", "description": "Jaehrliche Uebung des Incident-Response-Prozesses mit Dokumentation.", "category": "nis2", "priority": "high", "frequency": "yearly", "linked_module": "incident-response", }, { "task_code": "NIS2-VULNSCAN", "title": "Vulnerability-Scan", "description": "Monatlicher automatisierter Vulnerability-Scan aller IT-Systeme.", "category": "nis2", "priority": "critical", "frequency": "monthly", "linked_module": "security-backlog", }, { "task_code": "NIS2-PATCHMGMT", "title": "Patch-Management-Review", "description": "Monatliche Pruefung des Patch-Status aller Systeme.", "category": "nis2", "priority": "high", "frequency": "monthly", "linked_module": "security-backlog", }, { "task_code": "NIS2-BCM-TEST", "title": "Business-Continuity-Test", "description": "Jaehrlicher Test des Business-Continuity-Plans.", "category": "nis2", "priority": "high", "frequency": "yearly", "linked_module": None, }, { "task_code": "NIS2-ZUGANGSKONTROLLE", "title": "Zugangskontrollen-Review", "description": "Quartalspruefung aller Zugangsberechtigungen und Accounts.", "category": "nis2", "priority": "high", "frequency": "quarterly", "linked_module": None, }, { "task_code": "NIS2-KRYPTOGRAFIE", "title": "Kryptografie-Review", "description": "Jaehrliche Ueberpruefung der eingesetzten kryptografischen Verfahren.", "category": "nis2", "priority": "medium", "frequency": "yearly", "linked_module": None, }, { "task_code": "NIS2-MELDEPFLICHT", "title": "Meldepflicht-Prozess testen", "description": "Halbjaehrlicher Test des NIS2-Meldeprozesses (24h/72h Fristen).", "category": "nis2", "priority": "high", "frequency": "semi_annual", "linked_module": None, }, { "task_code": "NIS2-NETZSEGMENT", "title": "Netzwerksegmentierung-Review", "description": "Jaehrliche Ueberpruefung der Netzwerksegmentierung und Firewall-Regeln.", "category": "nis2", "priority": "medium", "frequency": "yearly", "linked_module": None, }, # ─── BSI (~10) ─────────────────────────────────────────────── { "task_code": "BSI-GRUNDSCHUTZ", "title": "IT-Grundschutz-Check", "description": "Jaehrlicher IT-Grundschutz-Check nach BSI-Standard 200-2.", "category": "bsi", "priority": "high", "frequency": "yearly", "linked_module": None, }, { "task_code": "BSI-BAUSTEIN", "title": "Baustein-Review", "description": "Quartalspruefung der implementierten BSI-Bausteine.", "category": "bsi", "priority": "medium", "frequency": "quarterly", "linked_module": None, }, { "task_code": "BSI-NOTFALLPLAN", "title": "Notfallplan-Test", "description": "Jaehrlicher Test des IT-Notfallplans mit Uebungsszenario.", "category": "bsi", "priority": "high", "frequency": "yearly", "linked_module": "notfallplan", }, { "task_code": "BSI-BACKUP-TEST", "title": "Backup-Restore-Test", "description": "Quartalstest der Backup-Wiederherstellung fuer kritische Systeme.", "category": "bsi", "priority": "high", "frequency": "quarterly", "linked_module": None, }, { "task_code": "BSI-FIREWALL", "title": "Firewall-Rule-Review", "description": "Quartalspruefung aller Firewall-Regeln auf Aktualitaet und Minimalitaet.", "category": "bsi", "priority": "high", "frequency": "quarterly", "linked_module": None, }, { "task_code": "BSI-LOGGING", "title": "Logging-Review", "description": "Monatliche Pruefung der Log-Einstellungen und Log-Auswertung.", "category": "bsi", "priority": "medium", "frequency": "monthly", "linked_module": None, }, { "task_code": "BSI-HAERTUNG", "title": "Haertungs-Check", "description": "Quartalspruefung der System-Haertung nach BSI-Empfehlungen.", "category": "bsi", "priority": "medium", "frequency": "quarterly", "linked_module": None, }, { "task_code": "BSI-ZERTIFIKATE", "title": "Zertifikats-Erneuerung pruefen", "description": "Monatliche Pruefung ablaufender TLS/SSL-Zertifikate.", "category": "bsi", "priority": "high", "frequency": "monthly", "linked_module": None, }, { "task_code": "BSI-RAUMSICHERHEIT", "title": "Raumsicherheit-Pruefung", "description": "Jaehrliche Pruefung der physischen Sicherheit der Serverraeume.", "category": "bsi", "priority": "low", "frequency": "yearly", "linked_module": None, }, { "task_code": "BSI-MEDIEN", "title": "Medienentsorgung-Kontrolle", "description": "Quartalskontrolle der ordnungsgemaessen Entsorgung von Datentraegern.", "category": "bsi", "priority": "medium", "frequency": "quarterly", "linked_module": None, }, # ─── ISO 27001 (~8) ────────────────────────────────────────── { "task_code": "ISO-MGMT-REVIEW", "title": "Management-Review", "description": "Jaehrliches Management-Review des ISMS gemaess ISO 27001 Kap. 9.3.", "category": "iso27001", "priority": "critical", "frequency": "yearly", "linked_module": None, }, { "task_code": "ISO-INT-AUDIT", "title": "Internes ISMS-Audit", "description": "Jaehrliches internes Audit des ISMS gemaess ISO 27001 Kap. 9.2.", "category": "iso27001", "priority": "critical", "frequency": "yearly", "linked_module": "audit", }, { "task_code": "ISO-KORREKTUR", "title": "Korrekturmassnahmen-Review", "description": "Quartalspruefung offener Korrekturmassnahmen aus Audits.", "category": "iso27001", "priority": "high", "frequency": "quarterly", "linked_module": None, }, { "task_code": "ISO-RISK-OWNER", "title": "Risiko-Owner-Review", "description": "Quartalspruefung der Risiko-Zuordnungen und Verantwortlichkeiten.", "category": "iso27001", "priority": "high", "frequency": "quarterly", "linked_module": "risks", }, { "task_code": "ISO-KENNZAHLEN", "title": "Kennzahlen-Erhebung", "description": "Monatliche Erhebung der ISMS-Kennzahlen und KPIs.", "category": "iso27001", "priority": "medium", "frequency": "monthly", "linked_module": None, }, { "task_code": "ISO-SCOPE", "title": "ISMS-Scope-Review", "description": "Jaehrliche Ueberpruefung des ISMS-Geltungsbereichs.", "category": "iso27001", "priority": "medium", "frequency": "yearly", "linked_module": "compliance-scope", }, { "task_code": "ISO-POLICY", "title": "Policy-Aktualisierung", "description": "Jaehrliche Ueberpruefung und Aktualisierung aller ISMS-Policies.", "category": "iso27001", "priority": "medium", "frequency": "yearly", "linked_module": "document-generator", }, { "task_code": "ISO-ZERTIFIZIERUNG", "title": "Zertifizierungs-Vorbereitung", "description": "Jaehrliche Vorbereitung auf externes Zertifizierungsaudit.", "category": "iso27001", "priority": "high", "frequency": "yearly", "linked_module": None, }, # ─── AI Act (~7) ───────────────────────────────────────────── { "task_code": "AIACT-INVENTAR", "title": "KI-Inventar aktualisieren", "description": "Quartalsaktualisierung des Inventars aller KI-Systeme im Einsatz.", "category": "ai_act", "priority": "high", "frequency": "quarterly", "linked_module": "ai-act", }, { "task_code": "AIACT-RISIKO", "title": "KI-Risikobewertung", "description": "Jaehrliche Risikobewertung aller KI-Systeme gemaess EU AI Act.", "category": "ai_act", "priority": "critical", "frequency": "yearly", "linked_module": "ai-act", }, { "task_code": "AIACT-TRANSPARENZ", "title": "Transparenzpflicht-Check", "description": "Quartalspruefung der Transparenzpflichten fuer KI-Systeme.", "category": "ai_act", "priority": "high", "frequency": "quarterly", "linked_module": None, }, { "task_code": "AIACT-OVERSIGHT", "title": "Human-Oversight-Validierung", "description": "Halbjaehrliche Validierung der menschlichen Aufsicht ueber KI-Systeme.", "category": "ai_act", "priority": "high", "frequency": "semi_annual", "linked_module": None, }, { "task_code": "AIACT-BIAS", "title": "Bias-Monitoring", "description": "Quartalspruefung auf Bias und Diskriminierung in KI-Ausgaben.", "category": "ai_act", "priority": "medium", "frequency": "quarterly", "linked_module": None, }, { "task_code": "AIACT-SCHULUNG", "title": "KI-Schulung Mitarbeiter", "description": "Jaehrliche Schulung aller Mitarbeiter zu KI-Kompetenz und AI Act.", "category": "ai_act", "priority": "medium", "frequency": "yearly", "linked_module": "training", }, { "task_code": "AIACT-DOKU", "title": "KI-Dokumentations-Review", "description": "Jaehrliche Ueberpruefung der technischen Dokumentation aller KI-Systeme.", "category": "ai_act", "priority": "medium", "frequency": "yearly", "linked_module": None, }, ]