arolariu.Backend.Domain.Invoices.Brokers.AnalysisBrokers.ClassifierBroker
arolariu.Backend.Domain.Invoices
arolariu.Backend.Domain.Invoices.Brokers.AnalysisBrokers.ClassifierBroker Namespace
Classes
AzureClassifierBroker Class
Azure AI Foundry concrete broker responsible for Large Language Model (LLM) backed enrichment of invoice domain aggregates.
public sealed class AzureClassifierBroker : arolariu.Backend.Domain.Invoices.Brokers.AnalysisBrokers.ClassifierBroker.IClassifierBroker
Inheritance System.Object 🡒 AzureClassifierBroker
Implements IClassifierBroker
Remarks
Role (The Standard): Implements the IClassifierBroker abstraction by issuing chat completion requests to Azure AI Foundry (Cognitive Services) model router and mapping raw responses back into domain objects. Provides ONLY translation and graceful degradation — NO orchestration, persistence, retry policy, caching, or domain validation (handled upstream).
Enrichment Pipeline: Parallelizes LLM calls into 3 batches (when enabled by AnalysisOptions): Batch 1 (invoice name + description), Batch 2 (all product categories + allergens), Batch 3 (possible recipes + invoice category). Each prompt failure (e.g. content filter rejection) results in a default / empty fallback without aborting the remaining steps.
Resilience: Catches ClientResultException (Azure SDK) per step and converts it to silent fallback (empty string /
default enum / empty collection) to keep a best-effort enrichment model. Upstream layers MAY introduce logging or metrics decorators.
Determinism: Non-deterministic by design; repeated executions can yield variant textual outputs. Upstream caching or freeze-on-first-success strategies SHOULD be applied if immutability is desired.
Thread Safety: Reuses a single Azure.AI.OpenAI.AzureOpenAIClient instance which is thread-safe; the class itself contains no mutable shared state.
Security: Uses Azure.AzureKeyCredential with the AI Foundry (Cognitive Services) endpoint and API key from application configuration. Ensure environment variables are correctly provisioned in deployment.
Constructors
AzureClassifierBroker(IOptionsManager, ILoggerFactory) Constructor
Initializes the broker with configuration-driven Azure AI Foundry (Cognitive Services) client settings and logger.
public AzureClassifierBroker(arolariu.Backend.Common.Options.IOptionsManager optionsManager, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory);
Parameters
optionsManager arolariu.Backend.Common.Options.IOptionsManager
Abstraction supplying strongly typed application options (MUST NOT be null).
loggerFactory Microsoft.Extensions.Logging.ILoggerFactory
Logger factory for creating category-specific loggers (MUST NOT be null).
Exceptions
System.ArgumentNullException
Thrown when optionsManager or loggerFactory is null.
Remarks
Retrieves application options via optionsManager (endpoint + credentials context) and builds a single long-lived Azure.AI.OpenAI.AzureOpenAIClient instance connected to Azure AI Foundry model router.
Throws fast on null dependency to fail early in composition root.
Fields
AzureClassifierBroker.AllergenWikipediaUrls Field
Maps EU 14 major allergen names to their Wikipedia article URLs for user education.
private static readonly Dictionary<string,string> AllergenWikipediaUrls;
Field Value
System.Collections.Generic.Dictionary<System.String,System.String>
AzureClassifierBroker.ChatModelDeploymentName Field
The Azure AI Foundry model deployment name used for chat completions.
private const string ChatModelDeploymentName = "model-router";
Field Value
Remarks
This constant defines the model router deployment name used across all enrichment operations.
The "model-router" endpoint routes requests to appropriate models based on capabilities and availability.
Methods
AzureClassifierBroker.GenerateCountryRegion(Invoice) Method
Infers the country/region from product names and currency when OCR doesn't detect it.
internal System.Threading.Tasks.Task<string> GenerateCountryRegion(arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice invoice);
Parameters
invoice Invoice
Invoice whose product names and currency seed the geographic inference.
Returns
System.Threading.Tasks.Task<System.String>
ISO 3166-1 alpha-2 country code (e.g., "RO", "US") or empty string on failure.
Remarks
Purpose: Provides GPT fallback for empty CountryRegion field from Document Intelligence.
Prompt Strategy: Uses product names (local language hints) and currency code to infer ISO 3166-1 alpha-2 country code.
Failure Handling: Returns empty string on provider/content filter failure (best-effort enrichment).
Integration: Called from Batch 4 fallback in PerformGptAnalysisOnSingleInvoice(Invoice, AnalysisOptions) when invoice.CountryRegion is empty.
AzureClassifierBroker.GenerateInvoiceCategory(Invoice) Method
Infers an overall invoice category from the distribution of product categories via LLM classification.
internal System.Threading.Tasks.Task<arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.InvoiceCategory> GenerateInvoiceCategory(arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice invoice);
Parameters
invoice Invoice
Invoice supplying aggregated item categories.
Returns
System.Threading.Tasks.Task<InvoiceCategory>
Resolved invoice category or OTHER on failure.
Remarks
Validation: Response text is parsed against InvoiceCategory enum using Enum.TryParse.
Fallback: Returns OTHER for filter blocks, parse failures, or provider exceptions.
AzureClassifierBroker.GenerateInvoiceDescription(Invoice) Method
Generates a short (max ~7 words) invoice description derived from product names.
internal System.Threading.Tasks.Task<string> GenerateInvoiceDescription(arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice invoice);
Parameters
invoice Invoice
Invoice whose product list seeds the descriptor.
Returns
System.Threading.Tasks.Task<System.String>
Concise description or empty string if generation suppressed.
Remarks
Prompt Strategy: Employs instructive system messages with diverse examples to guide tone and brevity.
Safety: Content filter or provider failures yield an empty string (no exception propagation).
AzureClassifierBroker.GenerateInvoiceName(Invoice) Method
Generates a short humorous (3-word) invoice name using an LLM prompt over the invoice's product raw names.
internal System.Threading.Tasks.Task<string> GenerateInvoiceName(arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice invoice);
Parameters
invoice Invoice
Source invoice whose Items (raw product names) seed the naming prompt (MUST NOT be null).
Returns
System.Threading.Tasks.Task<System.String>
Generated three-word name or empty string on graceful degradation.
Remarks
Prompt Strategy: Uses structured system instructions with diverse few-shot examples to encourage creative, playful naming.
Failure Handling: Returns empty string when Azure OpenAI content filter triggers (ChatFinishReason.ContentFilter) or when a
ClientResultException is thrown (network / provider issue). Exceptions are intentionally swallowed to preserve best-effort enrichment flow.
Determinism: Output is non-deterministic; upstream layers decide whether to persist or regenerate later.
AzureClassifierBroker.GenerateInvoiceRecipes(Invoice) Method
Generates a collection of candidate recipes derived from invoice product composition with full enrichment metadata.
internal System.Threading.Tasks.Task<System.Collections.Generic.ICollection<arolariu.Backend.Domain.Invoices.DDD.ValueObjects.Recipe>> GenerateInvoiceRecipes(arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice invoice);
Parameters
invoice Invoice
Invoice whose product raw names seed the recipe brainstorming prompt.
Returns
System.Threading.Tasks.Task<System.Collections.Generic.ICollection<Recipe>>
Collection of enriched recipes (possibly empty).
Remarks
Parsing: Expects recipes separated by vertical bar (|), each recipe in format: "Name;Description;Duration;Complexity;Ingredient1,Ingredient2,..."
Enrichment: Populates Name, Description, ApproximateTotalDuration (minutes), Complexity (EASY/NORMAL/HARD), and Ingredients list for each recipe.
Limitations: No semantic deduplication or profanity filtering is applied (backlog: post-processing module).
Failure Handling: Returns empty list on provider/content filter failure. Malformed recipe entries are skipped.
AzureClassifierBroker.GenerateMerchantCategory(Merchant) Method
Classifies a merchant into a MerchantCategory enum via LLM category inference.
internal System.Threading.Tasks.Task<arolariu.Backend.Domain.Invoices.DDD.Entities.Merchants.MerchantCategory> GenerateMerchantCategory(arolariu.Backend.Domain.Invoices.DDD.Entities.Merchants.Merchant merchant);
Parameters
merchant Merchant
Merchant (name populated) to classify.
Returns
System.Threading.Tasks.Task<MerchantCategory>
Resolved merchant category or OTHER.
Remarks
Prompt Strategy: Uses structured category descriptions with examples of well-known retailers.
Parsing: Attempts enum parse; returns OTHER on failure.
Stability: Non-deterministic; repeated calls may alter classification for ambiguous brands.
AzureClassifierBroker.GenerateMerchantDescription(Merchant) Method
Produces a concise (target ≤ 50 chars) merchant description sourced only from merchant name context.
internal System.Threading.Tasks.Task<string> GenerateMerchantDescription(arolariu.Backend.Domain.Invoices.DDD.Entities.Merchants.Merchant merchant);
Parameters
merchant Merchant
Merchant whose name seeds the LLM prompt.
Returns
System.Threading.Tasks.Task<System.String>
Short description or empty string.
Remarks
Prompt Strategy: Uses structured system instructions with examples of well-known and regional merchants.
Use Case: UI subtitle / summary surfaces where full profile enrichment is not yet available.
Failure Handling: Returns empty string on filter or provider exception.
AzureClassifierBroker.GenerateProductAllergens(Product) Method
Attempts to infer potential allergens for a product based on raw and translated (generic) names with descriptions.
internal System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<arolariu.Backend.Domain.Invoices.DDD.ValueObjects.Allergen>> GenerateProductAllergens(arolariu.Backend.Domain.Invoices.DDD.ValueObjects.Products.Product product);
Parameters
product Product
Product context used to seed allergen inference.
Returns
System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<Allergen>>
Sequence of inferred allergens with descriptions (possibly empty).
Remarks
Prompt Strategy: Uses EU major allergen list as reference for consistent classification.
Output Parsing: Format "AllergenName:Description" separated by vertical bar (|).
Enrichment: Populates both allergen Name and Description for UI tooltips and accessibility.
Filtering: Automatically filters out 'N/A', 'NONE', and empty tokens.
Reliability: Heuristic / probabilistic; upstream layers should NOT treat output as medically authoritative.
AzureClassifierBroker.GenerateProductCategory(Product) Method
Classifies a single product into a ProductCategory via LLM prompt using raw + generic names.
internal System.Threading.Tasks.Task<arolariu.Backend.Domain.Invoices.DDD.ValueObjects.Products.ProductCategory> GenerateProductCategory(arolariu.Backend.Domain.Invoices.DDD.ValueObjects.Products.Product product);
Parameters
product Product
Product value object (raw and generic names populated).
Returns
System.Threading.Tasks.Task<ProductCategory>
Resolved category; OTHER / NOT_DEFINED fallback.
Remarks
Prompt Strategy: Uses comprehensive category descriptions with diverse examples for accurate classification.
Enum Resolution: Attempts direct parse; returns NOT_DEFINED when parse fails.
Fallback: Returns OTHER on filter / provider failure.
AzureClassifierBroker.GenerateReceiptType(Invoice) Method
Infers the receipt type from product context when OCR doesn't detect it.
internal System.Threading.Tasks.Task<string> GenerateReceiptType(arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice invoice);
Parameters
invoice Invoice
Invoice whose product raw names seed the classification prompt.
Returns
System.Threading.Tasks.Task<System.String>
Receipt type string (e.g., "Itemized", "Meal") or empty string on failure.
Remarks
Purpose: Provides GPT fallback for empty ReceiptType field from Document Intelligence.
Prompt Strategy: Uses product names to classify receipt into standard types (Itemized, Meal, Gas, Parking, Hotel, Other).
Failure Handling: Returns empty string on provider/content filter failure (best-effort enrichment).
Integration: Called from Batch 4 fallback in PerformGptAnalysisOnSingleInvoice(Invoice, AnalysisOptions) when invoice.ReceiptType is empty.
AzureClassifierBroker.PerformGptAnalysisOnSingleInvoice(Invoice, AnalysisOptions) Method
Executes the full enrichment sequence over a single invoice aggregate.
public System.Threading.Tasks.ValueTask<arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice> PerformGptAnalysisOnSingleInvoice(arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice invoice, arolariu.Backend.Domain.Invoices.DTOs.AnalysisOptions options);
Parameters
invoice Invoice
Invoice aggregate to enrich (MUST NOT be null; MUST have initialized Items collection).
options AnalysisOptions
Analysis directives (currently advisory; future selective gating).
Implements PerformGptAnalysisOnSingleInvoice(Invoice, AnalysisOptions)
Returns
System.Threading.Tasks.ValueTask<Invoice>
Mutated invoice aggregate (same instance) after best-effort enrichment.
Exceptions
System.ArgumentNullException
Thrown when invoice is null.
Remarks
Sequence (Parallelized): Batch 1: Name + Description (parallel) -> Batch 2: All products (category + allergens per product in parallel) -> Batch 3: Recipes + Invoice category (parallel).
Performance: Parallelizes 24+ sequential API calls into 3 parallel batches, reducing latency from ~40s to ~12-15s for typical 10-item invoices.
Graceful Degradation: Each discrete LLM call is isolated; on content filter or transient provider exception the step yields a default and processing continues. No aggregate rollback is attempted.
Mutation: Operates on the supplied invoice instance in-place (returns same reference) to avoid unnecessary allocations. Callers expecting immutability SHOULD clone prior to invocation.
Thread Safety: Each product task writes to its own product instance with no shared state mutation, making parallelization safe.
Options: Current implementation does not yet conditionally branch by options flags (backlog: selective enrichment to reduce token spend).
AzureClassifierBroker.PerformGptAnalysisOnSingleMerchant(Merchant) Method
Executes merchant enrichment sequence including category classification and description generation.
public System.Threading.Tasks.ValueTask<arolariu.Backend.Domain.Invoices.DDD.Entities.Merchants.Merchant> PerformGptAnalysisOnSingleMerchant(arolariu.Backend.Domain.Invoices.DDD.Entities.Merchants.Merchant merchant);
Parameters
merchant Merchant
Merchant entity to enrich (MUST NOT be null; MUST have Name populated).
Implements PerformGptAnalysisOnSingleMerchant(Merchant)
Returns
System.Threading.Tasks.ValueTask<Merchant>
Mutated merchant entity (same instance) with enriched category and description.
Exceptions
System.ArgumentNullException
Thrown when merchant is null.
Remarks
Sequence: Category classification -> Description generation.
Graceful Degradation: Failures yield default category (OTHER) and empty description.
Mutation: Operates on supplied merchant in-place (returns same reference).
Integration Point: Should be called from MerchantOrchestrationService during merchant
creation/update flows to ensure category is populated before persistence.
Interfaces
IClassifierBroker Interface
Broker contract for delegating invoice analysis / enrichment to Azure AI Foundry classification services (LLM).
public interface IClassifierBroker
Derived
↳ AzureClassifierBroker
Remarks
Layer Role (The Standard): A broker is a thin, test-isolated abstraction over an external dependency (here: Azure AI Foundry model router). It provides minimal translation (domain → API request / API response → domain) and surfaces provider-specific failures as dependency / dependency validation exceptions (wrapping raw SDK exceptions in the concrete implementation).
Responsibilities:
- Accept a domain Invoice plus AnalysisOptions directives.
- Invoke one or more model completions / chat interactions to enrich invoice fields (naming, categorization, tagging).
- Return the mutated (or enriched) invoice aggregate to upstream foundation / processing services.
Exclusions: No persistence, no multi-aggregate orchestration, no retry / circuit-breaker policy (handled by higher resilience layer or pipeline), no business validation beyond basic null / shape checks.
Determinism and Idempotency: LLM calls are non-deterministic; repeated invocations may yield different enrichment values. Upstream layers MUST decide caching / freeze policies if consistency is required.
Performance and Cost: Each enrichment call may consume tokens (billable). Implementations SHOULD batch prompts where feasible (backlog).
Methods
IClassifierBroker.PerformGptAnalysisOnSingleInvoice(Invoice, AnalysisOptions) Method
Performs GPT-backed enrichment of a single Invoice aggregate according to supplied analysis options.
System.Threading.Tasks.ValueTask<arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice> PerformGptAnalysisOnSingleInvoice(arolariu.Backend.Domain.Invoices.DDD.AggregatorRoots.Invoices.Invoice invoice, arolariu.Backend.Domain.Invoices.DTOs.AnalysisOptions options);
Parameters
invoice Invoice
Invoice aggregate to enrich (MUST NOT be null; MUST have initialized product collection).
options AnalysisOptions
Directive flags controlling which enrichment operations execute (MUST NOT be null; may be extended in future).
Returns
System.Threading.Tasks.ValueTask<Invoice>
Mutated (enriched) invoice instance (same reference or updated clone per implementation strategy).
Exceptions
System.ArgumentNullException
Thrown when invoice or options is null.
Remarks
Behavior: Applies sequential LLM prompts (name, description, product category + allergens, invoice recipes, invoice category). Each failure (e.g. content filter) degrades gracefully by supplying an empty / default value so downstream processing can continue.
Partial Failure Handling: Individual prompt failures DO NOT abort the pipeline; missing enrichment fields are left empty / default.
Thread Safety: Implementations SHOULD treat the underlying client as thread-safe (AzureOpenAIClient is internally safe for concurrent usage).
IClassifierBroker.PerformGptAnalysisOnSingleMerchant(Merchant) Method
Performs GPT-backed enrichment of a single Merchant entity, populating category and description.
System.Threading.Tasks.ValueTask<arolariu.Backend.Domain.Invoices.DDD.Entities.Merchants.Merchant> PerformGptAnalysisOnSingleMerchant(arolariu.Backend.Domain.Invoices.DDD.Entities.Merchants.Merchant merchant);
Parameters
merchant Merchant
Merchant entity to enrich (MUST NOT be null; MUST have Name populated).
Returns
System.Threading.Tasks.ValueTask<Merchant>
Mutated merchant instance with Category and enriched metadata.
Exceptions
System.ArgumentNullException
Thrown when merchant is null.
Remarks
Behavior: Applies LLM prompts to classify merchant category and generate a concise description.
Graceful Degradation: Prompt failures result in default values (OTHER category, empty description) without throwing.
Intended Usage: Should be called from merchant orchestration service after merchant creation/discovery.
Thread Safety: Thread-safe (AzureOpenAIClient is internally safe for concurrent usage).