{ "kind": "open-flow-workflow", "version": 1, "name": "Exact-Match Quote Guard", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_20dbd36d0333d0c2": { "name": "Receive quote extraction", "kind": "webhook", "bodyFields": [ { "jsonSchema": {}, "nullable": true, "handle": "request_id" }, { "jsonSchema": {}, "nullable": true, "handle": "source_description" }, { "jsonSchema": {}, "nullable": true, "handle": "labor_hours" }, { "jsonSchema": {}, "nullable": true, "handle": "travel_required" }, { "jsonSchema": {}, "nullable": true, "handle": "extracted_items" } ], "method": "POST", "options": { "responseBody": { "kind": "workflow-outputs", "output": "body", "timeoutMs": 30000 }, "responseHeaders": { "Cache-Control": "no-store" }, "responseStatusCodeOutput": "statusCode" } }, "n_b88f42e634550ea9": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_20dbd36d0333d0c2", "output": "body" } ] } }, "name": "Validate untrusted extraction", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "data" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "valid" } ], "name": "Validate untrusted extraction", "moduleId": "n_38dd4a2865c1c316", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_a8beb479afde5780": { "inputs": {}, "name": "Input is valid", "kind": "condition", "matchMode": "first", "cases": [ { "output": "valid", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_b88f42e634550ea9", "output": "valid" } }, "operator": "isTrue" } ] } ] } ] }, "n_6a22d434e342c22f": { "inputs": { "data": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b88f42e634550ea9", "output": "data" } ] } }, "name": "Match catalog and calculate", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "data" } ], "outputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" } ], "name": "Match catalog and calculate", "moduleId": "n_2ff082df99e70dba", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_2863ad0de2749135": { "inputs": { "data": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b88f42e634550ea9", "output": "data" } ] } }, "name": "Build rejected response", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "data" } ], "outputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" } ], "name": "Build rejected response", "moduleId": "n_f64e67125cfb232f", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_6368f8c49d75f11b": { "name": "Quote assessment response", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" } ], "outputMappings": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6a22d434e342c22f", "output": "body" }, { "kind": "node", "nodeId": "n_2863ad0de2749135", "output": "body" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6a22d434e342c22f", "output": "statusCode" }, { "kind": "node", "nodeId": "n_2863ad0de2749135", "output": "statusCode" } ] } } } }, "edges": [ { "source": "n_20dbd36d0333d0c2", "target": "n_b88f42e634550ea9" }, { "source": "n_b88f42e634550ea9", "target": "n_a8beb479afde5780" }, { "source": "n_a8beb479afde5780", "target": "n_6a22d434e342c22f", "sourceHandle": "valid" }, { "source": "n_a8beb479afde5780", "target": "n_2863ad0de2749135", "sourceHandle": "otherwise" }, { "source": "n_6a22d434e342c22f", "target": "n_6368f8c49d75f11b" }, { "source": "n_2863ad0de2749135", "target": "n_6368f8c49d75f11b" } ] } }, "modules": { "n_38dd4a2865c1c316": { "name": "Validate untrusted extraction", "imports": [], "source": "function validate(input) {\n\nconst requestId = typeof input.request_id === 'string' ? input.request_id : '';\nconst sourceDescription = typeof input.source_description === 'string' ? input.source_description : '';\nconst laborHours = input.labor_hours;\nconst travelRequired = input.travel_required;\nconst extractedItems = Array.isArray(input.extracted_items) ? input.extracted_items : [];\nconst errors = [];\nif (!/^quote-[a-z0-9-]{3,40}$/i.test(requestId)) errors.push('request_id');\nif (!sourceDescription || sourceDescription.length > 1000) errors.push('source_description');\nconst laborHundredths = typeof laborHours === 'number' ? laborHours * 100 : Number.NaN;\nif (typeof laborHours !== 'number' || !Number.isFinite(laborHours) || laborHours < 0 || laborHours > 24 || Math.abs(Math.round(laborHundredths) - laborHundredths) > 1e-9) errors.push('labor_hours');\nif (typeof travelRequired !== 'boolean') errors.push('travel_required');\nif (extractedItems.length < 1 || extractedItems.length > 10) errors.push('extracted_items');\nconst normalizedItems = extractedItems.map((item, index) => {\n const reference = typeof item?.reference === 'string' ? item.reference : '';\n const quantity = item?.quantity;\n if (!/^SYN-[A-Z]+-[0-9]{3}$/.test(reference)) errors.push(`extracted_items[${index}].reference`);\n if (!Number.isInteger(quantity) || quantity < 1 || quantity > 20) errors.push(`extracted_items[${index}].quantity`);\n if (item?.claimed_unit_price_chf !== undefined && (typeof item.claimed_unit_price_chf !== 'number' || !Number.isFinite(item.claimed_unit_price_chf))) errors.push(`extracted_items[${index}].claimed_unit_price_chf`);\n return { reference, quantity: quantity ?? null, claimed_unit_price_chf: item?.claimed_unit_price_chf ?? null };\n});\nreturn [{ json: {\n ok: errors.length === 0,\n status: errors.length === 0 ? 'VALIDATED' : 'REJECTED',\n request_id: requestId || null,\n invalid_fields: [...new Set(errors)],\n source_description: sourceDescription,\n labor_hours: laborHours ?? null,\n travel_required: travelRequired ?? null,\n extracted_items: normalizedItems,\n extraction_boundary: 'untrusted_synthetic_fixture'\n} }];\n}\n\nexport default function validateExtraction({body}) {\n const data=validate(body)[0].json;\n return {data,valid:data.ok};\n}\n" }, "n_2ff082df99e70dba": { "name": "Match catalog and calculate", "imports": [], "source": "// Synthetic example catalog and rates. Replace with trusted, approved business configuration.\nfunction calculate(data) {\nconst catalog = Object.freeze({\n 'SYN-VALVE-100': Object.freeze({ name: 'Synthetic isolation valve 100', unit_price_cents: 12000 }),\n 'SYN-PIPE-200': Object.freeze({ name: 'Synthetic pipe section 200', unit_price_cents: 8550 }),\n 'SYN-SEAL-300': Object.freeze({ name: 'Synthetic seal kit 300', unit_price_cents: 3240 })\n});\nconst centsToChf = (value) => Number((value / 100).toFixed(2));\nconst matchedItems = [];\nconst missingReferences = [];\nlet ignoredClaimedPrices = false;\nfor (const item of data.extracted_items) {\n const catalogItem = catalog[item.reference];\n if (!catalogItem) {\n missingReferences.push(item.reference);\n continue;\n }\n if (item.claimed_unit_price_chf !== null && Math.round(item.claimed_unit_price_chf * 100) !== catalogItem.unit_price_cents) ignoredClaimedPrices = true;\n const lineTotalCents = catalogItem.unit_price_cents * item.quantity;\n matchedItems.push({\n reference: item.reference,\n name: catalogItem.name,\n quantity: item.quantity,\n unit_price_chf: centsToChf(catalogItem.unit_price_cents),\n line_total_chf: centsToChf(lineTotalCents),\n line_total_cents: lineTotalCents\n });\n}\nif (missingReferences.length > 0) {\n return [{ json: {\n ok: false,\n status: 'NEEDS_REVIEW',\n request_id: data.request_id,\n exact_match: false,\n matched_items: matchedItems.map(({ line_total_cents, ...item }) => item),\n missing_references: missingReferences,\n ignored_claimed_prices: ignoredClaimedPrices,\n totals: null,\n reason: 'exact_catalog_match_required'\n } }];\n}\nconst materialsCents = matchedItems.reduce((sum, item) => sum + item.line_total_cents, 0);\nconst marginCents = Math.round(materialsCents * 0.15);\nconst laborCents = Math.round(data.labor_hours * 9500);\nconst travelCents = data.travel_required ? 4500 : 0;\nconst netCents = materialsCents + marginCents + laborCents + travelCents;\nconst vatCents = Math.round(netCents * 0.081);\nconst totalCents = netCents + vatCents;\nreturn [{ json: {\n ok: true,\n status: 'QUOTABLE',\n request_id: data.request_id,\n exact_match: true,\n matched_items: matchedItems.map(({ line_total_cents, ...item }) => item),\n missing_references: [],\n ignored_claimed_prices: ignoredClaimedPrices,\n rates: { material_margin_percent: 15, labor_hour_chf: 95, travel_flat_chf: 45, vat_percent: 8.1 },\n totals: {\n materials_chf: centsToChf(materialsCents),\n material_margin_chf: centsToChf(marginCents),\n labor_chf: centsToChf(laborCents),\n travel_chf: centsToChf(travelCents),\n net_chf: centsToChf(netCents),\n vat_chf: centsToChf(vatCents),\n total_chf: centsToChf(totalCents)\n }\n} }];\n}\n\nexport default function calculateQuote({data}) {\n const body=calculate(data)[0].json;\n return {body,statusCode:body.ok?200:422};\n}\n" }, "n_f64e67125cfb232f": { "name": "Build rejected response", "imports": [], "source": "export default function rejectExtraction({ data }) {\n return { statusCode: 400, body: {\n ok: false,\n status: 'REJECTED',\n request_id: data.request_id,\n invalid_fields: data.invalid_fields,\n } };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }