Phase 1 — Python (klausur-service): 5 monoliths → 36 files - dsfa_corpus_ingestion.py (1,828 LOC → 5 files) - cv_ocr_engines.py (2,102 LOC → 7 files) - cv_layout.py (3,653 LOC → 10 files) - vocab_worksheet_api.py (2,783 LOC → 8 files) - grid_build_core.py (1,958 LOC → 6 files) Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files - staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3) - policy_handlers.go (700 → 2), repository.go (684 → 2) - search.go (592 → 2), ai_extraction_handlers.go (554 → 2) - seed_data.go (591 → 2), grade_service.go (646 → 2) Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files - sdk/types.ts (2,108 → 16 domain files) - ai/rag/page.tsx (2,686 → 14 files) - 22 page.tsx files split into _components/ + _hooks/ - 11 component files split into sub-components - 10 SDK data catalogs added to loc-exceptions - Deleted dead backup index_original.ts (4,899 LOC) All original public APIs preserved via re-export facades. Zero new errors: Python imports verified, Go builds clean, TypeScript tsc --noEmit shows only pre-existing errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
335 lines
8.6 KiB
TypeScript
335 lines
8.6 KiB
TypeScript
import type {
|
|
VendorComplianceState,
|
|
VendorComplianceAction,
|
|
VendorStatistics,
|
|
ComplianceStatistics,
|
|
RiskOverview,
|
|
VendorStatus,
|
|
VendorRole,
|
|
RiskLevel,
|
|
FindingType,
|
|
FindingSeverity,
|
|
} from './types'
|
|
|
|
import { getRiskLevelFromScore } from './types'
|
|
|
|
// ==========================================
|
|
// INITIAL STATE
|
|
// ==========================================
|
|
|
|
export const initialState: VendorComplianceState = {
|
|
processingActivities: [],
|
|
vendors: [],
|
|
contracts: [],
|
|
findings: [],
|
|
controls: [],
|
|
controlInstances: [],
|
|
riskAssessments: [],
|
|
isLoading: false,
|
|
error: null,
|
|
selectedVendorId: null,
|
|
selectedActivityId: null,
|
|
activeTab: 'overview',
|
|
lastModified: null,
|
|
}
|
|
|
|
// ==========================================
|
|
// REDUCER
|
|
// ==========================================
|
|
|
|
export function vendorComplianceReducer(
|
|
state: VendorComplianceState,
|
|
action: VendorComplianceAction
|
|
): VendorComplianceState {
|
|
const updateState = (updates: Partial<VendorComplianceState>): VendorComplianceState => ({
|
|
...state,
|
|
...updates,
|
|
lastModified: new Date(),
|
|
})
|
|
|
|
switch (action.type) {
|
|
// Processing Activities
|
|
case 'SET_PROCESSING_ACTIVITIES':
|
|
return updateState({ processingActivities: action.payload })
|
|
|
|
case 'ADD_PROCESSING_ACTIVITY':
|
|
return updateState({
|
|
processingActivities: [...state.processingActivities, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_PROCESSING_ACTIVITY':
|
|
return updateState({
|
|
processingActivities: state.processingActivities.map((activity) =>
|
|
activity.id === action.payload.id
|
|
? { ...activity, ...action.payload.data, updatedAt: new Date() }
|
|
: activity
|
|
),
|
|
})
|
|
|
|
case 'DELETE_PROCESSING_ACTIVITY':
|
|
return updateState({
|
|
processingActivities: state.processingActivities.filter(
|
|
(activity) => activity.id !== action.payload
|
|
),
|
|
})
|
|
|
|
// Vendors
|
|
case 'SET_VENDORS':
|
|
return updateState({ vendors: action.payload })
|
|
|
|
case 'ADD_VENDOR':
|
|
return updateState({
|
|
vendors: [...state.vendors, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_VENDOR':
|
|
return updateState({
|
|
vendors: state.vendors.map((vendor) =>
|
|
vendor.id === action.payload.id
|
|
? { ...vendor, ...action.payload.data, updatedAt: new Date() }
|
|
: vendor
|
|
),
|
|
})
|
|
|
|
case 'DELETE_VENDOR':
|
|
return updateState({
|
|
vendors: state.vendors.filter((vendor) => vendor.id !== action.payload),
|
|
})
|
|
|
|
// Contracts
|
|
case 'SET_CONTRACTS':
|
|
return updateState({ contracts: action.payload })
|
|
|
|
case 'ADD_CONTRACT':
|
|
return updateState({
|
|
contracts: [...state.contracts, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_CONTRACT':
|
|
return updateState({
|
|
contracts: state.contracts.map((contract) =>
|
|
contract.id === action.payload.id
|
|
? { ...contract, ...action.payload.data, updatedAt: new Date() }
|
|
: contract
|
|
),
|
|
})
|
|
|
|
case 'DELETE_CONTRACT':
|
|
return updateState({
|
|
contracts: state.contracts.filter((contract) => contract.id !== action.payload),
|
|
})
|
|
|
|
// Findings
|
|
case 'SET_FINDINGS':
|
|
return updateState({ findings: action.payload })
|
|
|
|
case 'ADD_FINDINGS':
|
|
return updateState({
|
|
findings: [...state.findings, ...action.payload],
|
|
})
|
|
|
|
case 'UPDATE_FINDING':
|
|
return updateState({
|
|
findings: state.findings.map((finding) =>
|
|
finding.id === action.payload.id
|
|
? { ...finding, ...action.payload.data, updatedAt: new Date() }
|
|
: finding
|
|
),
|
|
})
|
|
|
|
// Controls
|
|
case 'SET_CONTROLS':
|
|
return updateState({ controls: action.payload })
|
|
|
|
case 'SET_CONTROL_INSTANCES':
|
|
return updateState({ controlInstances: action.payload })
|
|
|
|
case 'UPDATE_CONTROL_INSTANCE':
|
|
return updateState({
|
|
controlInstances: state.controlInstances.map((instance) =>
|
|
instance.id === action.payload.id
|
|
? { ...instance, ...action.payload.data }
|
|
: instance
|
|
),
|
|
})
|
|
|
|
// Risk Assessments
|
|
case 'SET_RISK_ASSESSMENTS':
|
|
return updateState({ riskAssessments: action.payload })
|
|
|
|
case 'UPDATE_RISK_ASSESSMENT':
|
|
return updateState({
|
|
riskAssessments: state.riskAssessments.map((assessment) =>
|
|
assessment.id === action.payload.id
|
|
? { ...assessment, ...action.payload.data }
|
|
: assessment
|
|
),
|
|
})
|
|
|
|
// UI State
|
|
case 'SET_LOADING':
|
|
return { ...state, isLoading: action.payload }
|
|
|
|
case 'SET_ERROR':
|
|
return { ...state, error: action.payload }
|
|
|
|
case 'SET_SELECTED_VENDOR':
|
|
return { ...state, selectedVendorId: action.payload }
|
|
|
|
case 'SET_SELECTED_ACTIVITY':
|
|
return { ...state, selectedActivityId: action.payload }
|
|
|
|
case 'SET_ACTIVE_TAB':
|
|
return { ...state, activeTab: action.payload }
|
|
|
|
default:
|
|
return state
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// COMPUTED VALUES (pure functions of state)
|
|
// ==========================================
|
|
|
|
export function computeVendorStats(state: VendorComplianceState): 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) // Normalize to 1-25
|
|
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,
|
|
}
|
|
}
|
|
|
|
export function computeComplianceStats(state: VendorComplianceState): 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,
|
|
}
|
|
}
|
|
|
|
export function computeRiskOverview(state: VendorComplianceState): 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,
|
|
}
|
|
}
|