# فحص المستندات في سير عمل الاستقبال

> أضف فحص المستندات إلى مسار الاستقبال: الرفع، واستقبال Webhook، والتوجيه بحسب مستوى المخاطر، وإدراج الحالات في قائمة المراجعة البشرية، وتخزين رقم التقرير، والتحقق منه لاحقًا.

المصدر: https://fileverity.com/ar/docs/intake-workflow

يضع هذا الدليل التطبيقي فحص المستندات أمام أي عملية تستقبل مستندات من خارج مؤسستك: طلبات القروض أو مطالبات التأمين أو تسجيل الموردين الجدد أو تقارير المصروفات. يُفحص كل مستند مرة واحدة، ويُوجَّه بحسب مستوى مخاطره، ويُحتفظ برقم تقريره بوصفه سجل التدقيق. تعيد API مستوى مخاطر وإجراءً موصى به، أما القرار فتتخذه سياستك ومراجعوك.

يستخدم النموذج `fvdoc-3` مع الاستدعاءات الواردة في [الملفات](https://fileverity.com/ar/docs/files.md) و[التحليلات](https://fileverity.com/ar/docs/analyses.md) و[Webhooks](https://fileverity.com/ar/docs/webhooks.md). ومع مفتاح الاختبار يعيد الرفع والفحص نتيجة نموذجية فورية، لكن لا يُرسَل أي Webhook ولا يوجد ملف PDF نموذجي. استخدم **أرسل اختبارًا** على نقطة نهاية Webhook في لوحة التحكم لتجربة المستقبِل لديك.

## المسار

1. ارفع الملف عبر `POST /files` وابدأ الفحص عبر `POST /analyses`. ضع معرّف القضية لديك في `metadata` وأرسل `Idempotency-Key`.
2. أجب مستخدمك فورًا. قد تدخل المستندات في قائمة انتظار، فلا تُبقِ طلبه مفتوحًا؛ سيُخبرك Webhook عند انتهاء الفحص.
3. استقبل `analysis.completed` أو `analysis.failed`، وتحقق من التوقيع، وأدرج مهمة في قائمة الانتظار.
4. في المهمة، اجلب التحليل، ووجّهه بحسب ملخصه، واحفظ النتيجة وتقرير PDF مع القضية.
5. أرسل القضايا التي تحتاج إلى شخص إلى قائمة المراجعة، مع إرفاق ملف PDF.
6. لاحقًا، تحقق من التقرير بمقارنته بالملف المخزَّن.

## 1. الرفع وبدء الفحص

تُرفع الملفات حتى 4 ميغابايت بصيغة multipart. أما الملفات الأكبر، حتى 50 ميغابايت، فاستخدم الرفع بعنوان موقّع مسبقًا كما هو موصوف في [الملفات](https://fileverity.com/ar/docs/files.md#presigned). يعيد إنشاء التحليل الاستجابة `202` مع `status: processing` (ويعيد مفتاح الاختبار `200` مع نتيجة نموذجية جاهزة)، وتعود `metadata` الخاصة بك مع التحليل وفي كل Webhook متعلق به.

**إرسال مستند: cURL**

```bash
FILE_ID=$(curl -s https://api.fileverity.com/v1/files \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -H "Idempotency-Key: intake-A-10492-file" \
  -F file=@application.pdf | jq -r .id)

curl https://api.fileverity.com/v1/analyses \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: intake-A-10492-check" \
  -d "{\"model\": \"fvdoc-3\", \"file\": \"$FILE_ID\", \"metadata\": {\"case_id\": \"A-10492\"}}"
```

**إرسال مستند: JavaScript**

```javascript
import { readFile } from "node:fs/promises";
import { basename } from "node:path";

const base = "https://api.fileverity.com/v1";
const auth = { Authorization: "Bearer " + process.env.FILEVERITY_API_KEY };

async function call(path, init) {
  const response = await fetch(base + path, init);
  const body = await response.json();
  if (!response.ok) throw new Error(body.error.code + " (" + body.error.request_id + ")");
  return body;
}

export async function submit(caseId, path) {
  const form = new FormData();
  form.append("file", new Blob([await readFile(path)]), basename(path));
  const file = await call("/files", {
    method: "POST",
    headers: { ...auth, "Idempotency-Key": `intake-${caseId}-file` },
    body: form,
  });
  return call("/analyses", {
    method: "POST",
    headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": `intake-${caseId}-check` },
    body: JSON.stringify({ model: "fvdoc-3", file: file.id, metadata: { case_id: caseId } }),
  });
}
```

**إرسال مستند: Python**

```python
import os
import requests

BASE = "https://api.fileverity.com/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["FILEVERITY_API_KEY"]}

def call(method, path, **kwargs):
    headers = {**HEADERS, **kwargs.pop("headers", {})}
    response = requests.request(method, BASE + path, headers=headers, timeout=90, **kwargs)
    body = response.json()
    if not response.ok:
        raise RuntimeError(body["error"]["code"] + " (" + body["error"]["request_id"] + ")")
    return body

def submit(case_id, path):
    with open(path, "rb") as source:
        file = call("POST", "/files", headers={"Idempotency-Key": f"intake-{case_id}-file"}, files={"file": source})
    return call(
        "POST",
        "/analyses",
        headers={"Idempotency-Key": f"intake-{case_id}-check"},
        json={"model": "fvdoc-3", "file": file["id"], "metadata": {"case_id": case_id}},
    )
```

**202 Accepted**

```json
{
  "id": "an_8Hk2pQ4vN9sL1xR7cW3mY6zB",
  "object": "analysis",
  "created": 1793700120,
  "model": "fvdoc-3-2026-11-03",
  "livemode": true,
  "status": "processing",
  "file": {
    "name": "application.pdf",
    "bytes": 18874368,
    "format": "pdf"
  },
  "report_id": null,
  "report_url": null,
  "summary": null,
  "result": null,
  "error": null,
  "progress": {
    "stage": "queued",
    "done": 0,
    "total": 0,
    "position": 1
  },
  "usage": {
    "amount": 599,
    "currency": "usd",
    "funding": "balance"
  },
  "metadata": {
    "case_id": "A-10492"
  },
  "completed_at": null,
  "expires_at": null
}
```

احفظ `id` مع القضية. ولأن المفاتيح مشتقة من معرّف القضية، فإن تشغيل `submit` مرة أخرى بعد انتهاء المهلة يعيد الاستجابات الأولى بدلًا من رفع ملف ثانٍ أو احتساب فحص ثانٍ. تُحفظ المفاتيح 24 ساعة؛ راجع [Idempotency](https://fileverity.com/ar/docs/idempotency.md).

## 2. استقبال النتيجة

تحقق من التوقيع بالدالة `verify` من [Webhooks](https://fileverity.com/ar/docs/webhooks.md#signature)، المحفوظة باسم `verify.js` أو `verify.py`. ردّ بالحالة `2xx` بسرعة، وأنجز العمل في مهمة. وتمثّل `enqueue` قائمة المهام الخاصة بك. خزّن كل مهمة تحت معرّف الحدث، حتى لا تؤدي عملية تسليم مكررة إلى إنشاء مهمة ثانية.

**مستقبِل Webhook: JavaScript**

```javascript
import express from "express";
import { verify } from "./verify.js";

const app = express();

app.post("/webhooks/fileverity", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = verify(process.env.FILEVERITY_WEBHOOK_SECRET, req.headers, req.body.toString("utf8"));
  } catch {
    return res.sendStatus(400);
  }
  if (event.type === "analysis.completed" || event.type === "analysis.failed") {
    await enqueue({ eventId: event.id, analysisId: event.data.id });
  }
  res.sendStatus(200);
});
```

**مستقبِل Webhook: Python**

```python
import os

from fastapi import FastAPI, HTTPException, Request

from verify import verify

app = FastAPI()

@app.post("/webhooks/fileverity")
async def receive(request: Request):
    body = await request.body()
    try:
        event = verify(os.environ["FILEVERITY_WEBHOOK_SECRET"], request.headers, body)
    except (KeyError, ValueError):
        raise HTTPException(status_code=400)
    if event["type"] in ("analysis.completed", "analysis.failed"):
        enqueue(event["id"], event["data"]["id"])
    return {"ok": True}
```

## 3. التوجيه بحسب مستوى المخاطر

`summary` هو الحكم الثابت القابل للقراءة آليًا لفحص المستند. يتراوح `level` من `L1` إلى `L5`، ويمثل `action` الخطوة التالية الموصى بها. وجّه القضية بحسب الاثنين معًا، وأحِل أي قيمة لا تتعرف عليها إلى شخص.

| الملخص | معناه | مثال على التوجيه |
| --- | --- | --- |
| `L1` أو `L2`، `accept` | لم تُرصد أي علامات تلاعب. ويعني `L1` أنه جرى التحقق من المصدر. | تابع تلقائيًا. خزّن رقم التقرير. |
| `L1` أو `L2`، `note` | لا توجد علامات تلاعب، لكن التقرير يشير إلى نص مكتوب بالذكاء الاصطناعي أو إلى وسم ذكاء اصطناعي. | تابع، وعلّم القضية إذا كانت سياستك تقيّد المحتوى المكتوب بالذكاء الاصطناعي. |
| `L3`, `review` | تلزم مراجعة يدوية. | قائمة المراجعة العادية. |
| `L4`, `review` | مخاطر عالية. | قائمة المراجعة ذات الأولوية. |
| `L5`, `reject` | ثبتت المشكلات في التقرير. | قائمة المراجعة ذات الأولوية. يؤكد شخص ذلك قبل رفض أي قضية. |
| قيمة `status` هي `failed` أو `canceled` | لم ينتهِ الفحص ولم تُحتسب عليك رسوم. | ارفع الأصل المخزَّن مرة أخرى، أو أرسل القضية إلى شخص. |

تحدّد سياستك هذا التوجيه. انقل `L3` إلى قائمة الأولوية، أو اجعل `note` يذهب إلى المراجعة، بتعديل الدالة. وتضم مجموعة الإجراءات أيضًا `resubmit`، وتعدّه الدالة طلبًا للحصول على نسخة أخرى من الملف. وكل حقول الملخص موصوفة في [التحليلات](https://fileverity.com/ar/docs/analyses.md#summary-document).

**توجيه فحص منتهٍ: JavaScript**

```javascript
export function route(analysis) {
  if (analysis.status !== "succeeded") return "manual";
  const { action, level } = analysis.summary;
  if (action === "accept") return "accept";
  if (action === "note") return "accept_with_note";
  if (action === "resubmit") return "request_new_copy";
  if (action === "reject" || level === "L4" || level === "L5") return "priority_review";
  return "review";
}
```

**توجيه فحص منتهٍ: Python**

```python
def route(analysis):
    if analysis["status"] != "succeeded":
        return "manual"
    action = analysis["summary"]["action"]
    level = analysis["summary"]["level"]
    if action == "accept":
        return "accept"
    if action == "note":
        return "accept_with_note"
    if action == "resubmit":
        return "request_new_copy"
    if action == "reject" or level in ("L4", "L5"):
        return "priority_review"
    return "review"
```

## 4. تخزين رقم التقرير وملف PDF

تجلب المهمة التحليل وتوجّهه وتحفظ ما ستحتاج إليه لاحقًا. وهي تعيد استخدام `call` و`route` وبيانات الاعتماد من الخطوات السابقة، وتمثّل `saveCase` و`saveReport` وسيلة التخزين الخاصة بك. نزّل ملف PDF في المهمة نفسها: فنتيجة التحليل تُحفظ 7 أيام، وصور صفحات المستند ومقتطفات نصه 3 ساعات، ثم لا تحتفظ Fileverity بعد ذلك إلا بسجل التحقق، لا بالتقرير الكامل.

**معالجة فحص منتهٍ: JavaScript**

```javascript
export async function handleResult(analysisId) {
  const analysis = await call(`/analyses/${analysisId}`, { headers: auth });
  const decision = route(analysis);
  await saveCase(analysis.metadata.case_id, {
    analysisId: analysis.id,
    reportId: analysis.report_id,
    reportUrl: analysis.report_url,
    summary: analysis.summary,
    model: analysis.model,
    decision,
  });
  if (analysis.status === "succeeded") {
    const pdf = await fetch(`${base}/analyses/${analysis.id}/report.pdf`, { headers: auth });
    await saveReport(analysis.report_id, Buffer.from(await pdf.arrayBuffer()));
  }
  return decision;
}
```

**معالجة فحص منتهٍ: Python**

```python
def handle_result(analysis_id):
    analysis = call("GET", f"/analyses/{analysis_id}")
    decision = route(analysis)
    save_case(analysis["metadata"]["case_id"], {
        "analysis_id": analysis["id"],
        "report_id": analysis["report_id"],
        "report_url": analysis["report_url"],
        "summary": analysis["summary"],
        "model": analysis["model"],
        "decision": decision,
    })
    if analysis["status"] == "succeeded":
        pdf = requests.get(f"{BASE}/analyses/{analysis['id']}/report.pdf", headers=HEADERS, timeout=90)
        pdf.raise_for_status()
        save_report(analysis["report_id"], pdf.content)
    return decision
```

- `report_id` (`FV-XXXX-XXXX-XXXX`) هو مفتاح سجل التدقيق. احفظه مع القضية في قاعدة بياناتك وعلى أي رسالة قرار.
- `model` هو النسخة المؤرَّخة التي أنتجت النتيجة، لتتمكن لاحقًا من تحديد الإصدار الذي فحص المستند بدقة.
- احتفظ بالملف الأصلي في وسيلة التخزين الخاصة بك. تحذف Fileverity الملف المرفوع عند بدء التحليل، ويحتاج الفحص الفاشل إلى رفع جديد. راجع [الاحتفاظ بالبيانات والأمان](https://fileverity.com/ar/docs/security.md).

## 5. تشغيل قائمة المراجعة البشرية

أنشئ مهمة واحدة لكل قضية موجَّهة. وأعطِ المراجع ما يحتاج إليه ليقرر دون أن يستدعي API مرة أخرى:

- معرّف القضية و`level` و`action` و`reason` ومعرّفات المؤشرات في `findings`، مع إرفاق تقرير PDF. ويسرد ملف PDF كل مؤشر مع موضعه وأدلتها.
- الملف الأصلي الذي خزّنته.
- `report_url`، وهو صفحة التحقق العامة. تؤكد هذه الصفحة سجل التقرير وقيمة تجزئة الملف، لكنها لا تغني عن قراءة التقرير.

سجّل اسم المراجع والقرار والوقت في نظامك الخاص، بجوار `report_id`.

> **ملاحظة.** الفحص الذي لم يرصد شيئًا ليس دليلًا على أن المستند أصلي، والمؤشر المرصود ليس دليلًا على الاحتيال. لا ترفض قضية بناءً على نتيجة API وحدها. وبالنسبة إلى مستندات الدخل أو الهوية أو الملكية، تأكد من الجهة المُصدِرة عبر وسيلة اتصال عثرتَ عليها بنفسك. ويشرح [دليل كشف الاحتيال في المستندات](https://fileverity.com/ar/guides/ai-document-fraud-detection) ما ينبغي التحقق منه بحسب نوع المستند.

## 6. التحقق من التقرير لاحقًا

بعد أشهر، يستطيع مدقق أو عميل التحقق من السجل دون حساب في Fileverity. افتح `report_url` المخزَّن. تعرض الصفحة نوع التقرير، والمؤشر المسجَّل والخطوة التالية، وقيمة تجزئة SHA-256 للملف الذي جرى فحصه، والطابع الزمني وفق RFC 3161. أفلت الأصل المخزَّن على الصفحة فيحسب متصفحك قيمة SHA-256 له ويقارنها بالسجل؛ ولا يُرفع أي شيء. وأي نسخة أُعيد حفظها أو حُوِّلت أو عُدِّلت تعطي قيمة تجزئة مختلفة.

يمكنك إجراء المقارنة نفسها في نظامك الخاص:

**حساب قيمة تجزئة الأصل المخزَّن: cURL**

```bash
sha256sum application.pdf
```

**حساب قيمة تجزئة الأصل المخزَّن: JavaScript**

```javascript
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";

const digest = createHash("sha256").update(await readFile("application.pdf")).digest("hex");
console.log(digest);
```

**حساب قيمة تجزئة الأصل المخزَّن: Python**

```python
import hashlib

print(hashlib.sha256(open("application.pdf", "rb").read()).hexdigest())
```

لا يحلّل التحقق الملف مرة أخرى، ولا يشهد بأن المستند أصلي. وإنما يسجّل أي ملف جرى فحصه وما ورد في التقرير ومتى. أما التقرير الكامل فلا يوجد إلا في ملف PDF الذي خزّنته. وحقول التقرير موصوفة في [كائن التقرير](https://fileverity.com/ar/docs/analyses.md#report).

## قبل بدء التشغيل الفعلي

- ابنِ تكاملك باستخدام مفتاح اختبار. يعيد نتيجة نموذجية فورية بالمستوى `L2` والإجراء `accept` دون تشغيل النموذج. أما Webhooks وتنزيل ملف PDF فيتطلبان مفتاحًا حقيقيًا، ويمكنك تجربته برصيد البدء المجاني. ومرّر الملخصات الأخرى من [التحليلات](https://fileverity.com/ar/docs/analyses.md#summary-document) عبر `route` لاختبار كل فرع.
- عالج `503 service_unavailable`: انتظر عدد الثواني الوارد في `retry-after` ثم أعد المحاولة بقيمة `Idempotency-Key` نفسها. راجع [عندما تكون السعة المتاحة مستخدمة بالكامل](https://fileverity.com/ar/docs/analyses.md#busy).
- حدّد حد إنفاق شهريًا على المفتاح، وأضف Webhook للحدث `balance.low`، حتى لا يتوقف الاستقبال بسبب الخطأ `insufficient_balance`. راجع [الأسعار والفوترة](https://fileverity.com/ar/docs/pricing.md#alerts).
- استدعِ API من خادمك واحتفظ بالمفاتيح في خدمة لإدارة الأسرار، ولا تضعها أبدًا في شيفرة المتصفح.
- اذكر قيمة الترويسة `x-request-id` عند [التواصل مع الدعم](https://fileverity.com/ar/contact).

