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:
@@ -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]);
|
||||
}
|
||||
Reference in New Issue
Block a user