# VPandit — Dynamic Formula Engagement Engine

Bind **your own formulas** (proprietary code algorithms + data-driven rule-master
queries) to catalogue / report services. When a service is performed, the engine runs
the bound formulas and merges their **predictions / consultations** into the report —
an extra IP layer on top of the existing pay-to-unlock flow. Output is deterministic
(your code + your data), never AI-guessed.

---

## 1. Where do MY formulas go?  (two kinds)

### A) CODE formula — your proprietary algorithm  (server-side, never exposed)
1. Copy `app/Services/Formula/Handlers/_TemplateHandler.php` → e.g. `MyNaadiYoga.php`.
2. Put your maths inside `handle($context, $params)`. The file has three clearly-marked
   steps — look for the `👉 YOUR FORMULA HERE` banner.
   - `$context` = name, dob, gender, tob, place, lang + any computed fields you pass
     (grid, numbers, grahas, placements, inputs…).
   - `$params`  = tunables you set on the definition / binding.
   - return `['score'=>float, 'matched'=>[], 'text'=>'<html>', 'data'=>[]]`.
3. Worked reference: `app/Services/Formula/Handlers/SampleLoShuMissingNumbers.php`
   (fully implemented — finds missing Lo Shu numbers + remedies).

### B) QUERY formula — data-driven rule from a master you already own
No code. Just a Formula Definition row pointing at one of your rule masters
(`num_rules`, `vastu_object_rules`, `num_combo`, `*_score_rules`, `vastu_suggestions`,
`vastu_knowledge_base`, `matchmaking_kootas`, …) with a JSON `match_config`. The engine
builds the WHERE from your context, orders by priority, sums `score_impact`, and renders
the localized `finding` / `explanation`. Re-tune anytime by editing the master — no redeploy.

### C) HYBRID — a code handler that ALSO queries masters
Implement `FormulaHandler`, inject `FormulaEngine`, and call
`$this->engine->queryMaster($slug, $matchConfig, $selectConfig, $context, $lang)`.

---

## 2. How do I engage a formula with a service?

Two masters do the wiring (config-driven, no code):

**`formula_definitions`** — the catalogue of formulas
`formula_key · name · category · type(code|query|hybrid) · handler · source_master ·
match_config(JSON) · select_config(JSON) · output_template · params_schema · status`

**`service_formula_bindings`** — the engagement
`binding_code · svc_context(catalogue|report_service) · service_slug · formula_key ·
sequence · params(JSON) · output_slot · mode(append|replace) · condition · status`

`service_slug` is your **Main / Peripheral catalogue service** (`svc_context=catalogue`)
or a **paid report service** tab (`svc_context=report_service`). Bind as many formulas
to one service as you like; they run in `sequence` and land in the named `output_slot`.

Manage both at `/app/masters/formula_definitions` and
`/app/masters/service_formula_bindings` (import via the master template too).

---

## 3. Complete example (ready to load)

`database/formula/formula_example.sql` engages TWO formulas with the paid service
**`lo-shu-deep-dive`**:

| formula_key         | type  | source                                   |
|---------------------|-------|------------------------------------------|
| `num.lo_shu_missing`| code  | `SampleLoShuMissingNumbers` (your IP)    |
| `num.grid_rules`    | query | `num_rules` master, matched by calc_key+number |

Both write into the `predictions` slot of the report.

### Run it
```php
$out = app(\App\Services\Formula\FormulaEngine::class)->runForService(
    'report_service', 'lo-shu-deep-dive',
    [
        'name' => 'Ravi Sharma', 'dob' => '1988-03-14', 'lang' => 'en',
        // context the QUERY formula matches on (num_rules WHERE calc_key + number):
        'calc_key' => 'driver', 'number' => 5,
        // optional precomputed grid; else derived from dob:
        // 'grid_counts' => [1=>2,2=>0,3=>1,4=>1,5=>0,6=>0,7=>0,8=>2,9=>1],
    ],
    'en'
);
// $out['slots']['predictions']['html']  → merge into the report
// $out['score']                          → overall formula score
```

### What the CODE formula returns for dob 1988-03-14 (verified)
```
score   : 5
missing : 2, 5, 6, 7
Missing 2 — lacks intuition & relationships. Remedy: strengthen the Moon; offer water at sunrise.
Missing 5 — lacks communication & balance. Remedy: green colours; Budh (Mercury) mantra on Wednesday.
Missing 6 — lacks love, home & responsibility. Remedy: Shukra (Venus) remedies; care for the home.
Missing 7 — lacks spirituality & introspection. Remedy: meditation; Ketu remedies.
```

### The QUERY formula (`num.grid_rules`) generates roughly:
```sql
SELECT * FROM num_rules
WHERE is_active = 1
  AND calc_key = 'driver'     -- from context.calc_key
  AND number   = 5            -- from context.number
  AND deleted_at IS NULL
ORDER BY priority DESC
LIMIT 20;
-- score = SUM(score_impact); each row rendered as
--   <p><b>{{finding}}</b> — {{explanation}}</p>   (finding_hi/gu/mr used when lang≠en)
```

---

## 4. Wire it into a report (one line)

Inside any report builder (e.g. `NumReportService::generate()`), after the base
sections are built:

```php
$formula = app(\App\Services\Formula\FormulaEngine::class)
    ->runForService('report_service', $serviceSlug, $context, $lang);

foreach ($formula['slots'] as $slot => $s) {
    // append $s['html'] under a heading, or map $slot → a section of your template
    $sections[$slot] = ($sections[$slot] ?? '') . $s['html'];
}
$totalScore += $formula['score'];
```

The same call works for catalogue services with `svc_context = 'catalogue'`.

---

## 5. Test without touching a report

```
POST /api/formula/test    { "formula_key":"num.lo_shu_missing",
                            "context":{"dob":"1988-03-14","lang":"en"} }
POST /api/formula/run     { "svc_context":"report_service",
                            "service_slug":"lo-shu-deep-dive",
                            "context":{"dob":"1988-03-14","calc_key":"driver","number":5} }
```
(admin-gated: super_admin | trust_manager)

---

## 6. match_config / select_config reference

`match_config`:
```json
{ "where": [ { "col": "<column>", "op": "=|in|between|gte|lte|gt|lt|neq|like",
              "ctx": "<context key>", "value": "<literal, optional>", "required": false } ],
  "active_col": "is_active" }
```
- A clause with no value (literal or context) is **skipped** (so a missing context field
  never over-constrains), unless `"required": true`.

`select_config`:
```json
{ "order": [ { "col": "priority", "dir": "desc" } ],
  "limit": 50, "score_col": "score_impact",
  "lang_fields": ["finding","explanation"],
  "row_template": "<p><b>{{finding}}</b>{{explanation_sep}}{{explanation}}</p>" }
```
- `{{<column>}}` prints any column; `{{explanation_sep}}` = " — " only when explanation is non-empty.

## Files
- app/Services/Formula/FormulaHandler.php        — interface (your formula contract)
- app/Services/Formula/FormulaEngine.php         — runtime (bindings, query executor, dispatch)
- app/Services/Formula/Handlers/SampleLoShuMissingNumbers.php — worked CODE example
- app/Services/Formula/Handlers/_TemplateHandler.php          — copy-paste template
- app/Http/Controllers/Api/FormulaController.php  — /api/formula/run + /test
- config/masters.php                              — formula_definitions + service_formula_bindings
- database/formula/formula_example.sql            — the complete example rows
