将扫描表单与系统记录核对
一个设备借用台会扫描每张手写借用单,并把信息录入系统。借用结束前,每份扫描件都要与录入的记录核对:纸面上的姓名、序列号和归还日期是否一致,是否已签名?
| 无误 | 归还日期错误 | 未签名 |
|---|---|---|
![]() | ![]() | ![]() |
工作原理
代码把记录的每个字段转换成一句一眼就能核对的陈述,并按纸面上的写法来写("The return date handwritten on the form is 12 March 2026"),再加一道关于签名栏的问题。四道判断题和扫描件放在同一次请求中。
| 字段核对 | 状态 |
|---|---|
| 任一低于 0.3 | reject,列出未通过的字段 |
| 任一在 0.3 – 0.7 | review |
| 全部 0.7 及以上 | accept |
运行
先下载一次示例包。caches/document_check.json 中录制的回答让脚本无需 API key 即可运行;加 --replay off 则改为调用免费托管 API。
Bash
curl -fLO https://valen-team.github.io/cookbook/valen-cookbook.zip
python -m zipfile -e valen-cookbook.zip .
cd valen-cookbook && python -m pip install -r requirements.txt
python document_check.py
python document_check.py --replay off --base-url https://api.valen-team.workers.dev/v1
代码
document_check.py
"""Check a scanned loan form against the record typed into the system, field by field. Apache-2.0."""
from datetime import date
import json
from common import HERE, client, emit, image_bytes, image_input, parser, request
FIELDS = {"name": "borrower name", "serial": "serial number", "return_date": "return date"}
def written(field, value):
# The record is stored in machine form; ask about it the way a person would read it on paper.
if field != "return_date":
return value
day = date.fromisoformat(value)
return f"{day.day} {day:%B %Y}"
def run(predict, record):
questions = [{"name": field, "type": "predicate",
"instructions": f"The {label} handwritten on the form is {written(field, record[field])}."}
for field, label in FIELDS.items()]
questions.append({"name": "signed", "type": "predicate",
"instructions": "The signature box contains a handwritten signature."})
answers = request(predict, image_input(image_bytes(HERE / "media" / record["scan"])), questions)
checks = {name: answers[name]["probability"] for name in [*FIELDS, "signed"]}
failed = [name for name, p in checks.items() if p < 0.3]
unclear = [name for name, p in checks.items() if 0.3 <= p < 0.7]
status = "reject" if failed else "review" if unclear else "accept"
return {"scan": record["scan"], "status": status, "failed": failed, "unclear": unclear, "checks": checks}
def main():
args = parser(__doc__).parse_args()
records = json.loads((HERE / "document_check_data.json").read_text())
with client(args) as predict:
emit([run(predict, record) for record in records], args)
if __name__ == "__main__":
main()
document_check_data.json
[
{"scan": "loan-a.jpg", "name": "Lin Chen", "serial": "CAM-20417", "return_date": "2026-03-12"},
{"scan": "loan-b.jpg", "name": "Omar Haddad", "serial": "CAM-20562", "return_date": "2026-03-12"},
{"scan": "loan-c.jpg", "name": "Sara Novak", "serial": "LAP-11873", "return_date": "2026-03-19"}
]
输出
录制的输出
[
{
"scan": "loan-a.jpg",
"status": "accept",
"failed": [],
"unclear": [],
"checks": {
"name": 0.9992125595012277,
"serial": 0.9983302126024323,
"return_date": 0.9976673179646833,
"signed": 0.9990492985319737
}
},
{
"scan": "loan-b.jpg",
"status": "reject",
"failed": [
"return_date"
],
"unclear": [],
"checks": {
"name": 0.9986719510816103,
"serial": 0.9984982673413811,
"return_date": 0.007865409520616382,
"signed": 0.997229301770541
}
},
{
"scan": "loan-c.jpg",
"status": "reject",
"failed": [
"signed"
],
"unclear": [],
"checks": {
"name": 0.9989126426472942,
"serial": 0.9990923741991619,
"return_date": 0.997633862429052,
"signed": 0.006129618756955932
}
}
]
改成你自己的
- 用表单自身的写法询问记录内容:日期按书写的样子,姓名按签名的样子。
- 对于长文档,用代码裁出每个字段区域,把裁剪图作为单独的请求发送。
- 这里的表单是用手写字体为本教程生成的。


