fix(sdk): Align scope types with engine output + project isolation + optional block progress
Some checks failed
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 46s
CI/CD / test-python-backend-compliance (push) Successful in 42s
CI/CD / test-python-document-crawler (push) Successful in 29s
CI/CD / test-python-dsms-gateway (push) Successful in 25s
CI/CD / deploy-hetzner (push) Failing after 2s

Type alignment (root cause of client-side crash):
- RiskFlag: id/title/description → severity/category/message/recommendation
- ScopeGap: id/title/recommendation/relatedDocuments → gapType/currentState/targetState/effort
- NextAction: id/priority:number/effortDays → actionType/priority:string/estimatedEffort
- ScopeReasoning: details → factors + impact
- TriggeredHardTrigger: {rule: HardTriggerRule} → flat fields (ruleId, description, etc.)
- All UI components updated to match engine output shape

Project isolation:
- Scope localStorage key now includes projectId (prevents data leak between projects)

Optional block progress:
- Blocks with only optional questions now show green checkmark when any question answered

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-11 14:58:29 +01:00
parent 46048554cb
commit cb48b8289e
7 changed files with 185 additions and 152 deletions

View File

@@ -51,7 +51,7 @@ export function ScopeOverviewTab({ scopeState, completionStats, onStartProfiling
}
const renderLevelBadge = () => {
if (!decision?.level) {
if (!decision?.determinedLevel) {
return (
<div className="bg-gray-100 border border-gray-300 rounded-xl p-8 text-center">
<div className="inline-flex items-center justify-center w-24 h-24 bg-gray-200 rounded-full mb-4">
@@ -80,13 +80,7 @@ export function ScopeOverviewTab({ scopeState, completionStats, onStartProfiling
}
const renderActiveHardTriggers = () => {
if (!decision?.hardTriggers || decision.triggeredHardTriggers.length === 0) {
return null
}
const activeHardTriggers = decision.triggeredHardTriggers.filter((ht) => ht.matched)
if (activeHardTriggers.length === 0) {
if (!decision?.triggeredHardTriggers || decision.triggeredHardTriggers.length === 0) {
return null
}
@@ -104,23 +98,22 @@ export function ScopeOverviewTab({ scopeState, completionStats, onStartProfiling
<h3 className="text-lg font-semibold text-gray-900">Aktive Hard-Trigger</h3>
</div>
<div className="space-y-3">
{activeHardTriggers.map((trigger, idx) => (
{decision.triggeredHardTriggers.map((trigger, idx) => (
<div
key={idx}
className="border-l-4 border-red-500 bg-red-50 rounded-r-lg p-4"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<h4 className="font-semibold text-gray-900">{trigger.label}</h4>
<p className="text-sm text-gray-600 mt-1">{trigger.description}</p>
<h4 className="font-semibold text-gray-900">{trigger.description}</h4>
{trigger.legalReference && (
<p className="text-xs text-gray-500 mt-2">
<span className="font-medium">Rechtsgrundlage:</span> {trigger.legalReference}
</p>
)}
{trigger.matchedValue && (
{trigger.minimumLevel && (
<p className="text-xs text-gray-700 mt-1">
<span className="font-medium">Erfasster Wert:</span> {trigger.matchedValue}
<span className="font-medium">Mindest-Level:</span> {trigger.minimumLevel}
</p>
)}
</div>
@@ -137,12 +130,9 @@ export function ScopeOverviewTab({ scopeState, completionStats, onStartProfiling
return null
}
const mandatoryDocs = decision.requiredDocuments.filter((doc) => doc.isMandatory)
const optionalDocs = decision.requiredDocuments.filter((doc) => !doc.isMandatory)
const totalEffortDays = decision.requiredDocuments.reduce(
(sum, doc) => sum + (doc.effortEstimate?.days ?? 0),
0
)
const mandatoryDocs = decision.requiredDocuments.filter((doc) => doc.requirement === 'mandatory')
const optionalDocs = decision.requiredDocuments.filter((doc) => doc.requirement === 'recommended')
const totalEffortHours = decision.requiredDocuments.reduce((sum, doc) => sum + (doc.estimatedEffort ?? 0), 0)
return (
<div className="bg-white rounded-xl border border-gray-200 p-6">
@@ -157,8 +147,8 @@ export function ScopeOverviewTab({ scopeState, completionStats, onStartProfiling
<div className="text-sm text-gray-600 mt-1">Optional</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-gray-900">{totalEffortDays}</div>
<div className="text-sm text-gray-600 mt-1">Tage Aufwand (geschätzt)</div>
<div className="text-3xl font-bold text-gray-900">{totalEffortHours}h</div>
<div className="text-sm text-gray-600 mt-1">Aufwand (geschätzt)</div>
</div>
</div>
</div>
@@ -227,11 +217,11 @@ export function ScopeOverviewTab({ scopeState, completionStats, onStartProfiling
<div className="bg-white rounded-xl border border-gray-200 p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Score-Übersicht</h3>
<div className="space-y-4">
{renderScoreGauge('Risiko-Score', decision.scores?.riskScore)}
{renderScoreGauge('Komplexitäts-Score', decision.scores?.complexityScore)}
{renderScoreGauge('Assurance-Score', decision.scores?.assuranceScore)}
{renderScoreGauge('Risiko-Score', decision.scores?.risk_score)}
{renderScoreGauge('Komplexitäts-Score', decision.scores?.complexity_score)}
{renderScoreGauge('Assurance-Score', decision.scores?.assurance_need)}
<div className="pt-4 border-t border-gray-200">
{renderScoreGauge('Gesamt-Score', decision.scores?.compositeScore)}
{renderScoreGauge('Gesamt-Score', decision.scores?.composite_score)}
</div>
</div>
</div>