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
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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user