feat: 百度通用文字识别提取供应商/日期(config api_key 持久化)

This commit is contained in:
2026-07-19 19:23:38 +08:00
parent cc66448327
commit 056e7d8e75
14 changed files with 1198 additions and 17 deletions
+143
View File
@@ -0,0 +1,143 @@
"""OrderMetadataExtractor 单元测试。"""
import unittest
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from app.core.ocr.metadata_extractor import (
OrderMetadataExtractor, OrderMetadata, sanitize_for_filename,
)
class TestSupplierExtraction(unittest.TestCase):
def setUp(self):
self.ext = OrderMetadataExtractor()
def test_keyword_supplier(self):
text = "永辉超市供货单\n日期: 2026-07-17\n商品名称 条形码..."
m = self.ext.extract(text)
self.assertIn("永辉", m.supplier)
self.assertIn("供货单", m.supplier)
def test_supplier_keyword_no_data(self):
text = "供应商: 益选烟酒商行\n日期: 2026-07-17"
m = self.ext.extract(text)
self.assertIn("益选", m.supplier)
def test_fallback_supplier(self):
text = "杭州朝阳酒业有限公司\n日期: 2026-07-17"
m = self.ext.extract(text)
self.assertIn("朝阳", m.supplier)
def test_date_only_excluded(self):
"""纯日期行不该被当作供应商。"""
text = "单据日期:2026-07-17\n成都瑞晨丰商贸销售出库单\n..."
m = self.ext.extract(text)
self.assertNotIn("日期", m.supplier)
self.assertIn("瑞晨丰", m.supplier)
def test_date_prefix_excluded(self):
""""日期"开头的行不该被当作供应商。"""
text = "日期 2026-07-17\n永辉超市供货单\n..."
m = self.ext.extract(text)
self.assertNotIn("日期", m.supplier)
self.assertIn("永辉", m.supplier)
class TestDateExtraction(unittest.TestCase):
def setUp(self):
self.ext = OrderMetadataExtractor()
def test_dash_date(self):
m = self.ext.extract("日期 2026-07-17 其他内容")
self.assertEqual(m.bill_date, "20260717")
def test_slash_date(self):
m = self.ext.extract("日期 2026/07/17 其他内容")
self.assertEqual(m.bill_date, "20260717")
def test_dot_date(self):
m = self.ext.extract("日期 2026.07.17 其他内容")
self.assertEqual(m.bill_date, "20260717")
def test_chinese_date(self):
m = self.ext.extract("日期 2026年7月17日 其他内容")
self.assertEqual(m.bill_date, "20260717")
def test_compact_date(self):
m = self.ext.extract("日期 20260717 其他内容")
self.assertEqual(m.bill_date, "20260717")
def test_invalid_date(self):
m = self.ext.extract("日期 1800-01-01 其他内容") # 年份越界
self.assertEqual(m.bill_date, "")
def test_no_date(self):
m = self.ext.extract("没有任何日期")
self.assertEqual(m.bill_date, "")
class TestAmountExtraction(unittest.TestCase):
def setUp(self):
self.ext = OrderMetadataExtractor()
def test_payable_amount(self):
text = "商品名称 条形码\n应付金额 656.00\n总计 12 件 656.00"
m = self.ext.extract(text)
self.assertEqual(m.total_amount, 656.00)
def test_total_only(self):
text = "总计 12 件 656.00"
m = self.ext.extract(text)
self.assertEqual(m.total_amount, 656.00)
def test_max_amount_fallback(self):
text = "55.00 62.00 57.00 33.50\n各种单价"
m = self.ext.extract(text)
self.assertEqual(m.total_amount, 62.00)
def test_no_amount(self):
text = "没有任何数字123"
m = self.ext.extract(text)
self.assertEqual(m.total_amount, 0.0)
class TestCombined(unittest.TestCase):
def setUp(self):
self.ext = OrderMetadataExtractor()
def test_realistic(self):
text = """永辉超市供货单
单据日期: 2026-07-17
商品名称 条形码 数量 单价 金额
550水 6921168509256 1箱 55.00 55.00
茶π 6921168599905 3箱 62.00 186.00
应付金额 241.00"""
m = self.ext.extract(text)
self.assertIn("永辉", m.supplier)
self.assertEqual(m.bill_date, "20260717")
self.assertEqual(m.total_amount, 241.00)
self.assertTrue(m.is_complete())
def test_all_missing(self):
text = "asdfgh 12345"
m = self.ext.extract(text)
self.assertEqual(m.supplier, "")
self.assertEqual(m.bill_date, "")
self.assertEqual(m.total_amount, 0.0)
self.assertFalse(m.is_complete())
class TestSanitize(unittest.TestCase):
def test_windows_illegal_chars(self):
self.assertEqual(sanitize_for_filename("a/b\\c:d*e?f\"g<h>i|j"), "a_b_c_d_e_f_g_h_i_j")
def test_empty(self):
self.assertEqual(sanitize_for_filename(""), "")
def test_normal(self):
self.assertEqual(sanitize_for_filename("永辉超市"), "永辉超市")
if __name__ == '__main__':
unittest.main()
+60
View File
@@ -0,0 +1,60 @@
"""OrderMetadataDB 单元测试。"""
import unittest
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from app.core.db.order_metadata_db import OrderMetadataDB
class TestOrderMetadataDB(unittest.TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
self.db_path = str(Path(self.tmpdir) / "test_cache.db")
self.db = OrderMetadataDB(self.db_path)
def test_save_and_get(self):
ok = self.db.save("hash001", supplier="永辉超市", bill_date="20260717",
total_amount=656.00, raw_supplier_text="永辉超市供货单")
self.assertTrue(ok)
row = self.db.get("hash001")
self.assertIsNotNone(row)
self.assertEqual(row["supplier"], "永辉超市")
self.assertEqual(row["bill_date"], "20260717")
self.assertAlmostEqual(row["total_amount"], 656.00)
def test_upsert(self):
self.db.save("hash001", supplier="永辉", bill_date="20260717", total_amount=100)
self.db.save("hash001", supplier="永辉2", bill_date="20260718", total_amount=200)
row = self.db.get("hash001")
self.assertEqual(row["supplier"], "永辉2")
self.assertEqual(row["bill_date"], "20260718")
self.assertAlmostEqual(row["total_amount"], 200)
def test_get_missing(self):
self.assertIsNone(self.db.get("nonexistent"))
def test_list_all(self):
for i in range(3):
self.db.save(f"hash{i}", supplier=f"s{i}")
rows = self.db.list_all()
self.assertEqual(len(rows), 3)
def test_count(self):
self.assertEqual(self.db.count(), 0)
self.db.save("h1")
self.db.save("h2")
self.assertEqual(self.db.count(), 2)
def test_empty_hash_skipped(self):
self.assertFalse(self.db.save("", supplier="x"))
def test_delete(self):
self.db.save("h1")
self.db.delete("h1")
self.assertIsNone(self.db.get("h1"))
if __name__ == '__main__':
unittest.main()