ND.FW.DataValidation 1.0.1

dotnet add package ND.FW.DataValidation --version 1.0.1
                    
NuGet\Install-Package ND.FW.DataValidation -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ND.FW.DataValidation" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ND.FW.DataValidation" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="ND.FW.DataValidation" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ND.FW.DataValidation --version 1.0.1
                    
#r "nuget: ND.FW.DataValidation, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ND.FW.DataValidation@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ND.FW.DataValidation&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=ND.FW.DataValidation&version=1.0.1
                    
Install as a Cake Tool

ND.FW.DataValidation

A configurable data-validation and metadata-derivation nuget, built on top of ND.FW.RulesEngine. Everything is driven by the same Rule JSON shape — no C# changes needed to add a validation rule, switch rule providers, or add a metadata-derivation stage.

Install

dotnet add package ND.FW.DataValidation

The two halves

Half Entry point Input Output
Validation IDataValidationService.ValidateAsync one record (IDictionary<string,object?>) pass/fail per rule
Metadata derivation IMetadataDerivationService.DeriveAsync many AttributeCandidates (multi-page, multi-source) one canonical record + evidence + conflicts

Both read rules through the same IRuleRepository and the same Rule JSON shape — a rule's RuleExpressionType decides which half consumes it. Validation types (Required, Regex, NumberRange, DateValidation, Conditional, Lookup, Triangulation, FuzzyMatch, Enrichment, Standardization, Normalization) map to an ND.FW.RulesEngine workflow exactly as before. Metadata-derivation types (AttributeMapping, CandidateGrouping, MultiPageConsolidation, ConfidenceAggregation, CanonicalSelection, MultiValueConsolidation, ConflictDetection, CrossFieldValidation) drive the consolidation pipeline instead. You can mix both kinds of rules in the same rule set returned by a provider — each service simply filters for the types it understands.

Quick start

builder.Services.AddDataValidation(builder.Configuration, options =>
{
    // register any product-specific operators alongside the built-in base set
    options.CustomOperatorTypes.Add(typeof(MyProduct.Operators));
});
// appsettings.json
{
  "DataValidation": {
    "RuleRepository": {
      "UseDatabaseProvider": true,
      "UseLocalJsonProvider": true,
      "Mode": "Additive"
    },
    "Database": {
      "Schema": "dbo",
      "RuleDetailsSpName": "sp_Get_Rule_Details",
      "RuleJsonColumnName": "Rule_Definition_JSON",
      "ParameterMap": {
        "FileTaxonomyId": "@p_File_Taxonomy_Id",
        "DocTaxonomyId": "@p_Doc_Taxonomy_Id",
        "FileDocTaxonomyId": "@p_File_Doc_Taxonomy_Id",
        "FieldId": "@p_File_Taxonomy_X_Doc_Taxonomy_X_Field_Id",
        "RuleEventStage": "@p_Rule_Event_Stage"
      }
    },
    "LocalJson": {
      "RootDirectory": "RuleSets",
      "FileNameTemplate": "{DocTaxonomyId}.json"
    }
  }
}

Validation

var criteria = RuleQueryCriteria.FromKeys(
    ("FileTaxonomyId", 12),
    ("DocTaxonomyId", 4),
    ("FieldId", 101));

var input = new Dictionary<string, object?> { ["phone"] = "+919876543210", ["pan"] = "ABCDE1234F" };

var result = await dataValidationService.ValidateAsync(criteria, input);

foreach (var r in result.RuleResults)
    Console.WriteLine($"{r.RuleName}: {(r.IsSuccess ? "PASS" : $"FAIL - {r.ErrorMessage}")}");

Metadata derivation

var candidates = new List<AttributeCandidate>
{
    new() { AttributeName = "PT_NAME_1", Value = "John A Smith", Page = 1, Position = 0, ValueStrength = 0.81, Source = "ocr" },
    new() { AttributeName = "patient full name", Value = "John Smith", Page = 3, Position = 2, ValueStrength = 0.93, Source = "model-x" },
    new() { AttributeName = "PhoneNumber", Value = "555-0100", Page = 1, ValueStrength = 0.7 },
    new() { AttributeName = "PhoneNumber", Value = "555-0199", Page = 2, ValueStrength = 0.6 },
};

var result = await metadataDerivationService.DeriveAsync(criteria, candidates);

var patientName = result.Attributes["PatientName"]; // canonical value + evidence + confidence
foreach (var conflict in result.Conflicts)
    Console.WriteLine($"Conflict on {conflict.AttributeName}: {conflict.ConflictingValues.Count} candidate values");

Rule providers — multiple, at the same time

IRuleRepository has two built-in implementations:

  • DatabaseRuleRepository — calls a stored procedure. SP name, schema, and the mapping from RuleQueryCriteria.Keys to SP parameter names are all configuration (DatabaseRuleProviderOptions), not hardcoded — so this provider works for any product's own key shape, not just taxonomy IDs.
  • LocalJsonRuleRepository — reads rule JSON from local files (or inline configuration). Accepts a single Rule object, a Rule array, or a { "Rules": [...] } wrapper per file.

When both are enabled, CompositeRuleRepository merges their results:

  • Additive (default) — rules from every enabled provider are combined, de-duplicated by RuleName (earliest-registered provider wins on a collision).
  • Fallback — providers are tried in order; the first one that returns any rules wins and the rest are skipped.

Add a third provider (e.g. a remote config service) by implementing IRuleRepository and registering it — CompositeRuleRepository accepts any IEnumerable<IRuleRepository>.

Metadata-derivation pipeline

Stages run in a fixed logical order, but only the stages whose RuleExpressionType is present in the resolved rule set actually run — a rule set with just CandidateGrouping + CanonicalSelection rules skips multi-page consolidation and cross-field validation entirely.

AttributeMapping → CandidateGrouping → MultiPageConsolidation →
ConfidenceAggregation → CanonicalSelection → MultiValueConsolidation →
ConflictDetection → CrossFieldValidation
Stage RuleExpressionType What it does
Attribute name mapping AttributeMapping Renames raw extracted names onto one canonical name, via AttributeNameMap.
Candidate grouping CandidateGrouping Groups candidates by (mapped) attribute name, optionally narrowed by GroupByKeys.
Multi-page consolidation MultiPageConsolidation Orders each group by page/position per PageOrderStrategy.
Confidence aggregation ConfidenceAggregation Scores each candidate from key/position/value-strength via ConfidenceWeights.
Canonical value selection CanonicalSelection Picks the winning value per attribute (or ranks candidates for multi-value attributes), via ConflictResolutionStrategy.
Multi-value consolidation MultiValueConsolidation For attributes with IsMultiValue: true, keeps a de-duplicated, ranked list capped at MaxValues.
Conflict detection ConflictDetection Flags attributes whose top candidates differ by less than ConflictThreshold.
Cross-field validation CrossFieldValidation Runs an ND.FW.RulesEngine expression (Condition) against the canonical record — reuses the same Operators.* as ordinary validation.

Note: ToCanonicalDictionary() exposes multi-value attributes (IsMultiValue: true) as a List<object?>, not a scalar. A CrossFieldValidation expression referencing such an attribute must treat it as a list (e.g. ((List<object?>)input1["PhoneNumber"]).Count > 0) rather than comparing it directly — ND.FW.RulesEngine passes the value through unchanged and does not coerce it to a string or number.

Every canonical attribute retains full evidence (EvidenceRef: value, page, position, source location, source, and computed weight) for every candidate that contributed to it, whether it won or lost — satisfying audit/traceability needs without any extra configuration.

Extending with a custom stage

Metadata-derivation stages are just another named plugin point, exactly like ND.FW.RulesEngine's INdContextPlugin:

public sealed class MyCustomStage : IMetadataDerivationStage
{
    public string RuleExpressionType => "MyCustomStageType";
    public Task ExecuteAsync(Rule rule, MetadataDerivationContext context, CancellationToken ct) { ... }
}

services.AddDataValidation(configuration, options =>
{
    options.MetadataDerivationStageTypes.Add(typeof(MyCustomStage));
});

Rule JSON compatibility

Existing rule rows/files are not restructured. Every new capability is added by:

  1. New RuleExpressionType string values (data, not schema).
  2. A small number of new, optional fields on RuleExpressionAttributeNameMap, GroupByKeys, ConfidenceWeights, ConflictResolutionStrategy, ConflictThreshold, IsMultiValue, MaxValues, PageOrderStrategy, CanonicalFields. All nullable; absent in existing rows and simply unused by validation rule types.
  3. Reuse of one existing field, Condition, as the raw cross-field expression string for CrossFieldValidation rules (this field already existed on RuleExpression and was previously unused by any mapped type).

No existing field was renamed, retyped, or removed.

Packaging note

ND.FW.DataValidation references ND.FW.RDBM / Microsoft.Data.SqlClient directly so DatabaseRuleRepository is available out of the box, matching how DVS already depends on ND.FW.RDBM. If a consumer only ever wants the local-JSON provider (no database dependency at all — e.g. an edge/offline scenario), the cleaner long-term shape is to split this into ND.FW.DataValidation.Core (abstractions, local JSON, metadata derivation) + ND.FW.DataValidation.SqlServer (the database provider) so the SQL dependency is opt-in per package reference rather than always-transitive. That split is a bigger packaging change than this version takes on; call it out if a consumer without SQL Server ever needs this nuget.

License

Internal use.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 0 8/12/2026
1.0.0 0 8/12/2026