ADR-010: Valg av regelmotor og beslutningstaker⚓︎
Erstattet av ADR-013 (2026-07-13, AB#21271). ADR-013 fastsetter arkitekturen som faktisk ble bygget; denne ADR-en beskriver komponenter som aldri ble implementert.
Kontekst og problemstilling⚓︎
Dagens analyse-pipeline sender alle saksdokumenter og regler fra SharePoint til en LLM (Gemini) som evaluerer regeloppfyllelse i én operasjon. Dette gir ikke-deterministiske resultater på to nivåer:
- Fakta-ekstraksjon: LLM trekker ut inkonsistente fakta fra dokumentene fra kjøring til kjøring
- Regelanvendelse: LLM tolker regelverket ulikt selv med identiske fakta
Juridiske og faglige krav krever at en saksbehandler kan stå ansvarlig for en vurdering. Det betyr at alle regelkjøringer må være sporbare til konkrete fakta og eksplisitte regler. I tillegg har oppdagede feil i analyser svekket tilliten blant virkemiddelansvarlige, og systemet nærmer seg produksjonssetting.
Regler er i dag lagret som fritekst-prompts i SharePoint (Regel.Beskrivelse). Det gir lav testbarhet og ingen versjonskontroll.
To distinkte konsepter⚓︎
Det er viktig å skille mellom:
- Regelmotor — evaluerer individuelle regler mot strukturerte fakta og returnerer et
RegelResultat(oppfylt/ikke oppfylt + begrunnelse). Kan være deterministisk C# eller LLM-drevet per regel. - Beslutningstaker — orkestrerer regelkjøring, aggregerer resultater og produserer en endelig
RapportResultat(innstilling). Koordinerer også manglende data, oppfølging og tilstandsstyring gjennom saksgangen.
Et alternativ som C# pipeline/strategi-mønster håndterer disse lagene med separate metoder, men uten eksplisitt rammeverk. MAF Workflow skiller dem eksplisitt: funksjoner er regelmotoren, agenter er beslutningstakere.
Relaterte beslutninger: ADR-006 (købasert analyse), ADR-001 (blob-struktur for dokumenter).
Nøkkeldrivere⚓︎
- Saksbehandler skal kunne se hvilke regler som er kjørt, hvilke fakta som ble brukt, og om regelen er oppfylt eller ikke
- Regeldefinisjoner skal kunne deklareres i YAML og være enhetstestbare — ikke lagret som fritekst i SharePoint
- Hver regel skal kunne konfigurere hvilke data retrievers som henter input — løsrevet fra regellogikken selv
- Evalueringsinstruksjoner til LLM kan fortsatt uttrykkes som prompts (fleksibilitet for fagansvarlige)
- Arbeidsflyter er per virkemiddel — ikke en generisk motor
- Løsningen skal fungere i .NET 10 og passe inn i eksisterende Worker-arkitektur
- MAF Workflows anses som umoden teknologi — leverandøravhengighet og begrenset community er en risiko
Vurderte alternativer⚓︎
- C# pipeline/strategi-mønster — virkemiddelspesifikke arbeidsflyter i ren C#
- Microsoft RulesEngine — JSON-drevet regelmotor fra Microsoft, åpen kildekode
- NRules — .NET-native forward-chaining regelmotor
- MAF Workflows + YAML-deklarert regelmotor — MAF Workflows som beslutningstaker, YAML-deklarerte regler med konfigurerbare data retrievers som regelmotor
Beslutning⚓︎
Valgt alternativ: MAF Workflows + YAML-deklarert regelmotor, fordi det gir et tydelig arkitektonisk skille mellom regelmotor og beslutningstaker, støtter konfigurerbare data retrievers per regel, og lar fagansvarlige deklarere regler i YAML uten å måtte skrive C#. MAF Workflow fungerer som beslutningstaker og orkestrerer kjøring basert på regeldefinisjoner lastet fra YAML.
Konsekvenser⚓︎
- Positivt: regelkjøringer er deterministiske — samme fakta gir alltid samme konklusjon
- Positivt: regler kan enhetstestes isolert fra LLM og dokumenter
- Positivt: fagansvarlige kan deklarere og endre regler i YAML uten å skrive C#
- Positivt: data retrievers er konfigurerbare per regel — ny datakilde krever ikke endring av regellogikken
- Positivt: tydelig skille mellom hva som hentes (retriever), hva som evalueres (regel/funksjon), og hva som besluttes (MAF agent)
- Negativt: YAML-laster og retriever-registry er ny infrastruktur som må bygges og vedlikeholdes
- Negativt: MAF Workflows er umoden teknologi — leverandøravhengighet er en risiko
- Nøytralt: LLM brukes fortsatt for fakta-ekstraksjon og LLM-drevne regelsteg — ekstraksjonsfeil er isolert og sporbart
Implementasjonsplan⚓︎
Arkitekturen er tre lag per virkemiddel:
| Text Only | |
|---|---|
YAML-format for regeldeklarasjon⚓︎
Regler deklareres i YAML under src/Enova.Agent.Kai/Rules/<VirkemiddelId>/. Hver regel angir hvilke data retrievers som skal kjøres og hvilken funksjon som evaluerer resultatet:
Retriever-grensesnitt⚓︎
Retriever-registry løses via DI: IEnumerable<IDataRetriever> indeksert på Type.
Regelmotor-kjøring⚓︎
MAF Workflow-funksjonen kjører en regel slik:
Berørte filer⚓︎
src/Enova.Agent.Kai.Abstractions/Services/IDataRetriever.cs— nytt retriever-grensesnittsrc/Enova.Agent.Kai.Abstractions/Models/RegelDefinisjon.cs— C#-modell for YAML-struktursrc/Enova.Agent.Kai/Rules/<VirkemiddelId>/— YAML-filer per regel per virkemiddelsrc/Enova.Agent.Kai/Retrievers/— implementasjoner avIDataRetrieversrc/Enova.Agent.Kai/Rules/RegelLaster.cs— laster og validerer YAML-regler ved oppstartsrc/Enova.Agent.Kai/Rules/RetrieverRegistry.cs— løser oppIDataRetrieveretter type-navnsrc/Enova.Agent.Kai/Rules/FunksjonRegistry.cs— mapperfunksjon-navn til C#-metodesrc/Enova.Agent.Kai/Workflows/— MAF Workflow per virkemiddel (beslutningstaker)src/Enova.Agent.Kai.Worker/JobProcessor.cs— erstatt LLM-kall med MAF Workflow-kalltests/Enova.Agent.Kai.Tests/Rules/— enhetstester for individuelle regeldefinisjoner
Avhengigheter: YamlDotNet for YAML-parsing (eller Microsoft.Extensions.Configuration.Yaml)
Mønstre som skal følges:
- YAML-filer er embedded resources — lastes ved oppstart, valideres mot RegelDefinisjon-skjema
- Retriever-typer registreres i DI: services.AddScoped<IDataRetriever, DocumentExtractorRetriever>()
- MAF Workflow løser opp regler via RegelLaster og kjører dem via RetrieverRegistry + FunksjonRegistry
- Følg eksisterende options-mønster for virkemiddelspesifikke grenser og terskelverdier
Mønstre som skal unngås:
- Ikke hardkod retriever-typer i regellogikk — bruk alltid YAML-konfigurasjonen
- Ikke send rå dokumenttekst til LLM — kun strukturerte fakta fra retrievers
- Ikke legg regellogikk i
JobProcessordirekte — alt går via MAF Workflow
Migrasjonssti:
- Fase 1: Bygg
IDataRetriever,RegelLaster,RetrieverRegistry— verifiser med én regel - Fase 2: Migrer eksisterende SharePoint-regler til YAML per virkemiddel
- Fase 3: Fase ut
RegelRepositoryog direkte SharePoint-avhengighet
Verifisering⚓︎
- [ ] YAML-regler lastes og valideres ved oppstart — ugyldig YAML kaster tydelig feilmelding
- [ ] Retriever-typer fra YAML løses opp korrekt via registry
- [ ]
RapportResultatinneholderRegelResultatper regel medRegelId,Oppfylt,Begrunnelse, ogFaktaReferanser - [ ] Enhetstester for regeldefinisjoner kjører uten LLM eller nettverkskall (mocket
IDataRetriever) - [ ] Samme
RapportResultatproduseres ved to identiske kjøringer med samme inputdokumenter - [ ]
dotnet testpasserer uten endringer i integrasjonstester
Presiseringer ved akseptering (2026-05-21)⚓︎
Avklaringer som dukket opp under sprint 1-planlegging for første reelle bruk (Tunge nullutslippskjøretøy, US #20415). Disse presiseringene overstyrer tilsvarende detaljer i opprinnelig "Implementasjonsplan" ovenfor — den står som historisk intensjon.
Beslutningstaker: native workflow-graf, ikke MAF Workflows⚓︎
Etter ADR-utkastet er det levert en native WorkflowGraphRunner i US #20185 med støtte for Direct/Conditional/FanOut/FanIn-kanter — se src/Enova.Kai.Application/WorkflowEngine/WorkflowGraphRunner.cs. Den fungerer som beslutningstaker uten avhengighet til MAF Workflows. ADR-010s negative punkter knyttet til MAF Workflows ("umoden teknologi", "leverandøravhengighet") er dermed unngått. Valget av beslutningstaker-implementasjon presiseres her til native workflow-graf.
v2-prosjektstruktur⚓︎
Opprinnelig implementasjonsplan ble skrevet før v2-arkitekturen (Application/Domain/Infrastructure-split) var ferdig. v2-stier:
| Komponent | Plassering |
|---|---|
IDataRetriever-grensesnitt |
src/Enova.Kai.Application/Retrievers/IDataRetriever.cs |
RetrieverRegistry |
src/Enova.Kai.Application/Retrievers/RetrieverRegistry.cs |
| Retriever-implementasjoner | src/Enova.Kai.Infrastructure/Retrievers/ (DocumentExtractorRetriever, MimirRetriever, WebsakRetriever) |
| YAML-regelfiler | src/Enova.Kai.Application/Rules/<VirkemiddelId>/ som embedded resources |
RegelLaster |
src/Enova.Kai.Application/Rules/RegelLaster.cs |
FunksjonRegistry |
src/Enova.Kai.Application/Rules/FunksjonRegistry.cs |
| Regel-funksjoner | src/Enova.Kai.Application/Rules/<VirkemiddelId>/Functions/*.cs |
| Fact-modeller per virkemiddel | src/Enova.Kai.Domain/Facts/<VirkemiddelId>/ |
| Workflow-orkestrering | Eksisterer: WorkflowGraphRunner.cs |
Content Understanding-skjema per virkemiddel⚓︎
Fakta-modeller er per-virkemiddel, ikke felles katalog. Hvert virkemiddel har forskjellig kombinasjon av dokumenttyper og felter; en felles modell hadde drukknet i optional-felter. Schema-evolusjon styres per virkemiddel ved at Content Understanding-analyzeren versjoneres sammen med fact-modellen.
| C# | |
|---|---|
Funksjon-konvensjon⚓︎
Hver regel-funksjon implementerer:
| C# | |
|---|---|
DI-registrering: services.AddScoped<IRegelFunksjon, SjekkSøknadsdatoFunksjon>(). FunksjonRegistry indekserer på Navn. Asynk-signatur valgt fordi enkelte funksjoner kan trenge ekstra retriever-kall under evaluering.
HITL — WaitingForHuman⚓︎
WorkflowGraphRunner.cs:41 og :120 støtter allerede pause på RuleResult.WaitingForHuman = true. For full HITL-flyt:
CaseEvaluationfår feltStatus: Completed | WaitingForHuman | FailedogPausedAtRuleId: string?- Resume via
POST /cases/{caseKey}/evaluations/{evalId}/resumemed saksbehandlers utfall som input - Read-API (under US #20124) inkluderer pause-status og pauserende regel slik at webflaten kan vise saken i "venter på meg"-kø
Detaljer i Task #20423.
Regelkilde og versjonering — utstår⚓︎
Strategi for hvordan SharePoint-regler (kilde i dag) skal speiles til v2 (snapshot-import vs runtime-fetch) er utskutt til egen ADR. Sprint 1-arbeidet bruker manuelt respesifiserte YAML-filer per regel. Når snapshot-modellen er bestemt vil fact-modellen, regel-YAMLene og evt. Rule.Version knyttes til en RuleSetRevision for sporbarhet (jf. US #20124).
Oppdatert migrasjonssti⚓︎
Erstatter opprinnelig migrasjonssti (som forutsatte v1-prosjekter):
- Fase 1 —
IDataRetriever+RegelLaster+RetrieverRegistry+FunksjonRegistryiEnova.Kai.Application. Verifiseres med én deterministisk regel. - Fase 2 — Respesifiser SharePoint-regler per virkemiddel til YAML + funksjoner. Første virkemiddel: Tunge nullutslippskjøretøy.
- Fase 3 — Fas ut v1
RegelRepository.csog direkte SharePoint-runtime-avhengighet iEnova.Agent.Kai. v2 leser kun fra YAML/DB. - Fase 4 — Egen ADR + implementasjon av snapshot-import fra SharePoint (jf. "Regelkilde og versjonering" ovenfor).
Fordeler og ulemper per alternativ⚓︎
C# pipeline/strategi-mønster⚓︎
Virkemiddelspesifikke arbeidsflyt-klasser implementerer IRegelWorkflow. Fakta ekstraheres strukturert, regler evalueres i C#-kode eller via LLM med minimalt fakta-kontekst.
- Positivt: ingen nye avhengigheter
- Positivt: full testbarhet — enhetstester per regel uten LLM
- Positivt: versjonskontroll av regler via git
- Positivt: virkemiddelspesifikk logikk er eksplisitt
- Negativt: mer standardkode per virkemiddel enn deklarative alternativer
Microsoft RulesEngine⚓︎
JSON-drevet regelmotor fra Microsoft (åpen kildekode). Regler uttrykkes som lambda-uttrykk i JSON.
- Positivt: regler kan i prinsippet redigeres uten rekompilering
- Positivt: aktivt vedlikehold fra Microsoft
- Negativt: regler i JSON gir dårligere IDE-støtte og vanskeligere enhetstesting enn C#-kode
- Negativt: ekstra avhengighet for et problem C# løser like godt
- Negativt: lambda-syntaks i JSON er uleselig for fagansvarlige
NRules⚓︎
Forward-chaining regelmotor med fluent API i C#. Brukes i domenedrevne systemer med mange sammenkoblede regler.
- Positivt: kraftig for komplekse regelnettverk med avhengigheter mellom regler
- Negativt: overkill for sekvensielle, uavhengige regelsjekker per virkemiddel
- Negativt: bratt læringskurve — begrenset community i .NET-verdenen
- Negativt: ekstra avhengighet uten klar fordel over ren C#
MAF Workflows⚓︎
Microsoft Agents Framework Workflows skiller eksplisitt mellom regelmotor og beslutningstaker:
- Funksjoner = regelmotoren — deterministiske beregninger og strukturerte sjekkpunkter per regel
- Agenter = beslutningstakeren — LLM-drevne vurderinger, orkestrering av manglende data, og produksjon av endelig innstilling
En søknad for et virkemiddel modelleres som en arbeidsflyt der hvert steg fra innsendt søknad til innstilling er et eget trinn med funksjoner og agenter som utførere.
Eksempel: Tunge nullutslippskjøretøy — søknad til innstilling⚓︎
flowchart TD
A([Søknad mottatt]) --> B[Funksjon: Ekstraher søknadsfakta\nAzure Content Understanding]
B --> C{Fakta komplett?}
C -- Nei --> D[Agent: Be om manglende opplysninger\nLLM genererer oppfølgingsbrev]
D --> B
C -- Ja --> E[Funksjon: Sjekk formelle krav\nOrgnr, søknadsdato, virkemiddel-ID]
E --> F{Formelle krav\noppfylt?}
F -- Nei --> G([Avvis søknad\nautomatisk])
F -- Ja --> H[Funksjon: Beregn støttebeløp\nAntall kjøretøy × sats]
H --> I[Funksjon: Sjekk budsjettramme\nGjenstående ramme ≥ beregnet beløp?]
I --> J[Agent: Vurder teknisk dokumentasjon\nLLM verifiserer samsvar med krav]
J --> K[Agent: Vurder risiko\nLLM vurderer prosjektrisiko mot risikomatrise]
K --> L[Funksjon: Aggreger regelresultater\nSamle RegelResultat per regel]
L --> M{Alle regler\noppfylt?}
M -- Ja --> N([Innstilling: Innvilg\nmed beregnet beløp])
M -- Nei --> O([Innstilling: Avslå\nmed begrunnelse per regel])
style A fill:#e8f5e9
style G fill:#ffebee
style N fill:#e8f5e9
style O fill:#ffebee
style D fill:#fff3e0
style J fill:#fff3e0
style K fill:#fff3e0
Oransje bokser = agenter (beslutningstakere, LLM-drevne). Hvite = funksjoner (regelmotor, deterministiske). Grønne/røde = terminalsteg.
Vurdering av MAF Workflows for dette mønsteret:
- Positivt: native integrasjon med MAF som allerede brukes i prosjektet
- Positivt: eksplisitt skille mellom regelmotor (funksjoner) og beslutningstaker (agenter) — to distinkte konsepter i samme rammeverk
- Positivt: agenter håndterer kompleks orkestrering med innebygd tilstandsstyring
- Positivt: arbeidsflyt-diagrammet kan representeres direkte i MAF-kode
- Negativt: MAF Workflows er umoden teknologi med uklar langsiktig retning
- Negativt: begrenset community og dokumentasjon — feilsøking er kostbart
- Negativt: leverandøravhengighet — bruddendringer i rammeverket kan tvinge refaktorering
- Negativt: høy kompleksitet for et problem som løses enklere i ren C# — det samme flydiagrammet kan implementeres som en sekvens av metodekall
Det samme arbeidsflyten i C# pipeline/strategi-mønster ville vært en enkel sekvens av await SjekkFormellKravAsync(), await BeregneStøttebeløpAsync() osv. — uten rammeverksoverhead.
Tilleggsinformasjon⚓︎
- Fakta-ekstraksjon er avhengig av at Azure Content Understanding støtter virkemiddelspesifikke skjemaer — dette bør verifiseres i en spike før implementasjon starter
- Revisit-trigger: hvis antall virkemidler overstiger ~10 og standardkode per arbeidsflyt blir uhåndterlig, vurder Microsoft RulesEngine på nytt
- Relatert funksjonalitet: Deterministiske resultater, Korrekt regelverk
- ADR-013 — utvider regelmotoren med workflow-orkestrering og facts-pipelines.