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
+50
View File
@@ -0,0 +1,50 @@
// Rule-based guidance engine — turns the app's own data into plain-language
// next steps, the way Legacy Logic's "personalized report" and Rafiq's
// contextual coaching do. Deliberately rule-based against data already
// captured in this app, not a call to an external model: every recommendation
// here traces to a concrete fact (an unverified asset, a missing attestor,
// zero policies logged), not a generated guess.
export function buildRecommendations(data) {
const {
exposedTotal = 0, exposedCount = 0, unverifiedCount = 0, wassiyahCount = 0, estateTotal = 0,
insuranceCount = 0, zakatConfigured = false, hasCashOrGold = false, unlinkedLiabilityCount = 0,
confirmedAttestorCount = 0, hasAgent = false, waqfConfigured = false
} = data;
const items = [];
if (exposedTotal > 0) {
items.push({
severity: 'high',
text: `${exposedCount} asset${exposedCount === 1 ? ' is' : 's are'} still exposed to the slow Faraid/probate queue (${exposedTotal.toLocaleString()} total). Cover the highest-value one first — Hibah, Waqf, or a Nomination.`,
tab: 'Coverage'
});
}
if (estateTotal > 0 && wassiyahCount === 0) {
items.push({ severity: 'medium', text: 'No Wassiyah bequests recorded yet. Up to a third of your estate can go to non-heirs or charity — worth setting up even alongside Faraid.', tab: 'Wassiyah' });
}
if (unverifiedCount > 0) {
items.push({ severity: 'medium', text: `${unverifiedCount} asset${unverifiedCount === 1 ? '' : 's'} still unverified — attach proof (title, grant, cover note) and get it confirmed so there's no dispute later.`, tab: 'Assets' });
}
if (unlinkedLiabilityCount > 0) {
items.push({ severity: 'low', text: `${unlinkedLiabilityCount} liabilit${unlinkedLiabilityCount === 1 ? 'y is' : 'ies are'} not linked to the asset securing it — link it so the net estate calculation stays accurate.`, tab: 'Assets' });
}
if (insuranceCount === 0) {
items.push({ severity: 'low', text: 'No life or Takaful policies logged. If you have any, add them — payouts usually go straight to a named beneficiary, outside Faraid entirely.', tab: 'Insurance' });
}
if (hasCashOrGold && !zakatConfigured) {
items.push({ severity: 'low', text: 'You have cash or gold logged but no Zakat calculation set up. Check whether you\'re above Nisab this year.', tab: 'Zakat' });
}
if (confirmedAttestorCount < 2) {
items.push({ severity: 'medium', text: 'Fewer than 2 attestors are confirmed on your death trigger. Without them, your mutawalli/agent can\'t fire the trigger when the time comes.', tab: 'Trigger' });
}
if (!hasAgent) {
items.push({ severity: 'low', text: 'No estate agent/mutawalli assigned to this family yet. Without one, only the owner can execute triggers and confirm assets.', tab: 'Family' });
}
if (estateTotal > 0 && !waqfConfigured) {
items.push({ severity: 'low', text: 'No Waqf designation set up. If any part of your estate is meant as a lasting charitable endowment, this is where to set it aside.', tab: 'Family Waqf' });
}
const order = { high: 0, medium: 1, low: 2 };
return items.sort((a, b) => order[a.severity] - order[b.severity]);
}