Files
breakpilot-compliance/admin-compliance/lib/sdk/vendor-compliance/context.tsx
Sharang Parnerkar c05a71163b
Some checks failed
Build + Deploy / build-admin-compliance (push) Successful in 1m37s
Build + Deploy / build-backend-compliance (push) Successful in 12s
Build + Deploy / build-ai-sdk (push) Successful in 10s
Build + Deploy / build-developer-portal (push) Successful in 12s
Build + Deploy / build-tts (push) Successful in 12s
Build + Deploy / build-document-crawler (push) Successful in 11s
Build + Deploy / build-dsms-gateway (push) Successful in 12s
CI/CD / loc-budget (push) Successful in 21s
CI/CD / guardrail-integrity (push) Has been skipped
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 42s
CI/CD / test-python-backend-compliance (push) Has started running
CI/CD / test-python-document-crawler (push) Has been cancelled
CI/CD / test-python-dsms-gateway (push) Has been cancelled
CI/CD / sbom-scan (push) Has been cancelled
CI/CD / validate-canonical-controls (push) Has been cancelled
Build + Deploy / trigger-orca (push) Successful in 2m19s
fix: resolve CI failures in Python tests and admin-compliance build
Python: add missing 'import enum' to compliance/db/models.py shim.
TypeScript: remove duplicate export of useVendorCompliance from
  vendor-compliance/context.tsx (already exported from ./hooks).
Docs: add mandatory pre-push checklist (lint + test + build) to
  AGENTS.python.md and AGENTS.go.md. [guardrail-change]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:41:39 +02:00

272 lines
6.6 KiB
TypeScript

'use client'
import React, {
useReducer,
useMemo,
useEffect,
useState,
useContext,
} from 'react'
import {
VendorComplianceContextValue,
VendorStatistics,
ComplianceStatistics,
RiskOverview,
VendorStatus,
VendorRole,
RiskLevel,
FindingType,
FindingSeverity,
getRiskLevelFromScore,
} from './types'
import { initialState, vendorComplianceReducer } from './reducer'
import { VendorComplianceContext } from './hooks'
import { useVendorComplianceActions } from './use-actions'
import { useContextApiActions } from './context-actions'
// Re-export hooks and selectors for barrel
export {
useVendorCompliance,
useVendor,
useProcessingActivity,
useVendorContracts,
useVendorFindings,
useContractFindings,
useControlInstancesForEntity,
} from './hooks'
// ==========================================
// PROVIDER
// ==========================================
interface VendorComplianceProviderProps {
children: React.ReactNode
tenantId?: string
}
export function VendorComplianceProvider({
children,
tenantId,
}: VendorComplianceProviderProps) {
const [state, dispatch] = useReducer(vendorComplianceReducer, initialState)
const [isInitialized, setIsInitialized] = useState(false)
const actions = useVendorComplianceActions(state, dispatch)
// ==========================================
// COMPUTED VALUES
// ==========================================
const vendorStats = useMemo<VendorStatistics>(() => {
const vendors = state.vendors
const byStatus = vendors.reduce(
(acc, v) => {
acc[v.status] = (acc[v.status] || 0) + 1
return acc
},
{} as Record<VendorStatus, number>
)
const byRole = vendors.reduce(
(acc, v) => {
acc[v.role] = (acc[v.role] || 0) + 1
return acc
},
{} as Record<VendorRole, number>
)
const byRiskLevel = vendors.reduce(
(acc, v) => {
const level = getRiskLevelFromScore(v.residualRiskScore / 4)
acc[level] = (acc[level] || 0) + 1
return acc
},
{} as Record<RiskLevel, number>
)
const now = new Date()
const pendingReviews = vendors.filter(
(v) => v.nextReviewDate && new Date(v.nextReviewDate) <= now
).length
const withExpiredContracts = vendors.filter((v) =>
state.contracts.some(
(c) =>
c.vendorId === v.id &&
c.expirationDate &&
new Date(c.expirationDate) <= now &&
c.status === 'ACTIVE'
)
).length
return {
total: vendors.length,
byStatus,
byRole,
byRiskLevel,
pendingReviews,
withExpiredContracts,
}
}, [state.vendors, state.contracts])
const complianceStats = useMemo<ComplianceStatistics>(() => {
const findings = state.findings
const contracts = state.contracts
const controlInstances = state.controlInstances
const averageComplianceScore =
contracts.length > 0
? contracts.reduce((sum, c) => sum + (c.complianceScore || 0), 0) /
contracts.filter((c) => c.complianceScore !== undefined).length || 0
: 0
const findingsByType = findings.reduce(
(acc, f) => {
acc[f.type] = (acc[f.type] || 0) + 1
return acc
},
{} as Record<FindingType, number>
)
const findingsBySeverity = findings.reduce(
(acc, f) => {
acc[f.severity] = (acc[f.severity] || 0) + 1
return acc
},
{} as Record<FindingSeverity, number>
)
const openFindings = findings.filter(
(f) => f.status === 'OPEN' || f.status === 'IN_PROGRESS'
).length
const resolvedFindings = findings.filter(
(f) => f.status === 'RESOLVED' || f.status === 'FALSE_POSITIVE'
).length
const passedControls = controlInstances.filter(
(ci) => ci.status === 'PASS'
).length
const applicableControls = controlInstances.filter(
(ci) => ci.status !== 'NOT_APPLICABLE'
).length
const controlPassRate =
applicableControls > 0 ? (passedControls / applicableControls) * 100 : 0
return {
averageComplianceScore,
findingsByType,
findingsBySeverity,
openFindings,
resolvedFindings,
controlPassRate,
}
}, [state.findings, state.contracts, state.controlInstances])
const riskOverview = useMemo<RiskOverview>(() => {
const vendors = state.vendors
const findings = state.findings
const averageInherentRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.inherentRiskScore, 0) / vendors.length
: 0
const averageResidualRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.residualRiskScore, 0) / vendors.length
: 0
const highRiskVendors = vendors.filter(
(v) => v.residualRiskScore >= 60
).length
const criticalFindings = findings.filter(
(f) => f.severity === 'CRITICAL' && f.status === 'OPEN'
).length
const transfersToThirdCountries = vendors.filter((v) =>
v.processingLocations.some((pl) => !pl.isEU && !pl.isAdequate)
).length
return {
averageInherentRisk,
averageResidualRisk,
highRiskVendors,
criticalFindings,
transfersToThirdCountries,
}
}, [state.vendors, state.findings])
// ==========================================
// API CALLS (extracted to context-actions.tsx)
// ==========================================
const {
loadData,
refresh,
createProcessingActivity,
deleteProcessingActivity,
duplicateProcessingActivity,
deleteVendor,
deleteContract,
startContractReview,
} = useContextApiActions(state, dispatch)
// ==========================================
// INITIALIZATION
// ==========================================
useEffect(() => {
if (!isInitialized) {
actions.loadData()
setIsInitialized(true)
}
}, [isInitialized, actions])
// ==========================================
// CONTEXT VALUE
// ==========================================
const contextValue = useMemo<VendorComplianceContextValue>(
() => ({
...state,
dispatch,
vendorStats,
complianceStats,
riskOverview,
deleteProcessingActivity,
duplicateProcessingActivity,
deleteVendor,
deleteContract,
startContractReview,
loadData,
refresh,
}),
[
state,
vendorStats,
complianceStats,
riskOverview,
deleteProcessingActivity,
duplicateProcessingActivity,
deleteVendor,
deleteContract,
startContractReview,
loadData,
refresh,
]
)
return (
<VendorComplianceContext.Provider value={contextValue}>
{children}
</VendorComplianceContext.Provider>
)
}