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.