月次の発注書が3ページに渡ることがある。
1枚目にカレンダー表、2枚目に備考、3枚目に追記。こういう帳票を「まとめて1つの注文として処理したい」という要件は当然だ。1枚ずつ別の注文として登録して後からまとめる、という操作は担当者には頼めない。
複数枚の画像を1つの注文として扱う機能が必要だった。
ファイルの受け取り方
ブラウザのinputにmultiple属性を加える。それだけで複数ファイルの選択はできる。
<input type="file" id="fax-upload" accept="image/*,.pdf" multiple>
FastAPIはList[UploadFile]で受け取る。
@app.post('/api/orders/upload')
async def upload_order(
files: List[UploadFile] = File(...),
supplier: str = Form(...)
):
order_id = create_order_record(supplier)
for i, file in enumerate(files):
save_image(order_id, file, page_no=i+1)
background_tasks.add_task(process_all_pages, order_id)
return {'order_id': order_id}
page_noで順番を管理する。ページ1・2・3という順序は、担当者がファイルを選択した順番に対応する。
サムネイルストリップ
確認画面の画像エリアに、全ページのサムネイルを横に並べるストリップUIを実装した。
function renderThumbnails(images) {
const strip = document.getElementById('thumb-strip');
strip.innerHTML = '';
images.forEach((img, i) => {
const thumb = document.createElement('img');
thumb.src = `/api/orders/${orderId}/images/${i+1}/thumbnail`;
thumb.className = 'thumbnail';
thumb.addEventListener('click', () => showPage(i+1));
strip.appendChild(thumb);
});
}
サムネイル画像はFastAPIがPillowで200px幅にリサイズして返す。FAXのJPEGは数MBになることがある。元サイズをサムネイルとして毎回送っていたら、確認画面を開くたびに重くなる。リサイズしてキャッシュする。
モーダル内のページナビ
画像をクリックすると拡大モーダルが開く。FAX画像の文字は小さいことが多い。拡大して確認したい場面がある。
モーダル内に「◀ 前のページ」「次のページ ▶」のボタンと現在のページ数(3/5など)を置いた。
let currentPage = 1;
const totalPages = images.length;
document.getElementById('modal-prev').addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
updateModal();
}
});
document.getElementById('modal-next').addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
updateModal();
}
});
確認画面を閉じずにページを切り替えられる。担当者が「1枚目と3枚目を見比べたい」という場面でも対応できる。
画像の回転
FAXは事務所の機械でスキャンされる。スキャンの向きが90度や180度回転していることがある。
確認画面に「90度回転」ボタンを置いた。CSSのtransformで回転させるだけだ。サーバーのファイルは変えない。
let rotation = 0;
document.getElementById('rotate-btn').addEventListener('click', () => {
rotation = (rotation + 90) % 360;
document.getElementById('main-image').style.transform = `rotate(${rotation}deg)`;
});
「画像が横向きだったため読取結果がおかしい」という場合、回転して確認できる。AI読取はアップロード時に既に実行済みのため、回転後の再読取は別のオペレーションとして実装した。
PDFへの対応
取引先によってはFAXがPDF形式で届く。PDFはそのままimg要素では表示できない。
pdf2imageライブラリでページごとにJPEGに変換してから保存する。
from pdf2image import convert_from_bytes
def save_pdf_as_images(order_id: int, pdf_bytes: bytes):
images = convert_from_bytes(pdf_bytes, dpi=200)
for i, image in enumerate(images):
path = f'/var/orderocr/images/{order_id}/page_{i+1}.jpg'
image.save(path, 'JPEG')
save_image_record(order_id, path, page_no=i+1)
DPIは200に設定した。低すぎると文字がつぶれてAI読取の精度が下がる。高すぎるとファイルが重くなる。200は精度とサイズの折衷点として、実験で確認した値だ。
*次回は「HTTPS + Basic認証 + EC2相乗り——本番環境の構築」*
*シンプルシステム株式会社 代表 伊藤勝彦*