| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const RULES = [ |
| { |
| id: 'TITLE_DEED_NUMBER', |
| label: 'Title deed number present (e.g. T12345/2023)', |
| check: (text) => /T\d{5,}\/20\d{2}/i.test(text), |
| }, |
| { |
| id: 'PROPERTY_DESCRIPTION', |
| label: 'Property description (ERF, FARM, or SECTIONAL TITLE)', |
| check: (text) => |
| /\b(ERF\s+\d+|FARM\s+\d+|SECTIONAL\s+TITLE)\b/i.test(text), |
| }, |
| { |
| id: 'VESTING_CLAUSE', |
| label: 'Vesting clause present (hereby transferred)', |
| check: (text) => /(hereby transferred|do hereby transfer)/i.test(text), |
| }, |
| { |
| id: 'EXECUTION_DATE', |
| label: 'Execution date present and within last 50 years', |
| check: (text) => { |
| const match = text.match( |
| /(\d{1,2}\s+(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{4})/i |
| ); |
| if (!match) return false; |
| const year = parseInt(match[0].match(/\d{4}/)[0], 10); |
| const currentYear = new Date().getFullYear(); |
| return year >= currentYear - 50 && year <= currentYear; |
| }, |
| }, |
| { |
| id: 'REGISTRAR_SIGNATURE', |
| label: 'Registrar of Deeds signature block', |
| check: (text) => /REGISTRAR\s+OF\s+DEEDS/i.test(text), |
| }, |
| { |
| id: 'MINIMUM_LENGTH', |
| label: 'Document text at least 500 characters', |
| check: (text) => text.length >= 500, |
| }, |
| ]; |
|
|
| |
| |
| |
| |
| |
| function validateDeed(text) { |
| if (!text || typeof text !== 'string') { |
| return { |
| valid: false, |
| failures: [{ id: 'INPUT', label: 'No text provided' }], |
| }; |
| } |
|
|
| const failures = []; |
| for (const rule of RULES) { |
| try { |
| if (!rule.check(text)) { |
| failures.push({ id: rule.id, label: rule.label }); |
| } |
| } catch (err) { |
| failures.push({ |
| id: rule.id, |
| label: `${rule.label} (error: ${err.message})`, |
| }); |
| } |
| } |
|
|
| return { |
| valid: failures.length === 0, |
| failures, |
| }; |
| } |
|
|
| module.exports = { validateDeed, RULES }; |