Skip to main content

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​

System.String

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.

AzureClassifierBroker.logger Field​

private readonly ILogger<AzureClassifierBroker> logger;

Field Value​

Microsoft.Extensions.Logging.ILogger<AzureClassifierBroker>

AzureClassifierBroker.openAIClient Field​

private readonly AzureOpenAIClient openAIClient;

Field Value​

Azure.AI.OpenAI.AzureOpenAIClient

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).

// was this page useful?