UAD 3.6 Validation Rules

Comprehensive validation rules for UAD 3.6 forms.

Overview

DeepV-ADK provides extensive validation for UAD 3.6 forms to ensure data quality and compliance with industry standards.

Validation Levels

Level 1: Required Fields

Ensures all mandatory fields are present. ``typescript const level1Rules = { required: [ 'formType', 'version', 'subject.propertyAddress.street', 'subject.propertyAddress.city', 'subject.propertyAddress.state', 'subject.propertyAddress.zip', 'propertyInfo.yearBuilt', 'propertyInfo.grossLivingArea', 'propertyInfo.bedrooms', 'propertyInfo.bathrooms', 'site.area', 'site.zoningCompliance', 'comparables' // Minimum 3 ] }; `

Level 2: Data Type Validation

Validates field data types and formats.
`typescript const level2Rules = { types: { 'propertyInfo.yearBuilt': 'integer', 'propertyInfo.grossLivingArea': 'integer', 'propertyInfo.bathrooms': 'decimal', 'contract.salePrice': 'decimal', 'contract.date': 'date', 'subject.propertyAddress.zip': /^\d{5}(-\d{4})?$/ } }; `

Level 3: Range Validation

Ensures values fall within acceptable ranges.
`typescript const level3Rules = { ranges: { 'propertyInfo.yearBuilt': { min: 1800, max: () => new Date().getFullYear() + 2 }, 'propertyInfo.grossLivingArea': { min: 100, max: 20000 }, 'propertyInfo.effectiveAge': { min: 0, max: 200 }, 'contract.salePrice': { min: 0, max: 100000000 } } }; `

Level 4: Logical Validation

Validates relationships between fields.
`typescript const level4Rules = { logical: [ { name: 'Effective age vs actual age', validate: (data) => { const actualAge = new Date().getFullYear() - data.propertyInfo.yearBuilt; return data.propertyInfo.effectiveAge <= actualAge + 10; }, message: 'Effective age cannot significantly exceed actual age' }, { name: 'Finished basement area', validate: (data) => { return data.propertyInfo.basementFinished <= data.propertyInfo.basementArea; }, message: 'Finished basement cannot exceed total basement area' }, { name: 'Total rooms vs bedrooms', validate: (data) => { return data.propertyInfo.totalRooms >= data.propertyInfo.bedrooms; }, message: 'Total rooms must be at least equal to number of bedrooms' } ] }; `

Specific Field Validations

Address Validation

`typescript function validateAddress(address: PropertyAddress): ValidationResult { const errors = []; // Street address if (!address.street || address.street.length === 0) { errors.push('Street address is required'); } if (address.street && address.street.length > 100) { errors.push('Street address exceeds maximum length'); } // City if (!address.city || address.city.length === 0) { errors.push('City is required'); } // State const validStates = ['AL', 'AK', 'AZ', / ... full list ... /]; if (!validStates.includes(address.state)) { errors.push('Invalid state code'); } // ZIP code const zipPattern = /^\d{5}(-\d{4})?$/; if (!zipPattern.test(address.zip)) { errors.push('Invalid ZIP code format'); } return { valid: errors.length === 0, errors }; } `

Property Information Validation

`typescript function validatePropertyInfo(info: PropertyInfo): ValidationResult { const errors = []; const currentYear = new Date().getFullYear(); // Year built if (info.yearBuilt < 1800 || info.yearBuilt > currentYear + 2) { errors.push(Year built must be between 1800 and ${currentYear + 2}); } // GLA if (info.grossLivingArea < 100 || info.grossLivingArea > 20000) { errors.push('Gross living area must be between 100 and 20,000 sq ft'); } // Bedrooms if (info.bedrooms < 0 || info.bedrooms > 20) { errors.push('Number of bedrooms must be between 0 and 20'); } // Bathrooms format const validBathIncrements = [0, 0.25, 0.5, 0.75, 1.0]; const bathDecimal = info.bathrooms % 1; const isValidBath = validBathIncrements.some( inc => Math.abs(bathDecimal - inc) < 0.01 ); if (!isValidBath) { errors.push('Bathrooms must be in increments of 0.25'); } // Total rooms if (info.totalRooms < info.bedrooms) { errors.push('Total rooms cannot be less than bedrooms'); } return { valid: errors.length === 0, errors }; } `

Comparable Sales Validation

`typescript function validateComparables( comparables: Comparable[], subject: Property ): ValidationResult { const errors = []; // Minimum number if (comparables.length < 3) { errors.push('At least 3 comparables are required'); } // Maximum number if (comparables.length > 6) { errors.push('Maximum 6 comparables allowed'); } comparables.forEach((comp, index) => { const prefix = Comparable ${index + 1}; // Sale date const saleDate = new Date(comp.dateOfSale); const daysSinceSale = daysBetween(saleDate, new Date()); if (daysSinceSale > 365) { errors.push(${prefix}: Sale is older than 12 months); } // Sale price if (comp.salePrice <= 0) { errors.push(${prefix}: Sale price must be greater than 0); } // Proximity if (!comp.proximityToSubject) { errors.push(${prefix}: Proximity to subject is required); } // Data source if (!comp.dataSource) { errors.push(${prefix}: Data source is required); } // Verification if (!comp.verification) { errors.push(${prefix}: Verification source is required); } // GLA comparison (should be within 30% of subject) const glaRatio = comp.gla / subject.grossLivingArea; if (glaRatio < 0.7 || glaRatio > 1.3) { errors.push(${prefix}: GLA differs from subject by more than 30%); } // Adjustment limits const netAdjustment = Math.abs(comp.adjustments.net); const netAdjustmentPercent = (netAdjustment / comp.salePrice) * 100; if (netAdjustmentPercent > 25) { errors.push(${prefix}: Net adjustment exceeds 25% of sale price); } const grossAdjustment = comp.adjustments.gross; const grossAdjustmentPercent = (grossAdjustment / comp.salePrice) * 100; if (grossAdjustmentPercent > 35) { errors.push(${prefix}: Gross adjustment exceeds 35% of sale price); } }); return { valid: errors.length === 0, errors }; } `

Adjustment Validation

Adjustment Limits

`typescript const adjustmentLimits = { netAdjustment: { percentage: 25, // Max 25% of sale price absolute: 50000 // Max $50k in some cases }, grossAdjustment: { percentage: 35, // Max 35% of sale price absolute: 75000 // Max $75k in some cases }, individualAdjustment: { percentage: 10 // Max 10% for any single adjustment } }; function validateAdjustments( comparable: Comparable, limits: AdjustmentLimits ): ValidationResult { const errors = []; // Net adjustment const netAdjPercent = Math.abs(comparable.adjustments.net) / comparable.salePrice * 100; if (netAdjPercent > limits.netAdjustment.percentage) { errors.push( Net adjustment (${netAdjPercent.toFixed(1)}%) exceeds + ${limits.netAdjustment.percentage}% limit ); } // Gross adjustment const grossAdjPercent = comparable.adjustments.gross / comparable.salePrice * 100; if (grossAdjPercent > limits.grossAdjustment.percentage) { errors.push( Gross adjustment (${grossAdjPercent.toFixed(1)}%) exceeds + ${limits.grossAdjustment.percentage}% limit ); } return { valid: errors.length === 0, errors }; } `

Custom Validation Rules

Market-Specific Rules

`typescript class MarketValidator { constructor(private marketRules: MarketRules) {} validate(data: UADForm): ValidationResult { const errors = []; // Comparable age limit (may vary by market) const maxComparableAge = this.marketRules.maxComparableAge || 365; data.comparables.forEach((comp, i) => { const age = daysSince(comp.dateOfSale); if (age > maxComparableAge) { errors.push( Comparable ${i + 1} exceeds ${maxComparableAge} day age limit ); } }); // Market-specific proximity requirements const maxDistance = this.marketRules.maxProximity || 1.0; // miles data.comparables.forEach((comp, i) => { if (comp.distance > maxDistance) { errors.push( Comparable ${i + 1} exceeds ${maxDistance} mile proximity limit ); } }); return { valid: errors.length === 0, errors }; } } `

Property-Type Specific Rules

`typescript function validateByPropertyType( data: UADForm, propertyType: string ): ValidationResult { const errors = []; switch (propertyType) { case 'Condominium': // Require HOA information if (!data.hoa?.fee) { errors.push('HOA fee is required for condominiums'); } if (!data.project?.name) { errors.push('Project name is required for condominiums'); } break; case 'Small Residential Income': // Require income/expense data if (!data.income?.grossRent) { errors.push('Gross rent is required for income properties'); } if (!data.expenses?.total) { errors.push('Operating expenses required for income properties'); } break; case 'Single Family': // Standard validation only break; } return { valid: errors.length === 0, errors }; } `

Validation API

Using the Validation API

`typescript import { UADValidator } from '@deepv/adk-sdk'; const validator = new UADValidator({ level: 4, // Validation level (1-4) strict: true, // Strict mode marketRules: { maxComparableAge: 365, maxProximity: 1.0 } }); const result = validator.validate(uadData); if (!result.valid) { console.error('Validation errors:'); result.errors.forEach(error => { console.error(- ${error.field}: ${error.message}); }); } `

Custom Validators

`typescript const validator = new UADValidator(); // Add custom validation rule validator.addRule({ name: 'custom-gla-check', validate: (data) => { const avgComparableGLA = data.comparables.reduce((sum, c) => sum + c.gla, 0) / data.comparables.length; const variance = Math.abs( data.propertyInfo.grossLivingArea - avgComparableGLA ); return variance < avgComparableGLA * 0.2; }, message: 'Subject GLA varies more than 20% from comparable average' }); const result = validator.validate(uadData); `

Error Handling

Error Response Format

`typescript interface ValidationResult { valid: boolean; errors: ValidationError[]; warnings: ValidationWarning[]; } interface ValidationError { field: string; message: string; code: string; severity: 'error' | 'warning'; } `

Example Error Response

`json { "valid": false, "errors": [ { "field": "propertyInfo.yearBuilt", "message": "Year built must be between 1800 and 2027", "code": "INVALID_RANGE", "severity": "error" }, { "field": "comparables[0].dateOfSale", "message": "Sale is older than 12 months", "code": "COMPARABLE_TOO_OLD", "severity": "warning" } ], "warnings": [ { "field": "propertyInfo.effectiveAge", "message": "Effective age is significantly lower than actual age", "code": "EFFECTIVE_AGE_LOW", "severity": "warning" } ] } `

Best Practices

Pre-Validation

Validate data before submission:
`typescript async function submitAppraisal(data: UADForm) { // Validate first const validation = validator.validate(data); if (!validation.valid) { throw new ValidationError( 'UAD form validation failed', validation.errors ); } // Submit if valid return await client.submitUAD(data); } `

Incremental Validation

Validate as user inputs data:
`typescript function validateField(fieldName: string, value: any): ValidationResult { const fieldRules = validator.getFieldRules(fieldName); return validator.validateValue(value, fieldRules); } // In form handler onFieldChange('propertyInfo.yearBuilt', 1995); const result = validateField('propertyInfo.yearBuilt', 1995); if (!result.valid) { showFieldError(result.errors[0].message); } ``

Next Steps

Found an issue? Help us improve this page.