feat: close 3 more competitive gaps — recommendations engine, multi-madhab Faraid, draft will PDF

Closes 3 of the remaining 4 gaps from the competitive benchmark:

- Coverage Dashboard now has a rule-based recommendations engine
  (recommendations.js) surfacing plain-language next steps from data
  already in the app — exposed assets, missing Wassiyah, unverified
  assets, unlinked liabilities, missing insurance/Zakat setup,
  insufficient attestors, no agent assigned, no Waqf configured.

- Faraid Calculator gains a madhab selector (Shafi'i/Hanafi/Maliki/
  Hanbali) encoding the one well-documented divergence this engine's
  existing rules touch: whether radd extends to a sole-heir spouse
  (Hanafi: yes; Shafi'i/Maliki/Hanbali: no, residue unallocated).
  Ja'fari (Shia) is honestly gated as unsupported rather than silently
  computed with Sunni rules, since it's a structurally different
  classification system, not a parameter tweak. faraid.test.js grows
  from 14 to 17 cases covering the divergence and the gating.

- Wassiyah tab gains a 'Generate draft will document' button producing
  a formatted, statutory-style DRAFT will (declaration/revocation,
  executor appointment, bequest schedule, witness attestation blocks)
  via browser print-to-PDF — no new PDF dependency. Clearly watermarked
  'DRAFT — NOT EXECUTED, requires physical signing and witnessing.'
  Not a claim of legal validity, a lawyer-reviewable starting point.

The 4th gap (human-in-the-loop professional tier) remains open — out
of scope for a self-serve prevention tool. COMPETITIVE_BENCHMARK.md
updated to reflect closed/partially-closed status on each item.

Covered by e2e-gaps-round2.cjs (14/14) plus 3 new faraid.test.js cases.
Full regression: 226/226 across all suites.
This commit is contained in:
2026-08-14 12:15:54 +08:00
parent 972ad7ff68
commit 71aa4ae47c
10 changed files with 454 additions and 49 deletions
+88
View File
@@ -0,0 +1,88 @@
// Generates a print-ready DRAFT will document from Wassiyah data — a
// statutory-style layout (declaration/revocation, executor appointment,
// bequest schedule, witness attestation blocks) that a lawyer can review and
// finalize, not a document this app claims is legally executed on its own.
// Deliberately watermarked and disclaimed throughout: a will only takes
// legal effect once physically signed and witnessed per local law (in most
// jurisdictions, two witnesses present simultaneously, neither a
// beneficiary). No PDF library is added — the browser's own print-to-PDF
// keeps this dependency-free and print-perfect, matching the app's existing
// Blob-download pattern for execution packets.
function esc(s) {
return String(s || '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}
export function buildWillDocumentHtml({ testatorName, email, jurisdiction, witness1, witness2, executorName, bequests, estateTotal, cap, generatedDate }) {
const name = testatorName?.trim() || '[FULL LEGAL NAME NOT YET ENTERED]';
const exec = executorName?.trim() || '[EXECUTOR NOT YET NAMED]';
const w1 = witness1?.trim() || '[WITNESS 1 NOT YET NAMED]';
const w2 = witness2?.trim() || '[WITNESS 2 NOT YET NAMED]';
const bequestTotal = (bequests || []).reduce((s, b) => s + Number(b.value || 0), 0);
const bequestRows = (bequests || []).map((b, i) => `
<tr>
<td>${i + 1}</td>
<td>${esc(b.recipient)}${b.relation ? ` (${esc(b.relation)})` : ''}</td>
<td>${esc(b.description || '—')}</td>
<td class="num">${Number(b.value || 0).toLocaleString()}</td>
</tr>`).join('');
return `<!doctype html>
<html><head><meta charset="utf-8"><title>Draft Will — ${esc(name)}</title>
<style>
@page { margin: 2.2cm; }
body { font-family: Georgia, 'Times New Roman', serif; color: #111; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 24px; }
.watermark { text-align: center; background: #fff3f3; border: 2px dashed #c0392b; color: #c0392b; font-weight: bold; padding: 14px; margin-bottom: 24px; letter-spacing: 0.5px; text-transform: uppercase; font-family: Arial, sans-serif; font-size: 13px; }
h1 { text-align: center; font-size: 22px; letter-spacing: 1px; text-transform: uppercase; margin-bottom: 4px; }
.subtitle { text-align: center; font-size: 12px; color: #555; margin-bottom: 28px; font-family: Arial, sans-serif; }
h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid #999; padding-bottom: 4px; margin-top: 28px; }
p { font-size: 14px; text-align: justify; }
table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 13px; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background: #f4f4f4; font-family: Arial, sans-serif; font-size: 11px; text-transform: uppercase; }
td.num, th.num { text-align: right; }
.signature-block { margin-top: 40px; }
.sig-line { border-top: 1px solid #111; width: 300px; margin-top: 50px; padding-top: 4px; font-size: 12px; font-family: Arial, sans-serif; }
.witness-block { display: flex; gap: 40px; margin-top: 30px; }
.footer-note { margin-top: 40px; font-size: 11px; color: #777; font-family: Arial, sans-serif; border-top: 1px solid #ddd; padding-top: 12px; }
@media print { .no-print { display: none; } }
</style></head>
<body>
<div class="watermark">Draft — Not Executed. Requires physical signing and witnessing per local law to take legal effect.</div>
<h1>Last Will &amp; Testament</h1>
<p class="subtitle">(Islamic Wassiyah — limited to one-third of the net estate per Shariah)</p>
<h2>1. Declaration</h2>
<p>I, <strong>${esc(name)}</strong>${email ? ` (${esc(email)})` : ''}, being of sound mind, declare this to be my Will, and I hereby revoke all previous wills and testamentary dispositions made by me. This document expresses my Wassiyah — the portion of my estate I direct outside the fixed Faraid distribution — and does not purport to override any Faraid share owed to my heirs.</p>
<h2>2. Jurisdiction</h2>
<p>This Will is intended to take effect under the laws of <strong>${esc(jurisdiction || '[JURISDICTION NOT YET ENTERED]')}</strong>, in accordance with Shariah principles governing Wassiyah.</p>
<h2>3. Appointment of Executor</h2>
<p>I appoint <strong>${esc(exec)}</strong> as Executor of this Will, to administer my estate, settle my debts, and distribute both the Wassiyah bequests below and the remaining estate according to Faraid.</p>
<h2>4. One-Third Cap Acknowledgement</h2>
<p>My net estate is recorded at approximately <strong>${(estateTotal || 0).toLocaleString()}</strong>. The maximum permissible under Wassiyah (one-third) is approximately <strong>${(cap || 0).toLocaleString()}</strong>. The bequests below total <strong>${bequestTotal.toLocaleString()}</strong>${bequestTotal > (cap || 0) ? ' — THIS EXCEEDS THE ONE-THIRD CAP AND REQUIRES HEIR CONSENT TO STAND; REVIEW BEFORE EXECUTION.' : ', within the permitted limit.'} No bequest below is made to a Quranic fixed heir, who already receives a Faraid share.</p>
<h2>5. Schedule of Bequests</h2>
${bequestRows ? `<table><thead><tr><th>#</th><th>Recipient</th><th>Description</th><th class="num">Value</th></tr></thead><tbody>${bequestRows}</tbody></table>` : '<p><em>No bequests recorded.</em></p>'}
<p>The remainder of my estate, after debts, funeral expenses, and the bequests above, is to be distributed among my legal heirs according to Faraid, as determined at the time of my death.</p>
<h2>6. Witness Attestation</h2>
<p>This Will was signed by the testator in the presence of the two witnesses below, present at the same time, who then signed in the presence of the testator and each other. Neither witness should be a beneficiary under this Will.</p>
<div class="witness-block">
<div><div class="sig-line">Witness 1: ${esc(w1)}<br>Signature &amp; date</div></div>
<div><div class="sig-line">Witness 2: ${esc(w2)}<br>Signature &amp; date</div></div>
</div>
<div class="signature-block"><div class="sig-line">Testator signature &amp; date</div></div>
<div class="footer-note">
Generated by Nur Falah on ${esc(generatedDate)} from data entered in the Wassiyah tab. This is a DRAFT ONLY —
it has not been reviewed by a lawyer, has not been signed, and has no legal effect until properly executed
under the laws of the stated jurisdiction. Faraid shares shown elsewhere in this app are informational and
not a substitute for a qualified estate-planning consultation.
</div>
</body></html>`;
}