fix(mock-data): Fake-Daten bei leerer DB entfernt — ISMS 0%, Dashboard keine simulierten Trends, Compliance-Hub keine Fallback-Zahlen
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 29s
CI / test-python-backend-compliance (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 17s

- ISMS Overview: 14% → 0% bei leerer DB, "not_started" Status, alle Kapitel 0%
- Dashboard: 12-Monate simulierte Trend-Historie entfernt
- Compliance-Hub: Hardcoded Fallback-Statistiken (474/180/95/120/79/44/558/19) → 0
- SQLAlchemy Bug: `is not None` → `.isnot(None)` in SoA-Query
- Hardcoded chapter_7/8_status="pass" → berechnet aus Findings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-07 23:25:25 +01:00
parent 0c75182fb3
commit 56758e8b55
5 changed files with 89 additions and 62 deletions

View File

@@ -365,7 +365,7 @@ export default function ComplianceHubPage() {
</div>
<div className="flex items-center gap-6 mb-4">
<div>
<p className="text-4xl font-bold text-purple-600">{mappings?.total || 474}</p>
<p className="text-4xl font-bold text-purple-600">{mappings?.total || 0}</p>
<p className="text-sm text-slate-500">Mappings gesamt</p>
</div>
<div className="flex-1 h-16 bg-slate-50 rounded-lg p-3">
@@ -377,19 +377,14 @@ export default function ComplianceHubPage() {
</span>
))}
{!mappings?.by_regulation && (
<>
<span className="px-2 py-0.5 bg-purple-100 text-purple-700 rounded text-xs">GDPR: 180</span>
<span className="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs">AI Act: 95</span>
<span className="px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs">BSI: 120</span>
<span className="px-2 py-0.5 bg-orange-100 text-orange-700 rounded text-xs">CRA: 79</span>
</>
<span className="px-2 py-0.5 bg-slate-100 text-slate-500 rounded text-xs">Keine Mappings vorhanden</span>
)}
</div>
</div>
</div>
<p className="text-sm text-slate-600">
Automatisch generierte Verknuepfungen zwischen {dashboard?.total_controls || 44} Controls
und {dashboard?.total_requirements || 558} Anforderungen aus {dashboard?.total_regulations || 19} Verordnungen.
Automatisch generierte Verknuepfungen zwischen {dashboard?.total_controls || 0} Controls
und {dashboard?.total_requirements || 0} Anforderungen aus {dashboard?.total_regulations || 0} Verordnungen.
</p>
</div>

View File

@@ -164,16 +164,14 @@ async def get_executive_dashboard(db: Session = Depends(get_db)):
else:
traffic_light = "red"
# Generate trend data (last 12 months - simulated for now)
# Trend data — only show current score, no simulated history
trend_data = []
now = datetime.utcnow()
for i in range(11, -1, -1):
month_date = now - timedelta(days=i * 30)
trend_score = max(0, min(100, score - (11 - i) * 2 + (5 if i > 6 else 0)))
if total > 0:
now = datetime.utcnow()
trend_data.append(TrendDataPoint(
date=month_date.strftime("%Y-%m-%d"),
score=round(trend_score, 1),
label=month_abbr[month_date.month][:3],
date=now.strftime("%Y-%m-%d"),
score=round(score, 1),
label=month_abbr[now.month][:3],
))
# Get top 5 risks (sorted by severity)
@@ -304,21 +302,16 @@ async def get_compliance_trend(
current_score = ((passing + partial * 0.5) / total) * 100 if total > 0 else 0
# Generate simulated historical data
# Trend data — only current score, no simulated history
trend_data = []
now = datetime.utcnow()
for i in range(months - 1, -1, -1):
month_date = now - timedelta(days=i * 30)
variation = ((i * 7) % 5) - 2
trend_score = max(0, min(100, current_score - (months - 1 - i) * 1.5 + variation))
if total > 0:
now = datetime.utcnow()
trend_data.append({
"date": month_date.strftime("%Y-%m-%d"),
"score": round(trend_score, 1),
"label": f"{month_abbr[month_date.month]} {month_date.year % 100}",
"month": month_date.month,
"year": month_date.year,
"date": now.strftime("%Y-%m-%d"),
"score": round(current_score, 1),
"label": f"{month_abbr[now.month]} {now.year % 100}",
"month": now.month,
"year": now.year,
})
return {

View File

@@ -1399,8 +1399,14 @@ async def run_readiness_check(
chapter_4_status=get_chapter_status(chapter_4_majors, False),
chapter_5_status=get_chapter_status(chapter_5_majors, False),
chapter_6_status=get_chapter_status(chapter_6_majors, False),
chapter_7_status="pass", # Support - typically ok
chapter_8_status="pass", # Operation - checked via controls
chapter_7_status=get_chapter_status(
any("7." in (f.iso_reference or "") for f in potential_majors),
any("7." in (f.iso_reference or "") for f in potential_minors)
),
chapter_8_status=get_chapter_status(
any("8." in (f.iso_reference or "") for f in potential_majors),
any("8." in (f.iso_reference or "") for f in potential_minors)
),
chapter_9_status=get_chapter_status(chapter_9_majors, False),
chapter_10_status=get_chapter_status(chapter_10_majors, False),
potential_majors=[f.model_dump() for f in potential_majors],
@@ -1517,7 +1523,7 @@ async def get_iso27001_overview(db: Session = Depends(get_db)):
soa_total = db.query(StatementOfApplicabilityDB).count()
soa_approved = db.query(StatementOfApplicabilityDB).filter(
StatementOfApplicabilityDB.approved_at is not None
StatementOfApplicabilityDB.approved_at.isnot(None)
).count()
soa_all_approved = soa_total > 0 and soa_approved == soa_total
@@ -1555,32 +1561,58 @@ async def get_iso27001_overview(db: Session = Depends(get_db)):
SecurityObjectiveDB.status == "achieved"
).count()
# Calculate readiness
readiness_factors = [
scope_approved,
soa_all_approved,
last_mgmt_review is not None and last_mgmt_review.review_date >= last_year,
last_internal_audit is not None and (last_internal_audit.actual_end_date or date.min) >= last_year,
open_majors == 0,
policies_total > 0 and policies_approved >= policies_total * 0.8,
objectives_total > 0
]
certification_readiness = sum(readiness_factors) / len(readiness_factors) * 100
# Calculate readiness — empty DB must yield 0%
# Each factor requires positive evidence (not just absence of problems)
has_any_data = any([
scope_approved, soa_total > 0, policies_total > 0,
objectives_total > 0, last_mgmt_review is not None,
last_internal_audit is not None
])
if not has_any_data:
certification_readiness = 0.0
else:
readiness_factors = [
scope_approved,
soa_all_approved,
last_mgmt_review is not None and last_mgmt_review.review_date >= last_year,
last_internal_audit is not None and (last_internal_audit.actual_end_date or date.min) >= last_year,
open_majors == 0 and (soa_total > 0 or policies_total > 0), # Only counts if there's actual data
policies_total > 0 and policies_approved >= policies_total * 0.8,
objectives_total > 0
]
certification_readiness = sum(readiness_factors) / len(readiness_factors) * 100
# Overall status
if open_majors > 0:
if not has_any_data:
overall_status = "not_started"
elif open_majors > 0:
overall_status = "not_ready"
elif certification_readiness >= 80:
overall_status = "ready"
else:
overall_status = "at_risk"
# Build chapter status list
# Build chapter status list — empty DB must show 0% / "not_started"
def _chapter_status(has_positive_evidence: bool, has_issues: bool) -> str:
if not has_positive_evidence:
return "not_started"
return "compliant" if not has_issues else "non_compliant"
# Chapter 9: count sub-components for percentage
ch9_parts = sum([last_mgmt_review is not None, last_internal_audit is not None])
ch9_pct = (ch9_parts / 2) * 100
# Chapter 10: only show 100% if there's actual CAPA activity, not just empty
capa_total = db.query(AuditFindingDB).count()
ch10_has_data = capa_total > 0
ch10_pct = 100.0 if (ch10_has_data and open_majors == 0) else (50.0 if ch10_has_data else 0.0)
chapters = [
ISO27001ChapterStatus(
chapter="4",
title="Kontext der Organisation",
status="compliant" if scope_approved else "non_compliant",
status=_chapter_status(scope_approved, False),
completion_percentage=100.0 if scope_approved else 0.0,
open_findings=0,
key_documents=["ISMS Scope", "Context Analysis"] if scope_approved else [],
@@ -1589,37 +1621,40 @@ async def get_iso27001_overview(db: Session = Depends(get_db)):
ISO27001ChapterStatus(
chapter="5",
title="Führung",
status="compliant" if policies_approved > 0 else "non_compliant",
completion_percentage=(policies_approved / max(policies_total, 1)) * 100,
status=_chapter_status(policies_total > 0, policies_approved < policies_total),
completion_percentage=(policies_approved / max(policies_total, 1)) * 100 if policies_total > 0 else 0.0,
open_findings=0,
key_documents=[f"Policy {i+1}" for i in range(min(policies_approved, 3))],
key_documents=[f"Policy {i+1}" for i in range(min(policies_approved, 3))] if policies_approved > 0 else [],
last_reviewed=None
),
ISO27001ChapterStatus(
chapter="6",
title="Planung",
status="compliant" if objectives_total > 0 else "partial",
completion_percentage=75.0 if objectives_total > 0 else 25.0,
status=_chapter_status(objectives_total > 0, False),
completion_percentage=75.0 if objectives_total > 0 else 0.0,
open_findings=0,
key_documents=["Risk Register", "Security Objectives"],
key_documents=["Risk Register", "Security Objectives"] if objectives_total > 0 else [],
last_reviewed=None
),
ISO27001ChapterStatus(
chapter="9",
title="Bewertung der Leistung",
status="compliant" if (last_mgmt_review and last_internal_audit) else "non_compliant",
completion_percentage=100.0 if (last_mgmt_review and last_internal_audit) else 50.0,
status=_chapter_status(ch9_parts > 0, open_majors + open_minors > 0),
completion_percentage=ch9_pct,
open_findings=open_majors + open_minors,
key_documents=["Internal Audit Report", "Management Review Minutes"],
key_documents=(
(["Internal Audit Report"] if last_internal_audit else []) +
(["Management Review Minutes"] if last_mgmt_review else [])
),
last_reviewed=last_mgmt_review.approved_at if last_mgmt_review else None
),
ISO27001ChapterStatus(
chapter="10",
title="Verbesserung",
status="compliant" if open_majors == 0 else "non_compliant",
completion_percentage=100.0 if open_majors == 0 else 50.0,
status=_chapter_status(ch10_has_data, open_majors > 0),
completion_percentage=ch10_pct,
open_findings=open_majors,
key_documents=["CAPA Register"],
key_documents=["CAPA Register"] if ch10_has_data else [],
last_reviewed=None
)
]

View File

@@ -1677,7 +1677,7 @@ class ISO27001ChapterStatus(BaseModel):
class ISO27001OverviewResponse(BaseModel):
"""Complete ISO 27001 status overview."""
overall_status: str # "ready", "at_risk", "not_ready"
overall_status: str # "ready", "at_risk", "not_ready", "not_started"
certification_readiness: float # 0-100
chapters: List[ISO27001ChapterStatus]
scope_approved: bool

View File

@@ -821,14 +821,18 @@ class TestOverviewDashboard:
"""Tests for the ISO 27001 overview endpoint."""
def test_overview_empty_isms(self):
"""GET /isms/overview on empty DB should return not_ready."""
"""GET /isms/overview on empty DB should return not_started with 0% readiness."""
r = client.get("/isms/overview")
assert r.status_code == 200
body = r.json()
assert body["overall_status"] in ("not_ready", "at_risk")
assert body["overall_status"] == "not_started"
assert body["certification_readiness"] == 0.0
assert body["scope_approved"] is False
assert body["open_major_findings"] == 0
assert body["policies_count"] == 0
# All chapters should show 0% on empty DB
for ch in body["chapters"]:
assert ch["completion_percentage"] == 0.0, f"Chapter {ch['chapter']} should be 0% on empty DB"
def test_overview_with_data(self):
"""GET /isms/overview should reflect created data."""