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
+1 -1
View File
@@ -222,7 +222,7 @@ export async function getWassiyahSettings(familyId, authorId) {
export async function upsertWassiyahSettings(familyId, authorId, fields) {
const { error } = await supabase.from('nf_wassiyah_settings').upsert({
family_id: familyId, author_id: authorId, jurisdiction: fields.jurisdiction, witness1: fields.witness1, witness2: fields.witness2,
updated_at: new Date().toISOString()
testator_name: fields.testatorName, updated_at: new Date().toISOString()
}, { onConflict: 'family_id,author_id' });
if (error) throw error;
}