fix(reporting): Replace deleted dsgvo/vendor/incidents store imports with direct SQL
All checks were successful
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) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 18s
All checks were successful
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) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 18s
The reporting module imported packages deleted in the previous commit. Replaced with direct SQL queries against the compliance schema tables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,31 +7,22 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/academy"
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/dsgvo"
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/incidents"
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/vendor"
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/whistleblower"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
pool *pgxpool.Pool
|
||||
dsgvoStore *dsgvo.Store
|
||||
vendorStore *vendor.Store
|
||||
incidentStore *incidents.Store
|
||||
whistleStore *whistleblower.Store
|
||||
academyStore *academy.Store
|
||||
pool *pgxpool.Pool
|
||||
whistleStore *whistleblower.Store
|
||||
academyStore *academy.Store
|
||||
}
|
||||
|
||||
func NewStore(pool *pgxpool.Pool, ds *dsgvo.Store, vs *vendor.Store, is *incidents.Store, ws *whistleblower.Store, as *academy.Store) *Store {
|
||||
func NewStore(pool *pgxpool.Pool, ws *whistleblower.Store, as *academy.Store) *Store {
|
||||
return &Store{
|
||||
pool: pool,
|
||||
dsgvoStore: ds,
|
||||
vendorStore: vs,
|
||||
incidentStore: is,
|
||||
whistleStore: ws,
|
||||
academyStore: as,
|
||||
pool: pool,
|
||||
whistleStore: ws,
|
||||
academyStore: as,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,61 +32,14 @@ func (s *Store) GenerateReport(ctx context.Context, tenantID uuid.UUID) (*Execut
|
||||
TenantID: tenantID.String(),
|
||||
}
|
||||
|
||||
tid := tenantID.String()
|
||||
// 1. Gather DSGVO stats via direct SQL (Python is now primary for DSGVO)
|
||||
report.DSGVO = s.getDSGVOStats(ctx, tenantID)
|
||||
|
||||
// 1. Gather DSGVO stats
|
||||
dsgvoStats, err := s.dsgvoStore.GetStats(ctx, tenantID)
|
||||
if err == nil && dsgvoStats != nil {
|
||||
total := dsgvoStats.TOMsImplemented + dsgvoStats.TOMsPlanned
|
||||
pct := 0
|
||||
if total > 0 {
|
||||
pct = int(math.Round(float64(dsgvoStats.TOMsImplemented) / float64(total) * 100))
|
||||
}
|
||||
report.DSGVO = DSGVOSummary{
|
||||
ProcessingActivities: dsgvoStats.ProcessingActivities,
|
||||
ActiveProcessings: dsgvoStats.ActiveProcessings,
|
||||
TOMsImplemented: dsgvoStats.TOMsImplemented,
|
||||
TOMsPlanned: dsgvoStats.TOMsPlanned,
|
||||
TOMsTotal: total,
|
||||
CompletionPercent: pct,
|
||||
OpenDSRs: dsgvoStats.OpenDSRs,
|
||||
OverdueDSRs: dsgvoStats.OverdueDSRs,
|
||||
DSFAsCompleted: dsgvoStats.DSFAsCompleted,
|
||||
RetentionPolicies: dsgvoStats.RetentionPolicies,
|
||||
}
|
||||
}
|
||||
// 2. Gather vendor stats via direct SQL (Python is now primary for vendors)
|
||||
report.Vendors = s.getVendorStats(ctx, tenantID)
|
||||
|
||||
// 2. Gather vendor stats
|
||||
vendorStats, err := s.vendorStore.GetVendorStats(ctx, tid)
|
||||
if err == nil && vendorStats != nil {
|
||||
active := 0
|
||||
if v, ok := vendorStats.ByStatus["ACTIVE"]; ok {
|
||||
active = v
|
||||
}
|
||||
report.Vendors = VendorSummary{
|
||||
TotalVendors: vendorStats.TotalVendors,
|
||||
ActiveVendors: active,
|
||||
ByRiskLevel: vendorStats.ByRiskLevel,
|
||||
PendingReviews: vendorStats.PendingReviews,
|
||||
ExpiredContracts: vendorStats.ExpiredContracts,
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Gather incident stats
|
||||
incidentStats, err := s.incidentStore.GetStatistics(ctx, tenantID)
|
||||
if err == nil && incidentStats != nil {
|
||||
critical := 0
|
||||
if v, ok := incidentStats.BySeverity["CRITICAL"]; ok {
|
||||
critical = v
|
||||
}
|
||||
report.Incidents = IncidentSummary{
|
||||
TotalIncidents: incidentStats.TotalIncidents,
|
||||
OpenIncidents: incidentStats.OpenIncidents,
|
||||
CriticalIncidents: critical,
|
||||
NotificationsPending: incidentStats.NotificationsPending,
|
||||
AvgResolutionHours: incidentStats.AvgResolutionHours,
|
||||
}
|
||||
}
|
||||
// 3. Gather incident stats via direct SQL (Python is now primary for incidents)
|
||||
report.Incidents = s.getIncidentStats(ctx, tenantID)
|
||||
|
||||
// 4. Gather whistleblower stats
|
||||
whistleStats, err := s.whistleStore.GetStatistics(ctx, tenantID)
|
||||
@@ -142,6 +86,94 @@ func (s *Store) GenerateReport(ctx context.Context, tenantID uuid.UUID) (*Execut
|
||||
return report, nil
|
||||
}
|
||||
|
||||
// getDSGVOStats queries DSGVO tables directly (previously via dsgvo.Store)
|
||||
func (s *Store) getDSGVOStats(ctx context.Context, tenantID uuid.UUID) DSGVOSummary {
|
||||
summary := DSGVOSummary{}
|
||||
|
||||
// Processing activities
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*), COUNT(*) FILTER (WHERE status = 'ACTIVE') FROM compliance.vvt_entries WHERE tenant_id = $1`, tenantID,
|
||||
).Scan(&summary.ProcessingActivities, &summary.ActiveProcessings)
|
||||
|
||||
// TOMs
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*) FILTER (WHERE status = 'IMPLEMENTED'), COUNT(*) FILTER (WHERE status = 'PLANNED') FROM compliance.tom_entries WHERE tenant_id = $1`, tenantID,
|
||||
).Scan(&summary.TOMsImplemented, &summary.TOMsPlanned)
|
||||
summary.TOMsTotal = summary.TOMsImplemented + summary.TOMsPlanned
|
||||
if summary.TOMsTotal > 0 {
|
||||
summary.CompletionPercent = int(math.Round(float64(summary.TOMsImplemented) / float64(summary.TOMsTotal) * 100))
|
||||
}
|
||||
|
||||
// DSRs
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*) FILTER (WHERE status NOT IN ('COMPLETED','REJECTED')), COUNT(*) FILTER (WHERE deadline < NOW() AND status NOT IN ('COMPLETED','REJECTED')) FROM compliance.dsr_requests WHERE tenant_id = $1`, tenantID,
|
||||
).Scan(&summary.OpenDSRs, &summary.OverdueDSRs)
|
||||
|
||||
// DSFAs
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*) FROM compliance.dsfa_entries WHERE tenant_id = $1 AND status = 'COMPLETED'`, tenantID,
|
||||
).Scan(&summary.DSFAsCompleted)
|
||||
|
||||
// Retention policies
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*) FROM compliance.loeschfristen WHERE tenant_id = $1`, tenantID,
|
||||
).Scan(&summary.RetentionPolicies)
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// getVendorStats queries vendor tables directly (previously via vendor.Store)
|
||||
func (s *Store) getVendorStats(ctx context.Context, tenantID uuid.UUID) VendorSummary {
|
||||
summary := VendorSummary{ByRiskLevel: map[string]int{}}
|
||||
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*), COUNT(*) FILTER (WHERE status = 'ACTIVE') FROM compliance.vendor_compliance WHERE tenant_id = $1`, tenantID,
|
||||
).Scan(&summary.TotalVendors, &summary.ActiveVendors)
|
||||
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT COALESCE(risk_level, 'UNKNOWN'), COUNT(*) FROM compliance.vendor_compliance WHERE tenant_id = $1 GROUP BY risk_level`, tenantID,
|
||||
)
|
||||
if err == nil {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var level string
|
||||
var count int
|
||||
if rows.Scan(&level, &count) == nil {
|
||||
summary.ByRiskLevel[level] = count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*) FROM compliance.vendor_compliance WHERE tenant_id = $1 AND next_review_date < NOW()`, tenantID,
|
||||
).Scan(&summary.PendingReviews)
|
||||
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*) FROM compliance.vendor_compliance WHERE tenant_id = $1 AND contract_end < NOW()`, tenantID,
|
||||
).Scan(&summary.ExpiredContracts)
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// getIncidentStats queries incident tables directly (previously via incidents.Store)
|
||||
func (s *Store) getIncidentStats(ctx context.Context, tenantID uuid.UUID) IncidentSummary {
|
||||
summary := IncidentSummary{}
|
||||
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*), COUNT(*) FILTER (WHERE status NOT IN ('RESOLVED','CLOSED')), COUNT(*) FILTER (WHERE severity = 'CRITICAL' AND status NOT IN ('RESOLVED','CLOSED')) FROM compliance.incidents WHERE tenant_id = $1`, tenantID,
|
||||
).Scan(&summary.TotalIncidents, &summary.OpenIncidents, &summary.CriticalIncidents)
|
||||
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COUNT(*) FROM compliance.incidents WHERE tenant_id = $1 AND notification_required = true AND notification_sent = false`, tenantID,
|
||||
).Scan(&summary.NotificationsPending)
|
||||
|
||||
_ = s.pool.QueryRow(ctx,
|
||||
`SELECT COALESCE(AVG(EXTRACT(EPOCH FROM (resolved_at - created_at))/3600), 0) FROM compliance.incidents WHERE tenant_id = $1 AND resolved_at IS NOT NULL`, tenantID,
|
||||
).Scan(&summary.AvgResolutionHours)
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
func (s *Store) calculateRiskOverview(report *ExecutiveReport) RiskOverview {
|
||||
modules := []ModuleRisk{}
|
||||
|
||||
@@ -311,7 +343,7 @@ func (s *Store) getUpcomingDeadlines(ctx context.Context, tenantID uuid.UUID) []
|
||||
|
||||
// Vendor reviews due
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT name, next_review_date FROM vendor_vendors
|
||||
SELECT name, next_review_date FROM compliance.vendor_compliance
|
||||
WHERE tenant_id = $1 AND next_review_date IS NOT NULL
|
||||
ORDER BY next_review_date ASC LIMIT 10
|
||||
`, tenantID)
|
||||
@@ -343,54 +375,19 @@ func (s *Store) getUpcomingDeadlines(ctx context.Context, tenantID uuid.UUID) []
|
||||
}
|
||||
}
|
||||
|
||||
// Contract expirations
|
||||
rows2, err := s.pool.Query(ctx, `
|
||||
SELECT vv.name, vc.expiration_date, vc.document_type FROM vendor_contracts vc
|
||||
JOIN vendor_vendors vv ON vc.vendor_id = vv.id
|
||||
WHERE vc.tenant_id = $1 AND vc.expiration_date IS NOT NULL
|
||||
ORDER BY vc.expiration_date ASC LIMIT 10
|
||||
`, tenantID)
|
||||
if err == nil {
|
||||
defer rows2.Close()
|
||||
for rows2.Next() {
|
||||
var name, docType string
|
||||
var dueDate time.Time
|
||||
if err := rows2.Scan(&name, &dueDate, &docType); err != nil {
|
||||
continue
|
||||
}
|
||||
daysLeft := int(dueDate.Sub(now).Hours() / 24)
|
||||
severity := "INFO"
|
||||
if daysLeft < 0 {
|
||||
severity = "OVERDUE"
|
||||
} else if daysLeft <= 14 {
|
||||
severity = "URGENT"
|
||||
} else if daysLeft <= 60 {
|
||||
severity = "WARNING"
|
||||
}
|
||||
deadlines = append(deadlines, Deadline{
|
||||
Module: "Contracts",
|
||||
Type: "EXPIRATION",
|
||||
Description: docType + " läuft ab: " + name,
|
||||
DueDate: dueDate,
|
||||
DaysLeft: daysLeft,
|
||||
Severity: severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DSR deadlines (overdue)
|
||||
rows3, err := s.pool.Query(ctx, `
|
||||
SELECT request_type, deadline FROM dsgvo_dsr_requests
|
||||
rows2, err := s.pool.Query(ctx, `
|
||||
SELECT request_type, deadline FROM compliance.dsr_requests
|
||||
WHERE tenant_id = $1 AND status NOT IN ('COMPLETED', 'REJECTED')
|
||||
AND deadline IS NOT NULL
|
||||
ORDER BY deadline ASC LIMIT 10
|
||||
`, tenantID)
|
||||
if err == nil {
|
||||
defer rows3.Close()
|
||||
for rows3.Next() {
|
||||
defer rows2.Close()
|
||||
for rows2.Next() {
|
||||
var reqType string
|
||||
var dueDate time.Time
|
||||
if err := rows3.Scan(&reqType, &dueDate); err != nil {
|
||||
if err := rows2.Scan(&reqType, &dueDate); err != nil {
|
||||
continue
|
||||
}
|
||||
daysLeft := int(dueDate.Sub(now).Hours() / 24)
|
||||
@@ -418,7 +415,6 @@ func (s *Store) getUpcomingDeadlines(ctx context.Context, tenantID uuid.UUID) []
|
||||
return deadlines[i].DueDate.Before(deadlines[j].DueDate)
|
||||
})
|
||||
|
||||
// Limit to top 15
|
||||
if len(deadlines) > 15 {
|
||||
deadlines = deadlines[:15]
|
||||
}
|
||||
@@ -431,10 +427,10 @@ func (s *Store) getRecentActivity(ctx context.Context, tenantID uuid.UUID) []Act
|
||||
|
||||
// Recent vendors created/updated
|
||||
rows, _ := s.pool.Query(ctx, `
|
||||
SELECT name, created_at, 'CREATED' as action FROM vendor_vendors
|
||||
SELECT name, created_at, 'CREATED' as action FROM compliance.vendor_compliance
|
||||
WHERE tenant_id = $1 AND created_at > NOW() - INTERVAL '30 days'
|
||||
UNION ALL
|
||||
SELECT name, updated_at, 'UPDATED' FROM vendor_vendors
|
||||
SELECT name, updated_at, 'UPDATED' FROM compliance.vendor_compliance
|
||||
WHERE tenant_id = $1 AND updated_at > created_at AND updated_at > NOW() - INTERVAL '30 days'
|
||||
ORDER BY 2 DESC LIMIT 5
|
||||
`, tenantID)
|
||||
@@ -463,7 +459,7 @@ func (s *Store) getRecentActivity(ctx context.Context, tenantID uuid.UUID) []Act
|
||||
|
||||
// Recent incidents
|
||||
rows2, _ := s.pool.Query(ctx, `
|
||||
SELECT title, created_at, severity FROM incidents
|
||||
SELECT title, created_at, severity FROM compliance.incidents
|
||||
WHERE tenant_id = $1 AND created_at > NOW() - INTERVAL '30 days'
|
||||
ORDER BY created_at DESC LIMIT 5
|
||||
`, tenantID)
|
||||
|
||||
Reference in New Issue
Block a user