Skip to content

1969Mark/oil_equivalent

Repository files navigation

機油等效對照資料庫

把多家品牌的「機油等效對照表」(PDF / Excel)統整成一個 SQLite 資料庫, 作為日後查詢 app 的單一資料來源。

目錄結構

2026_oil_equivalent/
├── raw/                    # 原始檔案(按來源分資料夾,唯讀)
│   ├── lubmarine/          # Total Lubmarine 競品對照表
│   ├── q8oils/             # Q8 Oils 對照表
│   └── safra/              # Safra Química 對照表
├── parsers/                # 各來源的專屬 parser
│   ├── base.py             # 共用 dataclass: Product / Equivalence / ParseResult
│   ├── parse_matrix_pdf.py # 通用「橫式矩陣 PDF」抽取器(pdfplumber)
│   ├── parse_lubmarine_xlsx.py
│   ├── parse_q8_matrix_pdf.py
│   └── parse_safra_matrix_pdf.py
├── staging/                # parser 輸出的中繼 CSV(人類可審)
├── db/equivalents.sqlite   # 統一資料庫(查詢 app 直接讀)
├── manifest.json           # 已處理檔案 hash,避免重複 ingest
├── ingest.py               # 主流程
└── requirements.txt

資料模型

products(id, brand, product_name, category, description, source_file, source_date)
equivalents(product_a_id, product_b_id, confidence, source_file)
-- confidence: 'official' (Lubmarine 原廠對照) | 'closest' (PDF 矩陣)

把資料統一成「產品節點 + 對等關係邊」的圖結構, 查詢任一品牌的型號即可反查所有等效品。

安裝

python -m venv .venv
.venv\Scripts\activate         # Windows
pip install -r requirements.txt

執行 ingest

python ingest.py               # 處理 raw/ 下所有未見過或更動過的檔
python ingest.py --dry-run     # 只產生 staging CSV,不寫入 DB
python ingest.py --reingest    # 砍掉 DB 與 manifest 重新建

ingest.py 會:

  1. 掃描 raw/ 下所有檔案、計算 sha256
  2. manifest.json 比對,跳過已處理且未變動的檔
  3. ROUTES 表把檔案路由到對應的 parser
  4. parser 產出中繼 CSV 到 staging/(供人工檢查)
  5. upsert 進 db/equivalents.sqlite
  6. 更新 manifest.json

加入新檔案的標準流程

情境 A:與既有來源同格式(例如 Lubmarine 出新版)

  1. 把新檔丟進 raw/<品牌>/
  2. python ingest.py --dry-run 先產生中繼 CSV,目視檢查 staging/ 是否合理
  3. python ingest.py 寫入 DB

情境 B:新格式(新品牌、新版面)

  1. 把新檔丟進 raw/<新品牌>/
  2. 先用 pdfplumber.extract_tables()openpyxl 在 REPL 中觀察結構
  3. parsers/ 新增一支 parser:
    • 若是「橫式矩陣 PDF」→ 直接呼叫 parse_matrix_pdf.parse() 並提供 BRAND_COLUMNS / SPEC_COLUMNS / ref_brand 設定(參考 parse_q8_matrix_pdf.pyparse_safra_matrix_pdf.py
    • 若是直式 Excel → 仿 parse_lubmarine_xlsx.py
    • 其他格式 → 自行回傳 ParseResult(products=[...], equivalences=[...])
  4. ingest.pyROUTES 表加上 glob → parser 對應
  5. python ingest.py --dry-run 驗證、再正式 ingest

情境 C:原始檔內容更新(同一檔名、新版本)

  • 直接覆蓋 raw/<品牌>/<檔名>
  • python ingest.py,hash 變動會自動觸發重 parse 與 upsert
  • 注意:不會自動刪除舊資料的 product/edge。 若刪除了某些列,建議用 --reingest 整批重建

目前資料量(首次 ingest 結果)

來源 products equivalences
Lubmarine xlsx 1868 1712(official)
Q8 PDF 727 631(closest,Q8↔他牌)
Safra PDF 458 1930(closest,11 牌全配對)
DB 總計 2973(去重後) 4124

查詢範例

import sqlite3
conn = sqlite3.connect('db/equivalents.sqlite')
q = """
SELECT p2.brand, p2.product_name, e.confidence
FROM products p1
JOIN equivalents e ON e.product_a_id=p1.id OR e.product_b_id=p1.id
JOIN products p2 ON p2.id = CASE WHEN e.product_a_id=p1.id
                                  THEN e.product_b_id ELSE e.product_a_id END
WHERE p1.brand=? AND p1.product_name LIKE ?
"""
for row in conn.execute(q, ('SHELL', '%VALVATA 460%')):
    print(row)

後續延伸方向

  • 查詢 app:可用 Streamlit/FastAPI 直接讀 db/equivalents.sqlite
  • 品名正規化:目前不同來源拼法可能不一(如 5W-40 vs SAE 5W40), 可在 parsers/base.pynormalize_product_name() 統一
  • 多跳查詢:以 equivalents 邊做 BFS 可推導「A↔B↔C」傳遞性等效 (注意 confidence 會逐跳衰減,建議限制 hop 數)
  • 新增掃描影像 PDF 來源:若日後遇到掃描影像 PDF(pdfplumber 抽不到表), 再導入 OCR(Tesseract);目前三份檔案皆有可抽取文字

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors