Skip to content
Open
122 changes: 111 additions & 11 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,44 @@ def _build_scan_payload(
}


def _build_species_info(species_name: str) -> dict:
species_map = {
"Rohu Carp": {
"common_name": "Rohu Carp",
"scientific_name": "Labeo rohita",
"habitat": "Freshwater",
"tags": ["ROHU CARP", "LABEO ROHITA", "FRESHWATER"],
"weight_estimate_kg": 1.2,
"catch_age_hours": 6,
},
"Catla Carp": {
"common_name": "Catla Carp",
"scientific_name": "Gibelion catla",
"habitat": "Freshwater",
"tags": ["CATLA CARP", "GIBELION CATLA", "FRESHWATER"],
"weight_estimate_kg": 2.4,
"catch_age_hours": 4,
},
"Mrigal Carp": {
"common_name": "Mrigal Carp",
"scientific_name": "Cirrhinus cirrhosus",
"habitat": "Freshwater",
"tags": ["MRIGAL CARP", "CIRRHINUS CIRRHOSUS", "FRESHWATER"],
"weight_estimate_kg": 0.9,
"catch_age_hours": 8,
},
"Unsupported Species": {
"common_name": "Unsupported Species",
"scientific_name": "Unknown specimen",
"habitat": "Unknown",
"tags": ["UNSUPPORTED", "WARNING"],
"weight_estimate_kg": 0.0,
"catch_age_hours": 0,
}
}
return species_map.get(species_name, species_map["Rohu Carp"])


def _row_to_payload(row: dict) -> dict:
freshness = row.get("freshness_index") or 0
is_fresh = freshness >= 65
Expand All @@ -299,15 +337,8 @@ def _row_to_payload(row: dict) -> dict:
"confidence": round((row.get("confidence_score") or 0) * 100, 1),
"classification": "FRESH" if is_fresh else "SPOILED",
"is_fresh": is_fresh,
"uncertain_flag": False,
"species": {
"common_name": "Rohu Carp",
"scientific_name": "Labeo rohita",
"habitat": "Freshwater",
"tags": ["ROHU CARP", "LABEO ROHITA", "FRESHWATER"],
"weight_estimate_kg": 1.2,
"catch_age_hours": 6,
},
"uncertain_flag": (row.get("confidence_score") or 1.0) < 0.70,
"species": _build_species_info(row.get("species_detected") or "Rohu Carp"),
"biomarkers": bm,
"recommendations": {
"consume_within_hours": row.get("storage_hours") or 0,
Expand Down Expand Up @@ -450,6 +481,16 @@ async def process_scan(
score = round((gill + eye + body) / 3.0, 1)
conf = round(random.uniform(0.82, 0.97), 2)

fname = (body_image.filename or "").lower()
if "catla" in fname:
detected_species = "Catla Carp"
elif "mrigal" in fname:
detected_species = "Mrigal Carp"
elif any(k in fname for k in ["salmon", "tilapia", "unsupported", "tuna"]):
detected_species = "Unsupported Species"
else:
detected_species = random.choice(["Rohu Carp", "Catla Carp", "Mrigal Carp"])

demo_fusion = {
"final_score_percent": score,
"final_grade": "A" if score >= 75 else "B" if score >= 60 else "C",
Expand All @@ -462,6 +503,7 @@ async def process_scan(
},
}
payload = _build_scan_payload(demo_fusion, scan_id, display_id)
payload["species"] = _build_species_info(detected_species)

try:
_db().table("scans").insert(
Expand All @@ -474,7 +516,7 @@ async def process_scan(
"image_type": "full_scan",
"freshness_index": payload["freshness_index"],
"scan_display_id": display_id,
"species_detected": "Rohu Carp",
"species_detected": detected_species,
"biomarker_json": payload["biomarkers"],
"storage_hours": payload["recommendations"]["consume_within_hours"],
"alert_flags": payload["recommendations"]["alert_flags"],
Expand Down Expand Up @@ -528,12 +570,59 @@ async def process_scan(
async def scan_auto(
request: Request,
image: UploadFile = File(...),
freshness_label: Optional[str] = Form(None),
fused_score: Optional[float] = Form(None),
source: Optional[str] = Form(None),
confidence_score: Optional[float] = Form(None),
species_detected: Optional[str] = Form(None),
current_user=Depends(get_current_user),
):
image_bytes = await image.read()
scan_id = str(uuid.uuid4())
display_id = _generate_display_id()

# If edge_onnx path is used, save directly and bypass server inference
if source == "edge_onnx" and fused_score is not None:
freshness = int(fused_score * 100)
conf = confidence_score or 0.85
edge_fusion = {
"final_score_percent": freshness,
"final_grade": _to_db_grade(freshness_label or "C"),
"confidence_score": conf,
"uncertain_prediction_flag": conf < 0.70,
"regional_breakdown": {
"gill_freshness_score": fused_score,
"eye_freshness_score": fused_score,
"body_freshness_score": fused_score,
},
}
photo_url = await _upload_image(image_bytes, str(current_user.id), scan_id)
payload = _build_scan_payload(edge_fusion, scan_id, display_id, photo_url)
if species_detected:
payload["species"]["common_name"] = species_detected

try:
_db().table("scans").insert(
{
"id": scan_id,
"user_id": str(current_user.id),
"final_grade": _to_db_grade(payload["grade"]),
"confidence_score": conf,
"image_type": "BODY",
"freshness_index": payload["freshness_index"],
"scan_display_id": display_id,
"species_detected": species_detected or "Rohu Carp",
"biomarker_json": payload["biomarkers"],
"storage_hours": payload["recommendations"]["consume_within_hours"],
"alert_flags": payload["recommendations"]["alert_flags"],
"photo_urls": [photo_url] if photo_url else [],
}
).execute()
except Exception as exc:
print(f"DB write failed (edge_onnx): {exc}")

return {"success": True, "scan": payload}

# ── Demo mode: models not loaded (PyTorch not installed) ─────────────────
if not _models_loaded:
gill = random.randint(68, 96)
Expand All @@ -542,6 +631,16 @@ async def scan_auto(
score = round((gill + eye + body) / 3.0, 1)
conf = round(random.uniform(0.82, 0.97), 2)

fname = (image.filename or "").lower()
if "catla" in fname:
detected_species = "Catla Carp"
elif "mrigal" in fname:
detected_species = "Mrigal Carp"
elif any(k in fname for k in ["salmon", "tilapia", "unsupported", "tuna"]):
detected_species = "Unsupported Species"
else:
detected_species = random.choice(["Rohu Carp", "Catla Carp", "Mrigal Carp"])

demo_fusion = {
"final_score_percent": score,
"confidence_score": conf,
Expand All @@ -554,6 +653,7 @@ async def scan_auto(
}
photo_url = await _upload_image(image_bytes, str(current_user.id), scan_id)
payload = _build_scan_payload(demo_fusion, scan_id, display_id, photo_url)
payload["species"] = _build_species_info(detected_species)

try:
_db().table("scans").insert(
Expand All @@ -565,7 +665,7 @@ async def scan_auto(
"image_type": "BODY",
"freshness_index": payload["freshness_index"],
"scan_display_id": display_id,
"species_detected": "Rohu Carp",
"species_detected": detected_species,
"biomarker_json": payload["biomarkers"],
"storage_hours": payload["recommendations"]["consume_within_hours"],
"alert_flags": payload["recommendations"]["alert_flags"],
Expand Down
74 changes: 73 additions & 1 deletion backend/vendors.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from fastapi import APIRouter, HTTPException, Depends, Query
from fastapi import APIRouter, HTTPException, Depends, Query, Request
from datetime import datetime, timedelta, timezone
from auth import get_current_user
from fastapi_cache import FastAPICache

router = APIRouter(prefix="/api/v1/vendors", tags=["vendors"])

# In-memory reviews database fallback
_REVIEWS_DB = {}


def _compute_badge(avg_score: float, total_scans: int) -> str:
if total_scans < 5:
Expand Down Expand Up @@ -96,6 +99,75 @@ async def get_vendor_trust_score(vendor_id: str):
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc))

@router.get("/{vendor_id}/reviews")
async def get_vendor_reviews(vendor_id: str):
reviews = _REVIEWS_DB.get(vendor_id, [
{
"id": "rev-1",
"author": "Ankit R.",
"rating": 5,
"comment": "Consistently fresh rohu fish. Highly recommended!",
"timestamp": (datetime.now(timezone.utc) - timedelta(days=2)).isoformat()
},
{
"id": "rev-2",
"author": "Deepika S.",
"rating": 4,
"comment": "Good quality scales, operculum is bright red. Fair pricing.",
"timestamp": (datetime.now(timezone.utc) - timedelta(days=5)).isoformat()
}
])
return {"success": True, "reviews": reviews}

@router.post("/{vendor_id}/reviews")
async def add_vendor_review(
vendor_id: str,
review_data: dict,
request: Request
):
author = "Anonymous Consumer"
try:
auth_header = request.headers.get("Authorization")
if auth_header:
# We can call get_current_user dynamically
user = await get_current_user(request)
if user:
author = user.user_metadata.get("full_name") or user.email
except Exception:
pass

rating = review_data.get("rating", 5)
comment = review_data.get("comment", "")

new_review = {
"id": f"rev-{datetime.now(timezone.utc).timestamp()}",
"author": author,
"rating": int(rating),
"comment": comment,
"timestamp": datetime.now(timezone.utc).isoformat()
}

if vendor_id not in _REVIEWS_DB:
_REVIEWS_DB[vendor_id] = [
{
"id": "rev-1",
"author": "Ankit R.",
"rating": 5,
"comment": "Consistently fresh rohu fish. Highly recommended!",
"timestamp": (datetime.now(timezone.utc) - timedelta(days=2)).isoformat()
},
{
"id": "rev-2",
"author": "Deepika S.",
"rating": 4,
"comment": "Good quality scales, operculum is bright red. Fair pricing.",
"timestamp": (datetime.now(timezone.utc) - timedelta(days=5)).isoformat()
}
]

_REVIEWS_DB[vendor_id].insert(0, new_review)
return {"success": True, "review": new_review}

@router.post("/{vendor_id}/recalculate")
async def recalculate_trust_score(
vendor_id: str,
Expand Down
Loading
Loading