Add asset lookup from canteen DB — auto-match OCR machine IDs to asset records
This commit is contained in:
Binary file not shown.
@@ -116,6 +116,11 @@ async def analyze_photo(file: UploadFile = File(...)):
|
|||||||
exif_result = extract_exif(contents)
|
exif_result = extract_exif(contents)
|
||||||
ocr_result = run_ocr(contents) if HAS_TESSERACT else {"available": False, "text": ""}
|
ocr_result = run_ocr(contents) if HAS_TESSERACT else {"available": False, "text": ""}
|
||||||
|
|
||||||
|
# Extract machine ID from OCR for auto-lookup
|
||||||
|
machine_id = None
|
||||||
|
if ocr_result.get("match_5dash6"):
|
||||||
|
machine_id = re.sub(r"\D", "", ocr_result["match_5dash6"])[-5:]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"filename": file.filename,
|
"filename": file.filename,
|
||||||
"saved_as": fname,
|
"saved_as": fname,
|
||||||
@@ -123,9 +128,60 @@ async def analyze_photo(file: UploadFile = File(...)):
|
|||||||
"file_size_kb": round(file_size / 1024, 1),
|
"file_size_kb": round(file_size / 1024, 1),
|
||||||
"exif": exif_result,
|
"exif": exif_result,
|
||||||
"ocr": ocr_result,
|
"ocr": ocr_result,
|
||||||
|
"machine_id": machine_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/lookup")
|
||||||
|
async def lookup_asset(machine_id: str = ""):
|
||||||
|
"""Look up an asset by machine_id in the canteen assets database."""
|
||||||
|
if not machine_id or not machine_id.strip():
|
||||||
|
return {"found": False, "reason": "No machine_id provided"}
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
from pathlib import Path as _Path
|
||||||
|
|
||||||
|
db_path = _Path(__file__).parent.parent / "canteen-asset-tracker" / "assets.db"
|
||||||
|
if not db_path.exists():
|
||||||
|
return {"found": False, "reason": f"Database not found at {db_path}"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(str(db_path))
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT id, machine_id, name, category, status, address, building_name, "
|
||||||
|
"floor, room, latitude, longitude, make, model, description, photo_path "
|
||||||
|
"FROM assets WHERE machine_id = ?",
|
||||||
|
(machine_id.strip(),),
|
||||||
|
).fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
return {
|
||||||
|
"found": True,
|
||||||
|
"asset": {
|
||||||
|
"id": row["id"],
|
||||||
|
"machine_id": row["machine_id"],
|
||||||
|
"name": row["name"],
|
||||||
|
"category": row["category"],
|
||||||
|
"status": row["status"],
|
||||||
|
"address": row["address"],
|
||||||
|
"building_name": row["building_name"],
|
||||||
|
"floor": row["floor"],
|
||||||
|
"room": row["room"],
|
||||||
|
"latitude": row["latitude"],
|
||||||
|
"longitude": row["longitude"],
|
||||||
|
"make": row["make"],
|
||||||
|
"model": row["model"],
|
||||||
|
"description": row["description"],
|
||||||
|
"photo_path": row["photo_path"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return {"found": False, "reason": f"No asset with machine_id '{machine_id}'"}
|
||||||
|
except Exception as e:
|
||||||
|
return {"found": False, "reason": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/uploads")
|
@app.get("/api/uploads")
|
||||||
async def list_uploads():
|
async def list_uploads():
|
||||||
"""List previously uploaded files."""
|
"""List previously uploaded files."""
|
||||||
|
|||||||
+57
-1
@@ -155,6 +155,23 @@
|
|||||||
}
|
}
|
||||||
.filter-chip.active { background: var(--accent); border-color: var(--accent); color: #fff; }
|
.filter-chip.active { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||||
|
|
||||||
|
/* Asset match card */
|
||||||
|
.asset-card {
|
||||||
|
background: var(--card2); border-radius: var(--radius-sm);
|
||||||
|
padding: 12px; margin-top: 10px; border-left: 3px solid var(--green);
|
||||||
|
}
|
||||||
|
.asset-card .asset-name {
|
||||||
|
font-size: 15px; font-weight: 700; margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.asset-card .asset-meta {
|
||||||
|
font-size: 12px; color: var(--text2);
|
||||||
|
display: flex; flex-wrap: wrap; gap: 8px;
|
||||||
|
}
|
||||||
|
.asset-card .asset-meta span {
|
||||||
|
background: var(--card); padding: 2px 8px; border-radius: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* Server section */
|
/* Server section */
|
||||||
.section {
|
.section {
|
||||||
background: var(--card); border-radius: var(--radius);
|
background: var(--card); border-radius: var(--radius);
|
||||||
@@ -410,7 +427,10 @@ async function uploadSelected() {
|
|||||||
html += '<div class="ocr-text">' + esc(ocr.raw_text) + '</div>';
|
html += '<div class="ocr-text">' + esc(ocr.raw_text) + '</div>';
|
||||||
}
|
}
|
||||||
if (ocr.match_5dash6) {
|
if (ocr.match_5dash6) {
|
||||||
html += '<div style="margin-top:4px;color:var(--green);">✅ Matched: <strong>' + esc(ocr.match_5dash6) + '</strong> → machine ID: ' + ocr.match_5dash6.replace(/[^0-9]/g,'').slice(-5) + '</div>';
|
const mid = ocr.match_5dash6.replace(/[^0-9]/g,'').slice(-5);
|
||||||
|
html += '<div style="margin-top:4px;color:var(--green);">✅ Matched: <strong>' + esc(ocr.match_5dash6) + '</strong> → machine ID: ' + mid + '</div>';
|
||||||
|
// Auto-lookup
|
||||||
|
lookupAsset(mid);
|
||||||
} else if (ocr.match_5plus) {
|
} else if (ocr.match_5plus) {
|
||||||
html += '<div style="margin-top:4px;color:var(--amber);">⚠️ Digits: <strong>' + esc(ocr.match_5plus) + '</strong> (no 5-6 pattern)</div>';
|
html += '<div style="margin-top:4px;color:var(--amber);">⚠️ Digits: <strong>' + esc(ocr.match_5plus) + '</strong> (no 5-6 pattern)</div>';
|
||||||
} else {
|
} else {
|
||||||
@@ -424,6 +444,42 @@ async function uploadSelected() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function lookupAsset(machineId) {
|
||||||
|
const div = document.getElementById('serverResults');
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/lookup?machine_id=' + encodeURIComponent(machineId));
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.found) {
|
||||||
|
const a = data.asset;
|
||||||
|
let extra = '';
|
||||||
|
if (a.latitude && a.longitude) {
|
||||||
|
extra += '<span>📍 ' + Number(a.latitude).toFixed(5) + ', ' + Number(a.longitude).toFixed(5) + '</span>';
|
||||||
|
}
|
||||||
|
if (a.address) extra += '<span>🏠 ' + esc(a.address) + '</span>';
|
||||||
|
if (a.building_name) extra += '<span>🏢 ' + esc(a.building_name) + '</span>';
|
||||||
|
if (a.floor) extra += '<span>📶 Floor ' + esc(a.floor) + '</span>';
|
||||||
|
if (a.room) extra += '<span>🚪 ' + esc(a.room) + '</span>';
|
||||||
|
|
||||||
|
div.innerHTML +=
|
||||||
|
'<div class="asset-card">' +
|
||||||
|
'<div class="asset-name">' + esc(a.name) + '</div>' +
|
||||||
|
'<div class="asset-meta">' +
|
||||||
|
'<span>🆔 ' + esc(a.machine_id) + '</span>' +
|
||||||
|
'<span>📦 ' + esc(a.category) + '</span>' +
|
||||||
|
'<span>' + (a.status === 'active' ? '🟢 Active' : '⚪ ' + esc(a.status)) + '</span>' +
|
||||||
|
extra +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
} else {
|
||||||
|
div.innerHTML +=
|
||||||
|
'<div style="margin-top:6px;color:var(--amber);font-size:12px;">⚠️ No asset found for machine ID <strong>' + esc(machineId) + '</strong></div>';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
div.innerHTML +=
|
||||||
|
'<div style="margin-top:6px;color:var(--red);font-size:12px;">❌ Lookup error: ' + esc(e.message) + '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resetAll() {
|
function resetAll() {
|
||||||
allPhotos = [];
|
allPhotos = [];
|
||||||
selectedIdx = -1;
|
selectedIdx = -1;
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Reference in New Issue
Block a user