feat: Upselling-Funnel Assessment → Compliance Optimizer
Some checks failed
Build + Deploy / build-admin-compliance (push) Successful in 2m17s
Build + Deploy / build-backend-compliance (push) Successful in 3m22s
Build + Deploy / build-ai-sdk (push) Successful in 1m1s
Build + Deploy / build-developer-portal (push) Successful in 1m21s
Build + Deploy / build-tts (push) Failing after 1m32s
Build + Deploy / build-document-crawler (push) Successful in 37s
Build + Deploy / build-dsms-gateway (push) Successful in 24s
CI / branch-name (push) Has been skipped
Build + Deploy / trigger-orca (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m55s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Failing after 59s
CI / test-python-backend (push) Successful in 42s
CI / test-python-document-crawler (push) Successful in 35s
CI / test-python-dsms-gateway (push) Successful in 26s
CI / validate-canonical-controls (push) Successful in 18s
Some checks failed
Build + Deploy / build-admin-compliance (push) Successful in 2m17s
Build + Deploy / build-backend-compliance (push) Successful in 3m22s
Build + Deploy / build-ai-sdk (push) Successful in 1m1s
Build + Deploy / build-developer-portal (push) Successful in 1m21s
Build + Deploy / build-tts (push) Failing after 1m32s
Build + Deploy / build-document-crawler (push) Successful in 37s
Build + Deploy / build-dsms-gateway (push) Successful in 24s
CI / branch-name (push) Has been skipped
Build + Deploy / trigger-orca (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m55s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Failing after 59s
CI / test-python-backend (push) Successful in 42s
CI / test-python-document-crawler (push) Successful in 35s
CI / test-python-dsms-gateway (push) Successful in 26s
CI / validate-canonical-controls (push) Successful in 18s
Verbindet das kostenlose UCCA Assessment mit dem bezahlten
Compliance Optimizer durch gezielte CTAs:
- OptimizerUpsellCard: Kontextabhaengig (CONDITIONAL→prominent, YES→dezent)
- Assessment Detail: "Optimieren" Button + CTA-Block nach Ergebnis
- Advisory Board ResultView: CTA nach Wizard-Abschluss
- Optimizer "new": Auto-Submit bei ?from_assessment={id}
- Optimizer Liste + Detail: Links zum Quell-Assessment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import React from 'react'
|
||||
import { AssessmentResultCard } from '@/components/sdk/use-case-assessment/AssessmentResultCard'
|
||||
import { OptimizerUpsellCard } from '@/components/sdk/compliance-optimizer/OptimizerUpsellCard'
|
||||
|
||||
interface Props {
|
||||
result: unknown
|
||||
@@ -35,6 +36,13 @@ export function ResultView({ result, onGoToAssessment, onGoToOverview }: Props)
|
||||
{r.result && (
|
||||
<AssessmentResultCard result={r.result as unknown as Parameters<typeof AssessmentResultCard>[0]['result']} />
|
||||
)}
|
||||
{r.result && r.assessment?.id && (
|
||||
<OptimizerUpsellCard
|
||||
feasibility={(r.result as { feasibility?: string }).feasibility || 'YES'}
|
||||
assessmentId={r.assessment.id}
|
||||
riskScore={(r.result as { risk_score?: number }).risk_score}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ export default function OptimizationDetailPage() {
|
||||
<Link href="/sdk/compliance-optimizer" className="text-sm text-blue-600 hover:underline">← Zurueck</Link>
|
||||
<h1 className="text-2xl font-bold text-gray-900 mt-1">{data.title || 'Optimierung'}</h1>
|
||||
<p className="text-sm text-gray-500">{new Date(data.created_at).toLocaleString('de-DE')} — v{data.constraint_version}</p>
|
||||
{data.assessment_id && (
|
||||
<Link href={`/sdk/use-cases/${data.assessment_id}`} className="text-sm text-purple-600 hover:underline">
|
||||
Basierend auf Assessment
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<ZoneBadge zone={data.is_compliant ? 'SAFE' : 'FORBIDDEN'} />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useState, useEffect, Suspense } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { ZoneBadge } from '@/components/sdk/compliance-optimizer/ZoneBadge'
|
||||
|
||||
interface DimensionField {
|
||||
@@ -63,9 +63,21 @@ const TOGGLE_DIMENSIONS = [
|
||||
{ key: 'logging_required', label: 'Protokollierungspflicht' },
|
||||
]
|
||||
|
||||
export default function NewOptimizationPage() {
|
||||
function NewOptimizationPageInner() {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const fromAssessment = searchParams.get('from_assessment')
|
||||
const [autoOptimizing, setAutoOptimizing] = useState(false)
|
||||
const [title, setTitle] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (!fromAssessment) return
|
||||
setAutoOptimizing(true)
|
||||
fetch(`/api/sdk/v1/maximizer/optimize-from-assessment/${fromAssessment}`, { method: 'POST' })
|
||||
.then(r => r.ok ? r.json() : Promise.reject('failed'))
|
||||
.then(data => router.push(`/sdk/compliance-optimizer/${data.id}`))
|
||||
.catch(() => setAutoOptimizing(false))
|
||||
}, [fromAssessment, router])
|
||||
const [config, setConfig] = useState<Record<string, string>>({
|
||||
automation_level: 'assistive', decision_binding: 'non_binding', decision_impact: 'low',
|
||||
domain: 'general', data_type: 'non_personal', human_in_loop: 'required',
|
||||
@@ -104,6 +116,18 @@ export default function NewOptimizationPage() {
|
||||
}
|
||||
}
|
||||
|
||||
if (autoOptimizing) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 text-center py-24">
|
||||
<div className="animate-pulse">
|
||||
<span className="text-4xl">📊</span>
|
||||
<h2 className="text-xl font-bold text-gray-900 mt-4 mb-2">Optimierung laeuft...</h2>
|
||||
<p className="text-sm text-gray-500">Assessment wird analysiert und optimale Konfiguration berechnet.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-1">Neue Optimierung</h1>
|
||||
@@ -161,3 +185,11 @@ export default function NewOptimizationPage() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function NewOptimizationPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="max-w-4xl mx-auto p-6 text-gray-500">Laden...</div>}>
|
||||
<NewOptimizationPageInner />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ interface OptimizationSummary {
|
||||
created_at: string
|
||||
zone_map: Record<string, { zone: 'FORBIDDEN' | 'RESTRICTED' | 'SAFE' }>
|
||||
max_safe_config?: { safety_score: number; utility_score: number }
|
||||
assessment_id?: string
|
||||
}
|
||||
|
||||
function countZones(zoneMap: Record<string, { zone: string }>) {
|
||||
@@ -83,6 +84,7 @@ export default function ComplianceOptimizerPage() {
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Titel</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Zonen</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Quelle</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Datum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -104,6 +106,15 @@ export default function ComplianceOptimizerPage() {
|
||||
{zones.restricted > 0 && <span className="text-yellow-600 mr-2">{zones.restricted} eingeschraenkt</span>}
|
||||
<span className="text-green-600">{zones.safe} erlaubt</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm">
|
||||
{o.assessment_id ? (
|
||||
<Link href={`/sdk/use-cases/${o.assessment_id}`} className="text-purple-600 hover:underline text-xs">
|
||||
Assessment
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-gray-400 text-xs">Manuell</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-gray-500">
|
||||
{new Date(o.created_at).toLocaleDateString('de-DE')}
|
||||
</td>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { useState, useEffect } from 'react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { AssessmentResultCard } from '@/components/sdk/use-case-assessment/AssessmentResultCard'
|
||||
import { OptimizerUpsellCard } from '@/components/sdk/compliance-optimizer/OptimizerUpsellCard'
|
||||
|
||||
interface TriggeredRule {
|
||||
code: string
|
||||
@@ -138,6 +139,18 @@ export default function AssessmentDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const [optimizing, setOptimizing] = useState(false)
|
||||
const handleOptimize = async () => {
|
||||
setOptimizing(true)
|
||||
try {
|
||||
const res = await fetch(`/api/sdk/v1/maximizer/optimize-from-assessment/${assessmentId}`, { method: 'POST' })
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
router.push(`/sdk/compliance-optimizer/${data.id}`)
|
||||
}
|
||||
} catch { /* silent */ } finally { setOptimizing(false) }
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
@@ -234,6 +247,13 @@ export default function AssessmentDetailPage() {
|
||||
>
|
||||
↓ JSON
|
||||
</a>
|
||||
<button
|
||||
onClick={handleOptimize}
|
||||
disabled={optimizing}
|
||||
className="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{optimizing ? 'Optimiere...' : 'Optimieren'}
|
||||
</button>
|
||||
<Link
|
||||
href={`/sdk/use-cases/new?edit=${assessmentId}`}
|
||||
className="px-4 py-2 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
||||
@@ -273,6 +293,13 @@ export default function AssessmentDetailPage() {
|
||||
{/* Result */}
|
||||
<AssessmentResultCard result={resultForCard as Parameters<typeof AssessmentResultCard>[0]['result']} />
|
||||
|
||||
{/* Compliance Optimizer Upsell */}
|
||||
<OptimizerUpsellCard
|
||||
feasibility={assessment.feasibility}
|
||||
assessmentId={assessmentId}
|
||||
riskScore={assessment.risk_score}
|
||||
/>
|
||||
|
||||
{/* KI-Erklärung */}
|
||||
{assessment.explanation_text && (
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-xl p-6">
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
|
||||
interface OptimizerUpsellCardProps {
|
||||
feasibility: string
|
||||
assessmentId: string
|
||||
riskScore?: number
|
||||
}
|
||||
|
||||
export function OptimizerUpsellCard({ feasibility, assessmentId, riskScore }: OptimizerUpsellCardProps) {
|
||||
const isRestricted = feasibility === 'CONDITIONAL' || feasibility === 'NO'
|
||||
|
||||
if (isRestricted) {
|
||||
return (
|
||||
<div className="bg-amber-50 border-2 border-amber-300 rounded-xl p-5">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-2xl">📊</span>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-base font-semibold text-amber-900">
|
||||
{feasibility === 'NO' ? 'Use Case aktuell nicht umsetzbar' : 'Use Case eingeschraenkt machbar'}
|
||||
</h3>
|
||||
<p className="text-sm text-amber-800 mt-1">
|
||||
Der <strong>Compliance Optimizer</strong> zeigt Ihnen die optimale Konfiguration,
|
||||
um den regulatorischen Spielraum maximal auszunutzen — ohne Grenzen zu ueberschreiten.
|
||||
</p>
|
||||
{riskScore != null && riskScore >= 50 && (
|
||||
<p className="text-xs text-amber-700 mt-1">
|
||||
Risiko-Score {riskScore}/100 — besonders hohes Optimierungspotenzial.
|
||||
</p>
|
||||
)}
|
||||
<Link
|
||||
href={`/sdk/compliance-optimizer/new?from_assessment=${assessmentId}`}
|
||||
className="inline-flex items-center gap-1 mt-3 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Jetzt optimieren →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-blue-900">Regulatorischen Spielraum pruefen</h3>
|
||||
<p className="text-xs text-blue-700 mt-0.5">
|
||||
Pruefen Sie ob Sie den regulatorischen Spielraum noch besser nutzen koennen.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={`/sdk/compliance-optimizer/new?from_assessment=${assessmentId}`}
|
||||
className="text-sm text-blue-600 hover:underline whitespace-nowrap"
|
||||
>
|
||||
Optional optimieren →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user