Spaces:
Running
Running
# backend.py | |
from flask import Flask, request, jsonify | |
from flask_cors import CORS | |
import re | |
import random | |
from datetime import datetime | |
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, Boolean, Text | |
from sqlalchemy.ext.declarative import declarative_base | |
from sqlalchemy.orm import sessionmaker | |
import os | |
# Set up Flask app | |
app = Flask(__name__) | |
CORS(app) # Enable CORS for all routes | |
# Set up the database with SQLAlchemy | |
Base = declarative_base() | |
class Order(Base): | |
__tablename__ = 'orders' | |
id = Column(Integer, primary_key=True) | |
order_name = Column(String(200), nullable=False) | |
order_type = Column(String(100), nullable=False) | |
weight_kg = Column(Float, nullable=False) | |
pickup_location = Column(String(255), nullable=False) | |
delivery_location = Column(String(255), nullable=False) | |
shipping_date = Column(String(50), nullable=False) | |
payment_method = Column(String(50), nullable=False) | |
dimensions = Column(String(100)) | |
special_instructions = Column(Text) | |
created_at = Column(DateTime, default=datetime.now) | |
completed = Column(Boolean, default=False) | |
def __repr__(self): | |
return f"<Order(id={self.id}, name={self.order_name}, type={self.order_type})>" | |
# Initialize database connection | |
def init_database(): | |
# MySQL connection string - change these values to match your MySQL setup | |
# Format: mysql+pymysql://username:password@hostname/database_name | |
connection_string = os.environ.get( | |
'DATABASE_URL', | |
'mysql+pymysql://transport_user:transport_pass@localhost/transport_orders' | |
) | |
# Fall back to SQLite if MySQL connection fails | |
try: | |
engine = create_engine(connection_string) | |
Base.metadata.create_all(engine) | |
except Exception as e: | |
print(f"MySQL connection failed: {e}. Falling back to SQLite.") | |
connection_string = "sqlite:///transport_orders.db" | |
engine = create_engine(connection_string) | |
Base.metadata.create_all(engine) | |
Session = sessionmaker(bind=engine) | |
return Session() | |
# Initialize database session | |
db_session = init_database() | |
# Enhanced location extraction patterns | |
CITY_NAMES = [ | |
'New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia', | |
'San Antonio', 'San Diego', 'Dallas', 'San Jose', 'Austin', 'Jacksonville', | |
'Fort Worth', 'Columbus', 'San Francisco', 'Charlotte', 'Indianapolis', | |
'Seattle', 'Denver', 'Boston', 'Detroit', 'Nashville', 'Portland', 'Memphis', | |
'Atlanta', 'Miami', 'Las Vegas', 'Washington', 'Baltimore', 'Milwaukee' | |
] | |
# Create a regex pattern from the city names | |
CITIES_PATTERN = '|'.join([re.escape(city) for city in CITY_NAMES]) | |
# IMPROVED Helper function to extract data from text input | |
def extract_order_details(text): | |
# Initialize an empty details dictionary | |
details = { | |
'order_name': None, | |
'order_type': None, | |
'weight_kg': None, | |
'pickup_location': None, | |
'delivery_location': None, | |
'shipping_date': None, | |
'payment_method': None, | |
'dimensions': None, | |
'special_instructions': None | |
} | |
# Extract customer name - look for common name patterns | |
name_match = re.search(r'([A-Z][a-z]+(?: [A-Z][a-z]+)*) (?:has |is |placed |ordered)', text) | |
if name_match: | |
customer_name = name_match.group(1).strip() | |
# Use customer name as part of order name | |
details['order_name'] = f"{customer_name}'s Order" | |
# Extract order type with more flexible pattern | |
product_patterns = [ | |
r'order for(?: a| an)? (.+?)(?:weighing|weight|\.|,)', | |
r'ordered(?: a| an)? (.+?)(?:weighing|weight|\.|,)', | |
r'shipping(?: a| an)? (.+?)(?:weighing|weight|\.|,)', | |
r'ship(?: a| an)? (.+?)(?:weighing|weight|from|to|\.|,)', | |
r'send(?: a| an)? (.+?)(?:weighing|weight|from|to|\.|,)', | |
r'need to ship(?: a| an)? (.+?)(?:weighing|weight|from|to|\.|,)' | |
] | |
for pattern in product_patterns: | |
order_match = re.search(pattern, text, re.IGNORECASE) | |
if order_match: | |
details['order_type'] = order_match.group(1).strip() | |
break | |
# Extract weight with more flexible patterns | |
weight_patterns = [ | |
r'weighing (?:approximately |about |around |)(\d+(?:\.\d+)?)(?: kg| kilograms| kilos)', | |
r'weight (?:of |is |approximately |about |)(\d+(?:\.\d+)?)(?: kg| kilograms| kilos)', | |
r'(\d+(?:\.\d+)?)(?:kg|kilograms|kilos)', | |
r'weighs (?:approximately |about |around |)(\d+(?:\.\d+)?)(?: kg| kilograms| kilos)', | |
r'weighs about (\d+(?:\.\d+)?)(?: kg| kilograms| kilos)', | |
r'about (\d+(?:\.\d+)?)(?:kg|kilograms|kilos)' | |
] | |
for pattern in weight_patterns: | |
weight_match = re.search(pattern, text, re.IGNORECASE) | |
if weight_match: | |
try: | |
details['weight_kg'] = float(weight_match.group(1)) | |
break | |
except: | |
pass | |
# IMPROVED: Extract pickup location with stronger patterns | |
pickup_patterns = [ | |
r'pickup from (.+?)(?:and|will|to|\.|$)', | |
r'pick up from (.+?)(?:and|will|to|\.|$)', | |
r'shipping from (.+?)(?:and|will|to|\.|$)', | |
r'ship from (.+?)(?:and|will|to|\.|$)', | |
r'send from (.+?)(?:and|will|to|\.|$)', | |
r'from ([^,\.]*?(?:' + CITIES_PATTERN + ')[^,\.]*?)(?:and will|will|to|and|,|\.|$)', | |
r'from (.+?)(?:and will be delivered to|to deliver to|and delivery to|to be delivered to|to|\.|$)' | |
] | |
for pattern in pickup_patterns: | |
pickup_match = re.search(pattern, text, re.IGNORECASE) | |
if pickup_match: | |
pickup_loc = pickup_match.group(1).strip() | |
# Make sure pickup location ends with a city name if possible | |
for city in CITY_NAMES: | |
if city.lower() in pickup_loc.lower(): | |
# Ensure the city name is at the end | |
if not pickup_loc.lower().endswith(city.lower()): | |
city_index = pickup_loc.lower().find(city.lower()) | |
pickup_loc = pickup_loc[:city_index + len(city)] | |
details['pickup_location'] = pickup_loc | |
break | |
# IMPROVED: Extract delivery location with stronger patterns | |
delivery_patterns = [ | |
r'delivered to (.+?)(?:The|\.|\,|$)', | |
r'delivery to (.+?)(?:The|\.|\,|$)', | |
r'deliver to (.+?)(?:The|\.|\,|$)', | |
r'to be delivered to (.+?)(?:The|\.|\,|$)', | |
r'to ([^,\.]*?(?:' + CITIES_PATTERN + ')[^,\.]*?)(?:The|\.|,|$)', | |
r'to (.+?)(?:The requested shipping|The shipping|The package|\.|,|$)' | |
] | |
for pattern in delivery_patterns: | |
delivery_match = re.search(pattern, text, re.IGNORECASE) | |
if delivery_match: | |
delivery_loc = delivery_match.group(1).strip() | |
# Make sure delivery location ends with a city name if possible | |
for city in CITY_NAMES: | |
if city.lower() in delivery_loc.lower(): | |
# Ensure the city name is at the end | |
if not delivery_loc.lower().endswith(city.lower()): | |
city_index = delivery_loc.lower().find(city.lower()) | |
delivery_loc = delivery_loc[:city_index + len(city)] | |
details['delivery_location'] = delivery_loc | |
break | |
# Extract shipping date with more flexible patterns | |
date_patterns = [ | |
r'shipping date is (.+?)(?:,|\.|$)', | |
r'shipping date (.+?)(?:,|\.|$)', | |
r'shipped on (.+?)(?:,|\.|$)', | |
r'delivered by (.+?)(?:,|\.|$)', | |
r'delivery date is (.+?)(?:,|\.|$)', | |
r'delivery date (.+?)(?:,|\.|$)', | |
r'deliver by (.+?)(?:,|\.|$)', | |
r'ship on (.+?)(?:,|\.|$)', | |
r'shipping tomorrow', # Special case for "tomorrow" | |
r'shipping today' # Special case for "today" | |
] | |
for pattern in date_patterns: | |
date_match = re.search(pattern, text, re.IGNORECASE) | |
if date_match: | |
if "tomorrow" in pattern: | |
# Calculate tomorrow's date | |
tomorrow = (datetime.now() + datetime.timedelta(days=1)).strftime("%Y-%m-%d") | |
details['shipping_date'] = tomorrow | |
elif "today" in pattern: | |
# Use today's date | |
details['shipping_date'] = datetime.now().strftime("%Y-%m-%d") | |
else: | |
details['shipping_date'] = date_match.group(1).strip() | |
break | |
# Extract payment method with more flexible patterns | |
payment_patterns = [ | |
r'payment method (?:is |selected is |chosen is |)(.+?)(?:\.|\,|The|$)', | |
r'paying (?:with |by |using |via |)(.+?)(?:\.|\,|The|$)', | |
r'paid (?:with |by |using |via |)(.+?)(?:\.|\,|The|$)', | |
r'pay (?:with |by |using |via |)(.+?)(?:\.|\,|The|$)', | |
r'payment is (.+?)(?:\.|\,|The|$)' | |
] | |
for pattern in payment_patterns: | |
payment_match = re.search(pattern, text, re.IGNORECASE) | |
if payment_match: | |
details['payment_method'] = payment_match.group(1).strip() | |
break | |
# Extract dimensions with more flexible patterns | |
dimension_patterns = [ | |
r'dimensions (?:are |is |of |)(.+?)(?:ensuring|to ensure|for|\.|$)', | |
r'size (?:of |is |)(.+?)(?:ensuring|to ensure|for|\.|$)', | |
r'measuring (.+?)(?:ensuring|to ensure|for|\.|$)', | |
r'(\d+(?:\.\d+)?)[xX×](\d+(?:\.\d+)?)[xX×](\d+(?:\.\d+)?)(?: cm| centimeters| inches| in| mm)' | |
] | |
for pattern in dimension_patterns: | |
dim_match = re.search(pattern, text, re.IGNORECASE) | |
if dim_match: | |
if len(dim_match.groups()) == 3: # Direct dimensions format like 10x20x30 | |
details['dimensions'] = f"{dim_match.group(1)}x{dim_match.group(2)}x{dim_match.group(3)}" | |
else: | |
details['dimensions'] = dim_match.group(1).strip() | |
break | |
# Extract special instructions | |
instructions_patterns = [ | |
r'special instructions,? (.+?)\.', | |
r'instructions to (.+?)\.', | |
r'requesting (.+?)\.', | |
r'requested (.+?)\.', | |
r'please (.+?)\.', | |
r'handle with care(?: as| because| since)? (.+?)\.', | |
r'handle with care' # Just the phrase itself | |
] | |
for pattern in instructions_patterns: | |
instr_match = re.search(pattern, text, re.IGNORECASE) | |
if instr_match: | |
if pattern == 'handle with care': | |
details['special_instructions'] = "Handle with care" | |
else: | |
details['special_instructions'] = instr_match.group(1).strip() | |
break | |
# Generate an order name if none provided | |
if not details.get('order_name'): | |
current_date = datetime.now().strftime("%Y%m%d") | |
random_suffix = str(random.randint(1000, 9999)) | |
details['order_name'] = f"ORDER-{current_date}-{random_suffix}" | |
return details | |
# Function to save order to database with validation | |
def save_order(details): | |
try: | |
# Check for required fields | |
required_fields = ['order_name', 'order_type', 'weight_kg', | |
'pickup_location', 'delivery_location', | |
'shipping_date', 'payment_method'] | |
missing_fields = [field for field in required_fields if not details.get(field)] | |
if missing_fields: | |
print(f"Missing required fields: {', '.join(missing_fields)}") | |
return None | |
# Create new order with supplied details | |
new_order = Order( | |
order_name=details.get('order_name'), | |
order_type=details.get('order_type'), | |
weight_kg=details.get('weight_kg'), | |
pickup_location=details.get('pickup_location'), | |
delivery_location=details.get('delivery_location'), | |
shipping_date=details.get('shipping_date'), | |
payment_method=details.get('payment_method'), | |
dimensions=details.get('dimensions'), | |
special_instructions=details.get('special_instructions'), | |
completed=False | |
) | |
db_session.add(new_order) | |
db_session.commit() | |
return new_order.id | |
except Exception as e: | |
db_session.rollback() | |
print(f"Error saving order: {e}") | |
return None | |
# Function to get all orders | |
def get_all_orders(): | |
orders = db_session.query(Order).all() | |
orders_data = [] | |
if orders: | |
for order in orders: | |
orders_data.append({ | |
"ID": order.id, | |
"Name": order.order_name, | |
"Type": order.order_type, | |
"Weight (kg)": order.weight_kg, | |
"From": order.pickup_location, | |
"To": order.delivery_location, | |
"Date": order.shipping_date, | |
"Payment": order.payment_method, | |
"Dimensions": order.dimensions, | |
"Instructions": order.special_instructions, | |
"Created": order.created_at.strftime("%Y-%m-%d %H:%M") if order.created_at else "Unknown" | |
}) | |
return orders_data | |
# API routes | |
def extract_details(): | |
data = request.json | |
if not data or 'text' not in data: | |
return jsonify({"error": "No text provided"}), 400 | |
extracted = extract_order_details(data['text']) | |
return jsonify({"details": extracted}) | |
def api_save_order(): | |
data = request.json | |
if not data: | |
return jsonify({"error": "No order details provided"}), 400 | |
# Validate required fields | |
required_fields = ['order_name', 'order_type', 'weight_kg', | |
'pickup_location', 'delivery_location', | |
'shipping_date', 'payment_method'] | |
missing_fields = [field for field in required_fields if not data.get(field)] | |
if missing_fields: | |
return jsonify({ | |
"error": "Missing required fields", | |
"missing_fields": missing_fields | |
}), 400 | |
order_id = save_order(data) | |
if order_id: | |
return jsonify({"success": True, "order_id": order_id}) | |
else: | |
return jsonify({"error": "Failed to save order"}), 500 | |
def api_get_orders(): | |
orders = get_all_orders() | |
return jsonify({"orders": orders}) | |
def update_field(): | |
data = request.json | |
if not data or 'order_id' not in data or 'field' not in data or 'value' not in data: | |
return jsonify({"error": "Missing required data"}), 400 | |
try: | |
order = db_session.query(Order).filter(Order.id == data['order_id']).first() | |
if not order: | |
return jsonify({"error": "Order not found"}), 404 | |
# Update the specified field exactly as provided | |
setattr(order, data['field'], data['value']) | |
db_session.commit() | |
return jsonify({"success": True}) | |
except Exception as e: | |
db_session.rollback() | |
return jsonify({"error": str(e)}), 500 | |
# Add a debugging endpoint to help diagnose extraction issues | |
def debug_extract(): | |
data = request.json | |
if not data or 'text' not in data: | |
return jsonify({"error": "No text provided"}), 400 | |
# Extract details | |
extracted = extract_order_details(data['text']) | |
# Return both the extracted details and the original text for comparison | |
return jsonify({ | |
"original_text": data['text'], | |
"extracted_details": extracted, | |
"missing_fields": [k for k, v in extracted.items() if v is None] | |
}) | |
if __name__ == '__main__': | |
app.run(debug=True, port=5000) |