opzet_hypotheek_starter
Calculate mortgage setup for first-time homebuyers including total required amount, financing overview, and monthly payments based on income, property details, and financial obligations.
Instructions
Berekent de hypotheekopzet voor starters. Output: totaal benodigd bedrag, financieringsoverzicht en maandlast.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| inkomen_aanvrager | Yes | Bruto jaarinkomen hoofdaanvrager in euro's. | |
| geboortedatum_aanvrager | Yes | Geboortedatum hoofdaanvrager (YYYY-MM-DD). | |
| heeft_partner | Yes | Geeft aan of een partner mee leent. | |
| inkomen_partner | No | Optioneel partnerinkomen in euro's. | |
| geboortedatum_partner | No | Optionele geboortedatum partner (YYYY-MM-DD). | |
| verplichtingen_pm | No | Optionele maandelijkse verplichtingen in euro's. | |
| eigen_vermogen | No | Optioneel beschikbaar eigen geld in euro's. | |
| nieuwe_woning | Yes | Kerngegevens nieuwe woning (detailuitleg: hypotheek://v4/guide/opzet-intake). | |
| session_id | No | Optioneel sessie-ID vanuit n8n (voor logging). |
Implementation Reference
- src/index.ts:638-671 (handler)Main handler function: normalizes arguments, validates aanvrager data, constructs payload for Replit opzet API, calls the external API, logs success, and returns formatted response.async function handleOpzetStarter(request: any): Promise<ToolResponse> { const rawArgs = requireArguments<OpzetStarterArguments>(request); const normalizedArgs = normalizeOpzetAanvragerShape(rawArgs) as OpzetStarterArguments; const logger = createLogger(normalizedArgs.session_id); const aanvrager = requireOpzetAanvrager(normalizedArgs); validateOpzetAanvrager(aanvrager); enforceRateLimit(normalizedArgs.session_id); const payload: any = { aanvrager: mapOpzetAanvrager(aanvrager), nieuwe_woning: { waarde_woning: normalizedArgs.nieuwe_woning.waarde_woning, bedrag_verbouwen: normalizedArgs.nieuwe_woning.bedrag_verbouwen ?? 0, bedrag_verduurzamen: normalizedArgs.nieuwe_woning.bedrag_verduurzamen ?? 0, kosten_percentage: normalizedArgs.nieuwe_woning.kosten_percentage ?? 0.05, energielabel: normalizeEnergielabel(normalizedArgs.nieuwe_woning.energielabel || ''), }, }; if (normalizedArgs.session_id) { payload.session_id = normalizedArgs.session_id; } const apiClient = getApiClient(); const { data } = await apiClient.post( REPLIT_API_URL_OPZET, payload, { correlationId: normalizedArgs.session_id } ); logger.info('Toolcall succesvol', { tool: 'opzet_hypotheek_starter' }); return successResponse(formatResponse(data, "opzet_hypotheek_starter")); }
- src/index.ts:880-901 (registration)Tool registration in ListToolsRequestHandler: defines name, description, and input schema referencing aanvragerSchema and nieuweWoningSchema.{ name: "opzet_hypotheek_starter", description: `Opzet-berekening voor starters met een CONCRETE woning. Gebruik dit zodra de gebruiker een huis/koopprijs noemt en wil weten “kan ik deze woning kopen, hoe ziet de financiering eruit?”. Voor louter oriëntatie zonder woning blijft u bij de maximale-hypotheek tools. Dit is de standaardvariant; kies opzet_hypotheek_uitgebreid wanneer de gebruiker expliciet scenario’s/leningdelen wil tweaken.`, inputSchema: { type: "object", description: `Gebruik basisintake plus woninginfo; zie ${OPZET_GUIDE_URI} voor detailvelden en defaults.`, properties: { aanvrager: aanvragerSchema, nieuwe_woning: { ...nieuweWoningSchema, }, session_id: { type: "string", description: "Optioneel sessie-ID vanuit n8n (voor logging).", }, }, required: [ "aanvrager", "nieuwe_woning", ], }, },
- src/index.ts:210-254 (schema)Input schema definitions: aanvragerSchema (applicant data) and nieuweWoningSchema (new property details), used in tool inputSchema.const aanvragerSchema = { type: "object", description: 'Gegevens van de (hoofd)aanvrager. Vraag altijd: "Wat is uw leeftijd of geboortedatum?" en gebruik opgegeven leeftijden alleen intern.', properties: { ...baseIntakeProperties, eigen_vermogen: { type: "number", description: "Beschikbaar eigen geld in euro's (optioneel).", default: 0, }, }, required: [...baseIntakeRequired], }; const nieuweWoningSchema = { type: "object", description: `Kerngegevens nieuwe woning (detailuitleg: ${OPZET_GUIDE_URI}).`, properties: { waarde_woning: { type: "number", description: "Koopsom nieuwe woning in euro's.", }, bedrag_verbouwen: { type: "number", description: "Optionele verbouwingskosten in euro's.", default: 0, }, bedrag_verduurzamen: { type: "number", description: "Optionele verduurzamingskosten in euro's.", default: 0, }, kosten_percentage: { type: "number", description: "Optioneel kostenpercentage koper als decimaal.", default: 0.05, }, energielabel: { type: "string", description: "Optioneel energielabel van de woning.", enum: ["A++++ (met garantie)", "A++++", "A+++", "A++", "A+", "A", "B", "C", "D", "E", "F", "G"], }, }, required: ["waarde_woning"], };
- Normalization helper: converts legacy flat argument structure to nested 'aanvrager' object expected by the handler.export function normalizeOpzetAanvragerShape(args: any): any { if (!args || typeof args !== 'object') { return args; } if (args.aanvrager && typeof args.aanvrager === 'object') { return args; } const legacy = { ...args }; if ( typeof legacy.inkomen_aanvrager === 'number' && typeof legacy.geboortedatum_aanvrager === 'string' && typeof legacy.heeft_partner === 'boolean' ) { legacy.aanvrager = { inkomen_aanvrager: legacy.inkomen_aanvrager, geboortedatum_aanvrager: legacy.geboortedatum_aanvrager, heeft_partner: legacy.heeft_partner, inkomen_partner: legacy.inkomen_partner, geboortedatum_partner: legacy.geboortedatum_partner, verplichtingen_pm: legacy.verplichtingen_pm, eigen_vermogen: legacy.eigen_vermogen, }; } return legacy; }
- src/validation/schemas.ts:92-203 (helper)Validation helper called by validateOpzetAanvrager: validates base applicant data including income ranges, date formats, ages (18-75), partner completeness.export function validateBaseArguments(args: unknown): void { // Type check if (typeof args !== 'object' || args === null) { throw new ValidationError( ErrorCode.INVALID_INPUT, 'Arguments moet een object zijn', 'arguments' ); } const input = args as Record<string, unknown>; // Valideer verplichte velden if (typeof input.inkomen_aanvrager !== 'number') { throw new ValidationError( ErrorCode.INVALID_INPUT, 'inkomen_aanvrager moet een getal zijn', 'inkomen_aanvrager', input.inkomen_aanvrager ); } if (typeof input.geboortedatum_aanvrager !== 'string') { throw new ValidationError( ErrorCode.INVALID_INPUT, 'geboortedatum_aanvrager moet een string zijn', 'geboortedatum_aanvrager', input.geboortedatum_aanvrager ); } if (typeof input.heeft_partner !== 'boolean') { throw new ValidationError( ErrorCode.INVALID_INPUT, 'heeft_partner moet een boolean zijn', 'heeft_partner', input.heeft_partner ); } // Valideer inkomen range if (input.inkomen_aanvrager < ValidationConstraints.INKOMEN.MIN || input.inkomen_aanvrager > ValidationConstraints.INKOMEN.MAX) { throw new ValidationError( ErrorCode.INCOME_OUT_OF_RANGE, `Inkomen aanvrager moet tussen €${ValidationConstraints.INKOMEN.MIN} en €${ValidationConstraints.INKOMEN.MAX} liggen`, 'inkomen_aanvrager', input.inkomen_aanvrager ); } // Valideer leeftijd aanvrager validateAge(input.geboortedatum_aanvrager, 'aanvrager'); // Valideer partner gegevens indien heeft_partner = true if (input.heeft_partner) { if (input.inkomen_partner !== undefined) { if (typeof input.inkomen_partner !== 'number') { throw new ValidationError( ErrorCode.INVALID_INPUT, 'inkomen_partner moet een getal zijn', 'inkomen_partner', input.inkomen_partner ); } if (input.inkomen_partner < ValidationConstraints.INKOMEN.MIN || input.inkomen_partner > ValidationConstraints.INKOMEN.MAX) { throw new ValidationError( ErrorCode.INCOME_OUT_OF_RANGE, `Inkomen partner moet tussen €${ValidationConstraints.INKOMEN.MIN} en €${ValidationConstraints.INKOMEN.MAX} liggen`, 'inkomen_partner', input.inkomen_partner ); } } if (input.geboortedatum_partner) { if (typeof input.geboortedatum_partner !== 'string') { throw new ValidationError( ErrorCode.INVALID_INPUT, 'geboortedatum_partner moet een string zijn', 'geboortedatum_partner', input.geboortedatum_partner ); } validateAge(input.geboortedatum_partner, 'partner'); } } // Valideer verplichtingen if (input.verplichtingen_pm !== undefined) { if (typeof input.verplichtingen_pm !== 'number') { throw new ValidationError( ErrorCode.INVALID_INPUT, 'verplichtingen_pm moet een getal zijn', 'verplichtingen_pm', input.verplichtingen_pm ); } if (input.verplichtingen_pm < ValidationConstraints.VERPLICHTINGEN.MIN || input.verplichtingen_pm > ValidationConstraints.VERPLICHTINGEN.MAX) { throw new ValidationError( ErrorCode.INVALID_INPUT, `Verplichtingen moet tussen €${ValidationConstraints.VERPLICHTINGEN.MIN} en €${ValidationConstraints.VERPLICHTINGEN.MAX} liggen`, 'verplichtingen_pm', input.verplichtingen_pm ); } } }