Files
yara724-api/docs/external-integrations-reference.html
2026-08-17 17:18:49 +03:30

594 lines
46 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>External Integrations Reference</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, "Segoe UI", system-ui, sans-serif;
font-size: 14px; line-height: 1.6;
background: #ffffff; color: #1f2328; padding: 24px;
}
h1 { font-size: 20px; font-weight: 700; margin-bottom: 4px; }
.subtitle { font-size: 13px; color: #57606a; margin-bottom: 28px; }
h2 {
font-size: 15px; font-weight: 700;
margin-bottom: 10px; margin-top: 32px;
border-bottom: 1px solid #e5e7eb; padding-bottom: 6px;
}
h3 {
font-size: 12px; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.05em;
color: #57606a; margin-bottom: 8px; margin-top: 14px;
}
.section-intro {
font-size: 13px; color: #57606a;
margin-bottom: 14px; line-height: 1.5;
}
.card {
border: 1px solid #e5e7eb; border-radius: 6px;
padding: 16px; background: #f7f8fa; margin-bottom: 16px;
}
.card.card-blue { border-left: 4px solid #3b82f6; }
.card.card-green { border-left: 4px solid #22c55e; }
.card.card-purple { border-left: 4px solid #8b5cf6; }
.card.card-orange { border-left: 4px solid #f97316; }
.card.card-teal { border-left: 4px solid #14b8a6; }
.card.card-indigo { border-left: 4px solid #6366f1; }
.card.card-gray { border-left: 4px solid #94a3b8; }
.card.card-red { border-left: 4px solid #ef4444; }
.card.card-yellow { border-left: 4px solid #eab308; }
table {
border-collapse: collapse; width: 100%;
font-size: 12px; margin-top: 4px;
}
th {
background: #f1f5f9; font-weight: 600;
text-align: left; padding: 5px 8px; border: 1px solid #e5e7eb;
}
td { padding: 4px 8px; border: 1px solid #e5e7eb; vertical-align: top; }
tr:nth-child(even) td { background: #ffffff; }
code { font-family: monospace; font-size: 11px; color: #3b82d4; }
.method {
font-family: monospace; font-size: 11px;
font-weight: 700; white-space: nowrap;
}
.method.get { color: #059669; }
.method.post { color: #2563eb; }
.method.put { color: #d97706; }
.method.patch { color: #7c3aed; }
.note { font-size: 11px; color: #57606a; font-style: italic; margin-top: 6px; }
.warn { font-size: 11px; color: #9a3412; font-style: italic; margin-top: 6px; }
.status-badge {
display: inline-block; font-size: 11px; font-weight: 600;
padding: 1px 7px; border-radius: 10px;
}
.status-live { background: #dcfce7; color: #166534; }
.status-partial { background: #ffedd5; color: #9a3412; }
.status-disabled { background: #fee2e2; color: #991b1b; }
.status-internal { background: #f1f5f9; color: #475569; border: 1px solid #e2e8f0; }
.toc {
background: #f7f8fa; border: 1px solid #e5e7eb;
border-radius: 6px; padding: 14px 18px; margin-bottom: 28px;
}
.toc-title { font-size: 13px; font-weight: 700; margin-bottom: 8px; }
.toc ol { padding-left: 18px; }
.toc li { font-size: 13px; margin-bottom: 3px; }
.toc a { color: #3b82d4; text-decoration: none; }
.toc a:hover { text-decoration: underline; }
footer {
text-align: center; font-size: 12px; color: #57606a;
border-top: 1px solid #e5e7eb; margin-top: 40px; padding-top: 12px;
}
.max-wrap { max-width: 760px; margin: 0 auto; }
.flow-box {
background: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px;
padding: 10px 14px; font-size: 12px; margin-top: 8px;
}
.flow-step {
display: flex; gap: 10px; align-items: flex-start; margin-bottom: 6px;
}
.flow-num {
flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%;
background: #3b82d4; color: #fff; font-size: 11px; font-weight: 700;
display: flex; align-items: center; justify-content: center;
}
.flow-text { flex: 1; padding-top: 2px; }
.decision-tree {
font-size: 12px; background: #ffffff;
border: 1px solid #e5e7eb; border-radius: 4px; padding: 12px 16px;
margin-top: 8px; line-height: 1.8;
}
.decision-tree ul { padding-left: 20px; }
.decision-tree li { margin-bottom: 2px; }
.env-table th:first-child { width: 220px; }
pre {
font-family: monospace; font-size: 11px;
background: #f1f5f9; border: 1px solid #e5e7eb;
border-radius: 4px; padding: 10px 12px;
white-space: pre-wrap; word-break: break-all;
margin-top: 6px; color: #1f2328;
}
</style>
</head>
<body>
<div class="max-wrap">
<h1>External Integrations Reference</h1>
<p class="subtitle">
Every outbound integration: what it does, when it fires, how auth works,
retry behaviour, fallbacks, and all environment variables. Internal-only
services (captcha, offline inquiry seed) are included for completeness.
</p>
<!-- TOC -->
<div class="toc">
<div class="toc-title">Contents</div>
<ol>
<li><a href="#inquiry-routing">Inquiry routing decision tree</a></li>
<li><a href="#fanavaran">Fanavaran — insurance claims platform</a></li>
<li><a href="#sanhub">SandHub — legacy inquiry gateway</a></li>
<li><a href="#tejarat">Tejarat inquiry — block-inquiry gateway (V2+)</a></li>
<li><a href="#esg">ESG — Parsian-tenant inquiry provider</a></li>
<li><a href="#sms">SMS — Kavenegar and Parsian gateways</a></li>
<li><a href="#ai">AI service — car damage detection</a></li>
<li><a href="#car-pricing">Car pricing service — market value lookup</a></li>
<li><a href="#offline-inquiry">Offline inquiry — fallback seed data</a></li>
<li><a href="#env-ref">Environment variable reference</a></li>
</ol>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="inquiry-routing">1 — Inquiry Routing Decision Tree</h2>
<p class="section-intro">
Every blame file starts with a "run-inquiries" call that fetches the
guilty party's insurance policy from an external provider. Which provider
is actually called depends on three factors: the tenant (<code>CLIENT_ID</code>),
the file type (THIRD_PARTY vs CAR_BODY), and whether live API mode is
enabled in system settings. The offline-inquiry seed layer sits in front
of all three providers.
</p>
<div class="card card-indigo">
<h3>Provider selection</h3>
<div class="decision-tree">
<strong>For every plate-based block inquiry:</strong>
<ul>
<li>1. Check offline-inquiry seeds (MongoDB) — if a matching seed exists, return it and skip all HTTP.</li>
<li>2. If <code>CLIENT_ID=8</code> (Parsian/ESG tenant) → route to <strong>ESG</strong> <code>/inquiry/policyByPlate</code> or <code>/inquiry/policyByChassis</code>.</li>
<li>3. Otherwise → route to <strong>Tejarat inquiry</strong> <code>/block-inquiry-tejarat</code> (THIRD_PARTY) or <code>/block-inquiry-tejarat/badane</code> (CAR_BODY).</li>
<li>4. If <code>system_settings.externalApis.sandHubUseLiveApi = false</code> (default) → return mock response instead of making HTTP calls.</li>
</ul>
<br>
<strong>For personal-identity, driving-licence, ownership, and Sheba checks:</strong>
<ul>
<li>If <code>CLIENT_ID=8</code> → ESG <code>/inquiry/person</code> and <code>/inquiry/sheba</code>.</li>
<li>Otherwise → Tejarat/SandHub <code>/personal-inquiry/tejarat-no</code>, <code>/driver-license-check</code>, <code>/ownership</code>, <code>/sheba/sheba-tejaratno</code>.</li>
</ul>
<br>
<strong>Key difference — birth date format:</strong>
SandHub/Tejarat expect a <em>Gregorian</em> birth date (converted internally from Jalali).
ESG expects the <em>Jalali</em> date directly.
</div>
<p class="note" style="margin-top:8px;">
SandHub endpoints are only used in legacy code paths. All active V2+ blame flows go through the Tejarat or ESG providers.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="fanavaran">2 — Fanavaran <span class="status-badge status-live">live</span></h2>
<p class="section-intro">
Fanavaran (<code>apimanager.iraneit.com</code>) is the national insurance
damage-case platform. After the damage expert submits their assessment,
the system auto-submits a structured claim to Fanavaran through a
four-step protocol. Fanavaran also serves as the lookup source for
code-lists (accident types, car components, city codes, etc.) used
across the platform.
</p>
<div class="card card-blue">
<h3>Authentication lifecycle</h3>
<div class="flow-box">
<div class="flow-step"><div class="flow-num">1</div><div class="flow-text"><strong>GET AppToken</strong> — <code>POST /EITAuthentication/GetAppToken</code> with <code>appname</code> + <code>secret</code> headers. Returns <code>apptoken</code> header.</div></div>
<div class="flow-step"><div class="flow-num">2</div><div class="flow-text"><strong>Login</strong> — <code>POST /EITAuthentication/Login</code> with <code>appToken</code> + <code>userName</code> + <code>password</code> headers. Returns <code>authenticationToken</code> header.</div></div>
<div class="flow-step"><div class="flow-num">3</div><div class="flow-text"><strong>Cache</strong> — token is cached in memory <em>and</em> persisted to MongoDB (<code>fanavaran_auth_tokens</code>). Valid until midnight <strong>Asia/Tehran</strong> — the first call after 00:00 fetches a fresh token.</div></div>
<div class="flow-step"><div class="flow-num">4</div><div class="flow-text"><strong>All subsequent calls</strong> include four headers: <code>authenticationToken</code>, <code>CorpId</code>, <code>ContractId</code>, <code>Location</code> — tenant-specific, hardcoded per <code>FANAVARAN_CLIENT</code> key.</div></div>
</div>
<p class="note" style="margin-top:8px;">
A config fingerprint (hash of appName + secret + username + password + corpId + contractId + location)
forces a fresh login when any credential changes, even before midnight.
</p>
</div>
<div class="card card-blue">
<h3>Claim submission protocol (4 steps)</h3>
<div class="flow-box">
<div class="flow-step"><div class="flow-num">1</div><div class="flow-text"><strong>Base claim (GEN.03)</strong> — <code>POST /car/third-party-car-financial-claims</code>. Sends owner, driver, insurance, vehicle, and accident data. Returns a Fanavaran <code>claimId</code> and <code>claimNo</code>. SMS is sent to the owner with both identifiers.</div></div>
<div class="flow-step"><div class="flow-num">2</div><div class="flow-text"><strong>Damage cases (GEN.05)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/dmg-cases</code>. One entry per damaged part with component ID, severity, and price. Cap: total ≤ 53 000 000 Toman.</div></div>
<div class="flow-step"><div class="flow-num">3</div><div class="flow-text"><strong>Attachments (GEN.07)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/files</code>. Documents, car-capture images, and videos referenced by file ID.</div></div>
<div class="flow-step"><div class="flow-num">4</div><div class="flow-text"><strong>Expertise (GEN.08)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/expertise</code>. Expert assessment metadata (expert role, date, result). Finalises the submission.</div></div>
</div>
<p class="note" style="margin-top:8px;">
All four steps are recorded in the <code>fanavaran_audit_logs</code> collection with full request/response bodies, HTTP status, duration, and tracking code for debugging.
</p>
</div>
<div class="card card-blue">
<h3>Lookup endpoints</h3>
<p class="note">All under <code>https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/</code>. Results are cached to disk (per client key) and in the <code>lookups</code> MongoDB collection. Parsian tenant reads DB before hitting the API; others go to the API first.</p>
<table>
<tr><th>Path</th><th>Used for</th></tr>
<tr><td><code>/car/base-info/accident-causes</code></td><td>accidentReason dropdown options (mapped to local IDs)</td></tr>
<tr><td><code>/car/code-list/accident-report-type</code></td><td>accidentWay options</td></tr>
<tr><td><code>/car/base-info/vehicle-use-types</code></td><td>vehicle usage classification</td></tr>
<tr><td><code>/car/code-list/dmg-pay-method</code></td><td>damage payment method</td></tr>
<tr><td><code>/car/base-info/driving-licence-types</code></td><td>licence type options</td></tr>
<tr><td><code>/car/code-list/accident-culprit-type</code></td><td>guilty-party classification</td></tr>
<tr><td><code>/car/code-list/inspection-place</code></td><td>inspection location options</td></tr>
<tr><td><code>/car/code-list/drop-amount-status</code></td><td>price-drop status codes</td></tr>
<tr><td><code>/car/base-info/car-components</code></td><td>component catalog (maps to outer/inner parts)</td></tr>
<tr><td><code>/car/code-list/accident-level</code></td><td>accident severity options</td></tr>
<tr><td><code>/common/code-list/insurance-corp</code></td><td>resolve <code>INSURANCE_CORP_ID</code> → Fanavaran corpId</td></tr>
<tr><td><code>/common/base-info/cities</code>, <code>/common/base-info/Provinces</code></td><td>city/province pickers</td></tr>
<tr><td><code>/car/third-party-car-policies/{policyId}</code></td><td>fetch full policy by ID after inquiry</td></tr>
<tr><td><code>/car/vehicles/inquiry-by-vin?vin=…</code></td><td>VIN-based vehicle lookup</td></tr>
<tr><td><code>/common/Policies/inquiry-my-policies</code></td><td>list policies for a national code</td></tr>
<tr><td><code>/common/customers/{customerId}</code></td><td>fetch customer record by ID</td></tr>
<tr><td><code>/common/parties/inquiry-by-unique-identifier</code></td><td>party lookup by national code + birth date</td></tr>
</table>
</div>
<div class="card card-blue">
<h3>Error handling &amp; resilience</h3>
<table>
<tr><th>Mechanism</th><th>Detail</th></tr>
<tr><td>Retry</td><td>3 attempts, 500 ms → 1 000 ms exponential backoff on all HTTP calls.</td></tr>
<tr><td>Transient backoff</td><td>When Fanavaran returns the Persian "try again later" message (or tracking-code 500), a tenant-wide 5-minute pause is activated. All calls during this window get <code>503 ServiceUnavailable</code> immediately — no hammering.</td></tr>
<tr><td>Token invalidation</td><td>On 401, token is cleared from memory and MongoDB; next call triggers a fresh GetAppToken + Login.</td></tr>
<tr><td>Inflight de-dup</td><td>Concurrent login requests for the same tenant are collapsed to a single in-flight Promise.</td></tr>
<tr><td>Audit log</td><td>Every step (GET_APP_TOKEN, LOGIN, and all four submission steps) is written to <code>fanavaran_audit_logs</code> with STARTED / SUCCESS / FAILURE status, full headers, body, and duration.</td></tr>
<tr><td>Timeout</td><td>20–30 s per HTTP call.</td></tr>
</table>
</div>
<div class="card card-blue">
<h3>Tenant profiles (<code>FANAVARAN_CLIENT</code>)</h3>
<p class="section-intro" style="margin-top:6px; margin-bottom:8px;">Three pre-seeded tenant profiles exist. The active one is chosen by the <code>FANAVARAN_CLIENT</code> env var. Each profile carries its own <code>appName</code>, <code>secret</code>, <code>username</code>, <code>password</code>, <code>CorpId</code>, <code>ContractId</code>, and <code>Location</code> headers, plus payload defaults (AccidentCityId, etc.).</p>
<table>
<tr><th>Key</th><th>Insurance company</th></tr>
<tr><td><code>parsian</code></td><td>Parsian Insurance</td></tr>
<tr><td><code>tejaratno</code></td><td>Tejaratno Insurance</td></tr>
<tr><td><code>moallem</code></td><td>Moallem Insurance</td></tr>
</table>
<p class="note" style="margin-top:8px;">
<code>INSURANCE_CORP_ID</code> is a display-caption string (e.g. <em>"بیمه پارسیان"</em>) that is resolved against the live Fanavaran <code>insurance-corp</code> list to produce the numeric <code>corpId</code> used in submissions. The resolved ID is cached to disk.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="sanhub">3 — SandHub <span class="status-badge status-partial">legacy</span></h2>
<p class="section-intro">
SandHub is the original inquiry gateway. It is still present in the
codebase but all active blame flows (V2+) have been migrated to the
Tejarat inquiry provider. SandHub endpoints remain callable but are
only reached through legacy code paths. Its mock mode is controlled
by the same <code>sandHubUseLiveApi</code> system setting.
</p>
<div class="card card-gray">
<h3>Auth</h3>
<p class="note">
<code>POST {SANHUB_BASE_URL}/user/login</code> with username + password JSON body.
Token cached in memory for <strong>55 minutes</strong>. On 401, token is cleared and one retry is made.
3 attempts with 1 000 ms → 2 000 ms exponential backoff.
</p>
</div>
<div class="card card-gray">
<h3>Endpoints</h3>
<table>
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
<tr><td><span class="method post">POST</span></td><td><code>/block-inquiry-tejarat</code></td><td>Plate-based insurance policy inquiry (THIRD_PARTY). Body: <code>leftTwoDigits</code>, <code>serialLetter</code>, <code>threeDigits</code>, <code>rightTwoDigits</code>, <code>nationalCode</code>.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/block-inquiry-tejarat/badane</code></td><td>CAR_BODY policy inquiry. Timeout 50 s (longer than standard).</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/personal-inquiry/tejarat-no</code></td><td>Personal identity check. Body: <code>nationalCode</code> + <em>Gregorian</em> <code>birthDate</code> (converted from Jalali internally).</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/driver-license-check</code></td><td>Driving licence validation. Returns <code>IsSucceed</code> flag.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/ownership</code></td><td>Vehicle ownership check. Returns <code>IsSuccess</code> flag.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/sheba/sheba-tejaratno</code></td><td>Sheba / bank account validation. Returns <code>ReturnValue</code> + <code>HasError</code>.</td></tr>
</table>
<p class="note" style="margin-top:8px;">
All endpoints support full mock responses when <code>sandHubUseLiveApi=false</code> in system settings (default). Mock data is deterministic and produced locally without any HTTP calls.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="tejarat">4 — Tejarat Inquiry <span class="status-badge status-live">live</span></h2>
<p class="section-intro">
The active block-inquiry gateway for all non-ESG tenants. Used in every
V2+ <code>run-inquiries</code> call where <code>CLIENT_ID ≠ 8</code>.
The base URL is configurable; in production it points to the same host
as SandHub but uses separate credentials.
</p>
<div class="card card-teal">
<h3>Auth</h3>
<p class="note">
<code>POST {TEJARAT_INQUIRY_BASE_URL}/user/login</code> with email + password JSON body.
Token cached for <strong>55 minutes</strong>. 2 attempts with 500 ms → 1 000 ms backoff.
Separate from SandHub credentials — uses <code>TEJARAT_INQUIRY_EMAIL</code> / <code>TEJARAT_INQUIRY_PASSWORD</code>.
</p>
</div>
<div class="card card-teal">
<h3>Endpoints</h3>
<table>
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
<tr><td><span class="method post">POST</span></td><td><code>/block-inquiry-tejarat</code></td><td>THIRD_PARTY plate inquiry. Body: plate fields + <code>nationalCode</code>. Offline seed checked first.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/block-inquiry-tejarat/badane</code></td><td>CAR_BODY plate inquiry. Body: <code>part1–part4</code> (numeric) + <code>nationalCode</code>. Always goes live (no mock for badane path).</td></tr>
</table>
<p class="note" style="margin-top:8px;">
When <code>sandHubUseLiveApi=false</code>, the THIRD_PARTY path returns a mock response without HTTP. The CAR_BODY path always calls the live API regardless of this flag.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="esg">5 — ESG <span class="status-badge status-live">live (CLIENT_ID=8)</span></h2>
<p class="section-intro">
ESG is an internal insurance API gateway used exclusively by the Parsian
tenant (<code>CLIENT_ID=8</code>). It replaces Tejarat/SandHub for all
inquiry types when this tenant is active. It has a different response
shape, a dynamic token TTL, and expects birth dates in <strong>Jalali</strong>
format (not Gregorian, unlike SandHub/Tejarat).
</p>
<div class="card card-purple">
<h3>Auth</h3>
<p class="note">
<code>POST {ESG_URL}/auth/login</code> with <code>{ username, password }</code> JSON body.
Token TTL is read from the response <code>expiresIn</code> field (default 14 min).
2 attempts with 500 ms → 1 000 ms backoff. On 401, token cleared and one retry.
Default URL: <code>http://192.168.20.22:8085</code> (internal network).
</p>
</div>
<div class="card card-purple">
<h3>Endpoints</h3>
<table>
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByPlate</code></td><td>Plate-based policy lookup (THIRD_PARTY). Body: <code>nationalCode</code>, <code>plk1–plk4</code>. Response is mapped to the old Tejarat format before being stored.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByChassis</code></td><td>VIN/chassis-based alternative to plate inquiry. Called by <code>run-inquiries-vin</code> endpoints. Uses ESG chassis lookup (not the SandHub path). Body: <code>nationalCode</code>, <code>chassis</code>.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/person</code></td><td>Personal identity check. Body: <code>nationalCode</code>, <code>birthDate</code> (Jalali, NOT Gregorian).</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/sheba</code></td><td>Sheba / bank account validation.</td></tr>
</table>
<p class="note" style="margin-top:8px;">
ESG wraps every response as <code>{ success: boolean, data: … }</code>. A <code>success=false</code> body is translated to a Persian "استعلام در دسترس نیست" (inquiry unavailable) error.
The offline-inquiry seed check still runs first, before any ESG HTTP call.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="sms">6 — SMS <span class="status-badge status-live">live</span></h2>
<p class="section-intro">
Two SMS providers are supported: <strong>Kavenegar</strong> (default)
and <strong>Parsian SMS Gateway</strong>. The active provider is chosen
by the <code>SMS_PROVIDER</code> (or <code>SMS</code>) env var. Both
providers implement the same internal gateway interface so the
orchestration layer is provider-agnostic.
</p>
<div class="card card-green">
<h3>Provider selection</h3>
<table>
<tr><th>Env var</th><th>Value</th><th>Active provider</th></tr>
<tr><td><code>SMS_PROVIDER</code> (or <code>SMS</code>)</td><td><code>kavenegar</code> (default)</td><td>Kavenegar — <code>api.kavenegar.com</code></td></tr>
<tr><td><code>SMS_PROVIDER</code> (or <code>SMS</code>)</td><td><code>parsian</code></td><td>Parsian SMS Gateway — <code>PARSIAN_SMS_URL</code></td></tr>
</table>
</div>
<div class="card card-green">
<h3>Kavenegar endpoints</h3>
<p class="note">Base URL: <code>https://api.kavenegar.com/v1/{SMS_API_KEY}/</code></p>
<table>
<tr><th style="width:70px">Method</th><th>Path</th><th>When used</th></tr>
<tr><td><span class="method post">POST</span></td><td><code>sms/send.json</code></td><td>Plain-text messages (e.g. key-based notification texts stored in <code>sms_texts</code> collection).</td></tr>
<tr><td><span class="method get">GET</span></td><td><code>verify/lookup.json</code></td><td>All template-based messages (OTPs, invite links, expert notifications). Params: <code>receptor</code>, <code>token</code>[, <code>token2</code>, <code>token3</code>, <code>token10</code>], <code>template</code>.</td></tr>
</table>
</div>
<div class="card card-green">
<h3>Parsian SMS Gateway</h3>
<p class="note">Base URL from <code>PARSIAN_SMS_URL</code>. Auth: <code>X-PACKAGE-API-KEY</code> header + <code>Authorization: Basic {PARSIAN_BASIC_TOKEN}</code>. Sends as a GET with URL-encoded <code>ReceiverNumbers</code> and <code>Message</code> query params. Template messages are pre-rendered into a plain text body before sending (no verify/lookup equivalent).</p>
</div>
<div class="card card-green">
<h3>SMS templates in use</h3>
<table>
<tr><th>Template name</th><th>Trigger</th><th>Tokens</th></tr>
<tr><td><code>AUTH_SMS_TEMPLATE</code> (env)</td><td>User / actor OTP login, forget-password, party OTPs</td><td><code>token</code> = OTP code</td></tr>
<tr><td><code>yara724-invite-link</code></td><td>Second party receives blame invite link via SMS</td><td><code>token</code> = publicId, <code>token2</code> = link</td></tr>
<tr><td><code>yara-field-expert-link</code></td><td>Field expert sends link to a party</td><td><code>token</code> = file type, <code>token2</code> = expert surname, <code>token3</code> = link</td></tr>
<tr><td><code>yara-blame-agreement</code></td><td>Notify party that the other side agreed to the expert verdict</td><td><code>token</code> = publicId, <code>token2</code> = link</td></tr>
<tr><td><code>yara-claim-link</code></td><td>Damaged party notified to open claim flow after blame is complete</td><td><code>token</code> = publicId, <code>token2</code> = link</td></tr>
<tr><td><code>yara-expert-lock</code></td><td>Expert locks a blame or claim file</td><td><code>token</code> = "تصادف"/"خسارت", <code>token2</code> = publicId, <code>token3</code> = expert surname</td></tr>
<tr><td><code>yara-resend-documents</code></td><td>Expert requests document resend</td><td><code>token</code> = file kind, <code>token2</code> = publicId, <code>token3</code> = link</td></tr>
<tr><td><code>yara-signature</code></td><td>Party notified to sign the expert's damage assessment</td><td><code>token</code> = file kind, <code>token2</code> = publicId, <code>token3</code> = expert surname, <code>token10</code> = link</td></tr>
<tr><td><code>yara-fanavaran-claim</code></td><td>Fanavaran submission confirmed — sent to claim owner with Fanavaran claim number and ID</td><td><code>token</code> = publicId, <code>token2</code> = Fanavaran claimId, <code>token3</code> = Fanavaran claimNo</td></tr>
</table>
<p class="note" style="margin-top:8px;">
All SMS calls are fire-and-forget — they never throw. Failures are logged but do not block the main flow.
An <code>sms_send_logs</code> MongoDB collection records every outbound message with its kind (OTP vs TEMPLATE), provider, template name, and success/failure status.
Notification text messages (parties-disagree, one-party-signed, etc.) are seeded into the <code>sms_texts</code> collection on startup and editable at runtime.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="ai">7 — AI Service <span class="status-badge status-disabled">disabled (code present)</span></h2>
<p class="section-intro">
An image-based car damage detection service is integrated in the
codebase but its HTTP calls are <strong>fully commented out</strong>.
The module initialises on startup, attempts a login (silently swallowed
if it fails), and exposes an <code>aiRequestImage</code> method — but
the underlying axios calls are disabled. The service does not affect
any production flow.
</p>
<div class="card card-yellow">
<h3>Intended interface (when re-enabled)</h3>
<table>
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
<tr><td><span class="method post">POST</span></td><td><code>{AI_URL_V2}/auth/login</code></td><td>Authenticate with username + password. Returns <code>accessToken</code>.</td></tr>
<tr><td><span class="method get">GET</span></td><td><code>{AI_URL_V2}/auth/profile</code></td><td>Fetch <code>apiKey.key</code> needed as the <code>gateway-api-key</code> request header.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>{AI_URL_V2}/services/car-damage/detector?version=ai-v7</code></td><td>Submit a car part image (multipart). Returns <code>downloadLink</code> with annotated result.</td></tr>
</table>
<p class="warn" style="margin-top:8px;">
Status: all three calls are wrapped in commented-out <code>axios.request(…)</code> blocks.
<code>CW_URL</code> is not in <code>.env.example</code>. To re-enable, uncomment the login, getApiKey, and aiRequestImage axios calls, and configure <code>AI_URL_V2</code>, <code>AI_USERNAME</code>, <code>AI_PASSWORD</code>.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="car-pricing">8 — Car Pricing Service <span class="status-badge status-partial">partially active</span></h2>
<p class="section-intro">
Used only during damage-expert price-drop calculation. When an expert
provides per-part severity values the system fetches real-time market
prices for the damaged car model, then computes the price-drop using
the formula: <strong>carPrice × yearCoefficient × sumOfPartCoefficients ÷ 400</strong>.
The service has two data sources (endpoints) that are tried in parallel.
</p>
<div class="card card-orange">
<h3>Endpoints</h3>
<table>
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
<tr><td><span class="method get">GET</span></td><td><code>{CW_URL}price?akharin</code></td><td>Fetch car market prices from the "Akharin" source. Returns array of <code>{ carName, marketPrice }</code>.</td></tr>
<tr><td><span class="method get">GET</span></td><td><code>{CW_URL}price?hamrah</code></td><td>Fetch car market prices from the "Hamrah" source. Same response shape.</td></tr>
</table>
<p class="note" style="margin-top:8px;">
Both endpoints are tried; results are merged and de-duplicated. The best match for
the damaged car's name is found using <strong>Levenshtein distance</strong> (fuzzy string match).
If both endpoints fail or return empty, the price-drop calculation is skipped (marked incomplete) — it does not block claim submission.
</p>
<p class="warn" style="margin-top:6px;">
<strong><code>CW_URL</code> is not documented in <code>.env.example</code>.</strong>
This service will silently produce no price-drop if the variable is unset.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="offline-inquiry">9 — Offline Inquiry <span class="status-badge status-internal">internal / fallback</span></h2>
<p class="section-intro">
The offline inquiry layer intercepts plate-based inquiry calls before
any external HTTP is made. It is primarily used for development and
testing (pre-seeded known plates) but also acts as a resilience fallback
when live inquiry services are unavailable. It is controlled by a
runtime database flag, not an env var.
</p>
<div class="card card-gray">
<h3>How it works</h3>
<table>
<tr><th>Aspect</th><th>Detail</th></tr>
<tr><td>Storage</td><td>MongoDB collection <code>offline-inquiries</code>. Documents contain <code>clientKey</code>, normalised plate fields, <code>nationalCode</code>, and the pre-built <code>raw</code> + <code>mapped</code> response to return.</td></tr>
<tr><td>Master switch</td><td><code>system_settings.offlineInquiry.enabled</code> — defaults to <code>true</code>. Toggle via <code>PATCH /super-admin/system-settings/offline-inquiry</code>.</td></tr>
<tr><td>Lookup order</td><td>Normalised plate (digits-only, Arabic→Persian) + national code + Fanavaran client key must all match. If found, returned immediately; no HTTP call is made.</td></tr>
<tr><td>Scope</td><td>Only applies to plate-based block-inquiry (THIRD_PARTY). CAR_BODY inquiry (<code>/badane</code>) always hits the live API.</td></tr>
<tr><td>Live API flag</td><td><code>system_settings.externalApis.sandHubUseLiveApi</code> — when <code>false</code> (default), even if no offline seed matches, a built-in mock response is returned rather than calling Tejarat/ESG.</td></tr>
</table>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 id="env-ref">10 — Environment Variable Reference</h2>
<p class="section-intro">
All env vars across all integrations, grouped by service.
Variables marked <strong>*</strong> are not present in <code>.env.example</code>.
</p>
<div class="card card-blue">
<h3>Fanavaran</h3>
<table class="env-table">
<tr><th>Variable</th><th>Description</th></tr>
<tr><td><code>FANAVARAN_CLIENT</code></td><td>Active tenant profile key: <code>parsian</code> | <code>tejaratno</code> | <code>moallem</code></td></tr>
<tr><td><code>INSURANCE_CORP_ID</code></td><td>Display caption of the insurer company (e.g. <em>"بیمه پارسیان"</em>) — resolved to a numeric corpId at startup against the Fanavaran insurance-corp list.</td></tr>
</table>
<p class="note" style="margin-top:8px;">Per-tenant credentials (appName, secret, username, password, CorpId, ContractId, Location) are hardcoded in <code>src/core/config/fanavaran-client.config.ts</code> under <code>SEED_FANAVARAN_CLIENT_PROFILES</code>.</p>
</div>
<div class="card card-gray">
<h3>SandHub (legacy)</h3>
<table class="env-table">
<tr><th>Variable</th><th>Description</th></tr>
<tr><td><code>SANHUB_BASE_URL</code></td><td>Base URL for SandHub. Default: <code>http://82.99.202.245:3027</code></td></tr>
<tr><td><code>SANHUB_URL_LOGIN</code></td><td>Full login URL (usually base + <code>/user/login</code>)</td></tr>
<tr><td><code>SANHUB_USERNAME</code></td><td>SandHub login email</td></tr>
<tr><td><code>SANHUB_PASSWORD</code></td><td>SandHub login password</td></tr>
</table>
</div>
<div class="card card-teal">
<h3>Tejarat inquiry</h3>
<table class="env-table">
<tr><th>Variable</th><th>Description</th></tr>
<tr><td><code>TEJARAT_INQUIRY_BASE_URL</code></td><td>Base URL. Default: <code>http://82.99.202.245:3027</code></td></tr>
<tr><td><code>TEJARAT_INQUIRY_EMAIL</code></td><td>Login email</td></tr>
<tr><td><code>TEJARAT_INQUIRY_PASSWORD</code></td><td>Login password</td></tr>
</table>
</div>
<div class="card card-purple">
<h3>ESG (CLIENT_ID=8 only)</h3>
<table class="env-table">
<tr><th>Variable</th><th>Description</th></tr>
<tr><td><code>CLIENT_ID</code></td><td>Set to <code>8</code> to activate the ESG inquiry provider for the Parsian tenant.</td></tr>
<tr><td><code>ESG_URL</code></td><td>ESG base URL. Default: <code>http://192.168.20.22:8085</code> (internal network)</td></tr>
<tr><td><code>ESG_USERNAME</code></td><td>ESG login username</td></tr>
<tr><td><code>ESG_PASSWORD</code></td><td>ESG login password</td></tr>
</table>
</div>
<div class="card card-green">
<h3>SMS</h3>
<table class="env-table">
<tr><th>Variable</th><th>Description</th></tr>
<tr><td><code>SMS_PROVIDER</code> (or <code>SMS</code>)</td><td><code>kavenegar</code> (default) or <code>parsian</code></td></tr>
<tr><td><code>SMS_API_KEY</code></td><td>Kavenegar API key (required when provider = kavenegar)</td></tr>
<tr><td><code>AUTH_SMS_TEMPLATE</code></td><td>Kavenegar template name for OTP messages (e.g. <code>yara-otp</code>)</td></tr>
<tr><td><code>PARSIAN_SMS_URL</code></td><td>Parsian SMS Gateway base URL (required when provider = parsian)</td></tr>
<tr><td><code>PARSIAN_API_KEY</code></td><td>Parsian SMS <code>X-PACKAGE-API-KEY</code> header value</td></tr>
<tr><td><code>PARSIAN_BASIC_TOKEN</code></td><td>Base64-encoded credentials for <code>Authorization: Basic …</code> header</td></tr>
<tr><td><code>URL</code></td><td>Frontend base URL — used to build all invite + claim links embedded in SMS messages</td></tr>
</table>
</div>
<div class="card card-yellow">
<h3>AI service</h3>
<table class="env-table">
<tr><th>Variable</th><th>Description</th></tr>
<tr><td><code>AI_URL_V2</code></td><td>AI gateway base URL. Default: <code>https://ai-gw.ittalie.ir</code> (unused — service is disabled)</td></tr>
<tr><td><code>AI_USERNAME</code></td><td>AI service login username (unused)</td></tr>
<tr><td><code>AI_PASSWORD</code></td><td>AI service login password (unused)</td></tr>
</table>
</div>
<div class="card card-orange">
<h3>Car pricing service</h3>
<table class="env-table">
<tr><th>Variable</th><th>Description</th></tr>
<tr><td><code>CW_URL</code> *</td><td>Base URL for car market price API (e.g. <code>https://…/</code>). Not in <code>.env.example</code>. Price-drop silently skipped if unset.</td></tr>
</table>
</div>
<div class="card card-gray">
<h3>General / app</h3>
<table class="env-table">
<tr><th>Variable</th><th>Description</th></tr>
<tr><td><code>PORT</code></td><td>HTTP port (default 3000). Used by the Fanavaran insurance-corp fallback to call its own local lookup endpoint.</td></tr>
<tr><td><code>CAPTCHA_ENABLED</code></td><td><code>true</code> / <code>false</code> — enables/disables login CAPTCHA challenge. Internal, no external service.</td></tr>
<tr><td><code>EXP_CAPTCHA_TIME</code></td><td>CAPTCHA challenge TTL in minutes.</td></tr>
<tr><td><code>EXP_OTP_TIME</code></td><td>OTP TTL in minutes.</td></tr>
</table>
</div>
<footer>Made by Sepehr</footer>
</div>
</body>
</html>