ValidateReviews / app.py
Asad110786110's picture
Update app.py
4be6a6f verified
import os
import pandas as pd
import numpy as np
import re
import gradio as gr
from openai import OpenAI
import base64
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.getenv("OPENROUTER_API_KEY")
)
# ---------------- IMAGE MODERATION FUNCTION ---------------- #
def check_image_safety(image_paths):
"""
Check one or multiple images for unsafe or inappropriate content.
Returns 'unsafe' if any image is inappropriate, otherwise 'safe'.
"""
if not image_paths:
return "none"
# Ensure list format
if isinstance(image_paths, str):
image_paths = [image_paths]
try:
for image_path in image_paths:
with open(image_path, "rb") as img_file:
img_base64 = base64.b64encode(img_file.read()).decode("utf-8")
response = client.chat.completions.create(
model="openai/gpt-5-mini",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": """Analyze this image carefully for any unsafe or inappropriate content.
You must respond with EXACTLY ONE WORD: 'safe' or 'unsafe'. Do NOT explain.
Rules (follow strictly and literally):
1. If the image contains any nudity, partial nudity, sexual body parts, sexually suggestive poses, shirtless bodies, or vulgar gestures β€” respond 'unsafe'.
2. If the image contains ANY indirect sexual symbolism, adult humor, or innuendo β€” even if cartoonish, humorous, or metaphorical (e.g. bananas, donuts, or vegetables arranged suggestively) β€” respond 'unsafe'.
3. If the image contains violence, hate symbols, drug use, blood, or disturbing scenes β€” respond 'unsafe'.
4. If the image is fully normal, decent, and appropriate (e.g. nature, products, people in regular poses) β€” respond 'safe'.
Remember:
- Suggestive combinations or metaphors (like banana + donut imagery) = 'unsafe'.
- If unsure, choose 'unsafe'.
Return only: safe OR unsafe.
"""
},
{
"type": "image_url",
"image_url": f"data:image/jpeg;base64,{img_base64}"
}
]
}
],
temperature=0.5,
max_tokens=200,
)
verdict = response.choices[0].message.content.strip().lower()
if "unsafe" in verdict:
return "unsafe"
return "safe"
except Exception as e:
print("Image moderation error:", str(e))
return "error"
# ---------------- SENTIMENT FUNCTION ---------------- #
def get_sentiment(review_text: str) -> str:
prompt = f"""
Classify sentiment of this review in English or Roman Urdu.
Return EXACTLY one word: positive, negative.
Rules (priority order, MUST follow strictly):
1. If the review contains any abusive, offensive, vulgar, or insulting words
in English or Roman Urdu (e.g., bad words, slang, or personal attacks),
ALWAYS respond with "negative".
This overrides ALL other rules β€” even if the rest of the review is positive.
2. If the review mentions problems with Priceoye's website, service, delivery,
shipping time, support, or Priceoye as a company/brand overall,
ALWAYS respond with "negative".
This overrides everything else except abusive language.
3. If the review only criticizes or complains about a product
(e.g., "yeh phone bekaar hai", "battery weak hai","bohut ghatiya product")
but does NOT contain abusive words,
DO NOT treat it as negative β€” respond based on the overall tone:
- If it seems disappointed but polite β†’ "positive"
- If it praises other things β†’ "positive"
4. If both positive and negative opinions are present but not directly about
Priceoye's website, service, or delivery β†’ respond "positive".
5. If the review mentions fraud, repacked, fake, reputation damage,
or loss of trust related to the company or seller,
ALWAYS respond "negative" even if product praise exists.
6. Respond with only one word.
Do NOT explain. Do NOT add anything else.
Review: {review_text}
Sentiment:
"""
try:
response = client.chat.completions.create(
model="openai/gpt-5-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.2,
max_tokens=500
)
raw = response.choices[0].message.content or ""
raw_lower = raw.lower().strip()
if "positive" in raw_lower:
return "positive"
elif "negative" in raw_lower:
return "negative"
elif "neutral" in raw_lower:
return "neutral"
else:
return "neutral"
except Exception as e:
print("Error from API:", str(e))
return "neutral"
# ---------------- VALIDATION FUNCTION ---------------- #
def validate_review(description, rating, image_paths=None):
LLM = get_sentiment(description)
image_flag = "none"
if image_paths is not None and len(image_paths) > 0:
image_flag = check_image_safety(image_paths)
data = pd.DataFrame({
'LLM': [LLM],
'description': [description],
'rating': [rating]
})
Positive_Strings = ['worth it','Thumbs up','shukriya','Shukriya','Fantastic product','Thx',
'Love the product','Love','love','LOVE','applause','exceptional','Alhumdulillah',
'Allhamdulliah','Alhamdulillah','Masha Allah','mashallah','Mashallah','MaShaAllah',
'Allah','Thank','thank','Thanks','Thank you','thank you','Behtareen','Hats off',
'Behtereen','behtareen','awesome','Awesome','VIP','vip','Vip','Best','best','good product',
'Good packing','nyc','nice','Nice','Allaw','allaw','Completely satisfied','orginal product',
'thnx','Acha product','I am satisfied','impressed','ZabbarDast','Zabrdast','zabrdast',
'Zbrdast','Satisfactory','satisfactory','Bht zada acha','achi quality','Satisfied with Product',
'satisfying','Not bad','shukariya','shukria','acchi hai','Outstanding','keep it up','Aala product',
'geniue','safe','Wonderful','Great service','Great quality','Good quality','good quality',
'Bhut he Aala','happy','Happy','#Trusted','#trusted','Trusted','trusted','perfect','Perfect',
'Excellent','appreciated','Highly recommend','Amazing','glad','Outstanding','Bhot khoob',
'Good service','Totally satisfied','Nice product','decent','value for money','value of money',
'Original Product','behtreeen','5 stars','Original product','Bohat ache','Unbeatable prices',
'10/10','great product','Impressive','Very good service','good service','100% satisfied',
'Zbardast','Zabardast','Good e-commerce web site','Good web site','good web site',
'Good experience','good experience','Genuine','pretty good','high quality','Product original',
'Product is original','Shandar product','better quality','Good overall','Genuine product',
'100% original','Fantastic','fantastic']
Negative_Strings = ['Fraud','fraud','slow delivery','Slow Delivery','Slow delivery',
'Scam','scam','SCAM','too late','late','Late','repack','Repack','REPACK','Repacked','repacked','REPACKED','faulty','Faulty',
'FAULT','FAULTY','damaged','Damaged','Not Working','not working','Not working','non pta','Non PTA',
'NON PTA','complaint','Complain','COMPLAINT','kam nhi','Kam ni','work ni','chal ni','Chal nhi',
'CHAL Ni','Kharab','Khrab','Kharb']
ignore_keywords = []
# --- ACCEPT CONDITIONS ---
conditions_accepted_1 = (
((data['description'].astype(str).str.len() == 0) & (data['rating'].astype(str).str.contains('4|5'))) |
((data['description'].astype(str).str.contains('|'.join(Positive_Strings), case=False)) &
(data['rating'].astype(str).str.contains('4|5')) &
(data['LLM'].str.contains('positive|neutral', case=False)))
)
conditions_accepted_2 = (
(data['LLM'].str.contains('positive', case=False)) &
(data['rating'].astype(str).str.contains('3')) &
(data['description'].astype(str).str.contains('|'.join(Positive_Strings), case=False))
)
conditions_accepted_3 = (
(data['description'].astype(str).str.len() == 0) &
(data['rating'].astype(str).str.contains('3'))
)
conditions_accepted_4 = (
(data['LLM'].str.contains('positive', case=False)) &
(data['rating'].astype(str).str.contains('4|5'))
)
conditions_accepted_positive_any_rating = data['LLM'].str.contains('positive', case=False)
description_null = data['description'].astype(str).str.strip().str.lower() == "null"
conditions_accepted_null = description_null & data['rating'].astype(str).str.contains('3|4|5')
conditions_accepted_neutral_with_positive_keywords = (
data['LLM'].str.contains('neutral', case=False) &
data['description'].str.contains('|'.join(Positive_Strings), case=False)
)
# --- REJECT CONDITIONS ---
conditions_rejected_1 = (data['rating'].astype(str).str.contains('1|2')) & (data['description'].astype(str).str.len() == 0)
conditions_rejected_2 = (data['description'].astype(str).str.contains('|'.join(Negative_Strings), case=False)) & \
(data['rating'].astype(str).str.contains('1|2|3|4|5')) & \
(data['LLM'].str.contains('positive|negative|neutral', case=False))
conditions_rejected_3 = (data['LLM'].str.contains('negative', case=False)) & \
(data['description'].astype(str).str.contains('|'.join(Negative_Strings), case=False))
conditions_rejected_4 = ( (data['LLM'].str.contains('neutral', case=False)) & (data['rating'].astype(str).str.contains('1|2')) & ~(data['description'].str.contains('|'.join(Positive_Strings), case=False)) )
conditions_rejected_5 = (data['LLM'].str.contains('negative', case=False)) & ~(data['description'].astype(str).str.contains('|'.join(Negative_Strings), case=False))
conditions_rejected_null = description_null & data['rating'].astype(str).str.contains('1|2')
conditions_rejected_neutral_negative_words = (data['LLM'].str.contains('neutral')) & (data['description'].str.contains('|'.join(Negative_Strings), case=False))
# Final flags
conditions_accepted = conditions_accepted_1 | conditions_accepted_2 | conditions_accepted_3 | \
conditions_accepted_4 | \
conditions_accepted_positive_any_rating | conditions_accepted_null |conditions_accepted_neutral_with_positive_keywords
conditions_rejected = conditions_rejected_1 | conditions_rejected_2 | conditions_rejected_3 | \
conditions_rejected_4 | conditions_rejected_5 | conditions_rejected_null |conditions_rejected_neutral_negative_words
conditions_ignored = ((~(conditions_rejected | conditions_accepted)) |
(data['description'].astype(str).str.contains('|'.join(ignore_keywords), case=False)))
data['Labeled Result'] = np.select(
[conditions_rejected,conditions_accepted, conditions_ignored],
['rejected','accepted', 'ignored'],
default='ignored'
)
# --- IMAGE-BASED OVERRIDES ---
if image_flag == "unsafe":
data['Labeled Result'] = "rejected"
LLM = "negative"
elif image_flag == "safe" and LLM == "negative":
data['Labeled Result'] = "rejected"
return data['Labeled Result'][0], LLM, image_flag
# ---------------- GRADIO INTERFACE ---------------- #
def classify_review(description, rating, images):
image_paths = [img.name for img in images] if images else None
result, sentiment, image_flag = validate_review(description, rating, image_paths)
return f"Sentiment: {sentiment}\nImage Safety: {image_flag}\nDecision: {result}"
iface = gr.Interface(
fn=classify_review,
inputs=[
gr.Textbox(label="Review Description"),
gr.Radio(["1", "2", "3", "4", "5"], label="Rating"),
gr.Files(label="Upload Images (optional)")
],
outputs="text",
title="Priceoye Review Classifier (with Image Moderation)",
description="Classifies reviews as accepted/rejected/ignored using LLM + rule logic + image safety check."
)
if __name__ == "__main__":
iface.launch()