LAE-log/backend/app.py
2026-05-16 11:50:33 +08:00

636 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import os
import uuid
from datetime import datetime
from flask import Flask, jsonify, request, send_from_directory
from flask_cors import CORS
app = Flask(__name__, static_folder='../platform', static_url_path='')
CORS(app)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_DIR = os.path.join(BASE_DIR, 'db')
def load_json(filename):
path = os.path.join(DB_DIR, filename)
if not os.path.exists(path):
return []
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def save_json(filename, data):
path = os.path.join(DB_DIR, filename)
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# ── helpers ──
def gen_id(prefix='ORD'):
return f"{prefix}-{uuid.uuid4().hex[:8].upper()}"
def calc_distance(lat1, lng1, lat2, lng2):
from math import radians, cos, sin, asin, sqrt
r = 6371
d_lat = radians(lat2 - lat1)
d_lng = radians(lng2 - lng1)
a = sin(d_lat/2)**2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(d_lng/2)**2
return round(2 * r * asin(sqrt(a)), 1)
def calc_price(dist_km, weight_kg, urgency):
base = dist_km * 12 + weight_kg * 5
if urgency == 'urgent':
base *= 1.3
elif urgency == 'ASAP':
base *= 1.5
return max(int(base), 30)
# ── static files ──
@app.route('/')
def index():
return send_from_directory(app.static_folder, 'index.html')
# ── Auth ──
@app.route('/api/login', methods=['POST'])
def login():
data = request.get_json()
phone = data.get('phone', '')
role = data.get('role', 'demander')
users = load_json('users.json')
user = next((u for u in users if u['phone'] == phone), None)
if not user:
user = {
'id': gen_id('U'),
'name': phone[-4:],
'role': role,
'phone': phone,
'company': '',
'avatar': phone[-1],
'credit_score': 800,
'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
users.append(user)
save_json('users.json', users)
return jsonify({'code': 0, 'data': user})
# ── Users ──
@app.route('/api/users/<user_id>')
def get_user(user_id):
users = load_json('users.json')
user = next((u for u in users if u['id'] == user_id), None)
if not user:
return jsonify({'code': 404, 'msg': '用户不存在'}), 404
return jsonify({'code': 0, 'data': user})
@app.route('/api/users/<user_id>', methods=['PUT'])
def update_user(user_id):
users = load_json('users.json')
idx = next((i for i, u in enumerate(users) if u['id'] == user_id), None)
if idx is None:
return jsonify({'code': 404, 'msg': '用户不存在'}), 404
data = request.get_json()
for k in ['name', 'company', 'phone']:
if k in data:
users[idx][k] = data[k]
save_json('users.json', users)
return jsonify({'code': 0, 'data': users[idx]})
# ── Drones ──
@app.route('/api/drones')
def list_drones():
drones = load_json('drones.json')
status = request.args.get('status')
user_id = request.args.get('user_id')
if status:
drones = [d for d in drones if d['status'] == status]
if user_id:
drones = [d for d in drones if d['user_id'] == user_id]
return jsonify({'code': 0, 'data': drones})
@app.route('/api/drones/available')
def available_drones():
drones = load_json('drones.json')
available = [d for d in drones if d['status'] == 'available']
return jsonify({'code': 0, 'data': available})
@app.route('/api/drones', methods=['POST'])
def add_drone():
data = request.get_json()
drones = load_json('drones.json')
drone = {
'id': gen_id('D'),
'name': data.get('name', '未知机型'),
'model': data.get('model', '通用'),
'max_weight': data.get('max_weight', 10),
'range_km': data.get('range_km', 10),
'status': 'available',
'location': data.get('location', '未设置'),
'total_flights': 0,
'user_id': data.get('user_id'),
'price_per_km': data.get('price_per_km', 15),
'insurance_expiry': data.get('insurance_expiry', '2026-12-31')
}
drones.append(drone)
save_json('drones.json', drones)
return jsonify({'code': 0, 'data': drone}), 201
@app.route('/api/drones/<drone_id>', methods=['PUT'])
def update_drone(drone_id):
drones = load_json('drones.json')
idx = next((i for i, d in enumerate(drones) if d['id'] == drone_id), None)
if idx is None:
return jsonify({'code': 404, 'msg': '无人机不存在'}), 404
data = request.get_json()
for k in ['name', 'status', 'location', 'price_per_km']:
if k in data:
drones[idx][k] = data[k]
save_json('drones.json', drones)
return jsonify({'code': 0, 'data': drones[idx]})
# ── Orders ──
@app.route('/api/orders')
def list_orders():
orders = load_json('orders.json')
role = request.args.get('role')
user_id = request.args.get('user_id')
status = request.args.get('status')
if role == 'demander' and user_id:
orders = [o for o in orders if o['demander_id'] == user_id]
elif role == 'provider' and user_id:
orders = [o for o in orders if o.get('provider_id') == user_id]
if status:
orders = [o for o in orders if o['status'] == status]
else:
ordering = {'pending': 0, 'matching': 1, 'accepted': 2,
'in_transit': 3, 'completed': 4, 'cancelled': 5}
orders.sort(key=lambda o: ordering.get(o['status'], 9))
return jsonify({'code': 0, 'data': orders})
@app.route('/api/orders/pending')
def pending_orders():
orders = load_json('orders.json')
pending = [o for o in orders if o['status'] == 'pending']
return jsonify({'code': 0, 'data': pending})
@app.route('/api/orders', methods=['POST'])
def create_order():
data = request.get_json()
orders = load_json('orders.json')
order = {
'id': gen_id('ORDER'),
'from_addr': data['from_addr'],
'to_addr': data['to_addr'],
'from_lat': data.get('from_lat', 39.904),
'from_lng': data.get('from_lng', 116.407),
'to_lat': data.get('to_lat', 39.904),
'to_lng': data.get('to_lng', 116.407),
'cargo': data.get('cargo', '普通包裹'),
'weight': float(data.get('weight', 1)),
'price': 0,
'distance_km': 0,
'status': 'pending',
'urgency': data.get('urgency', 'normal'),
'insurance': float(data.get('insurance', 0)),
'note': data.get('note', ''),
'demander_id': data.get('user_id', ''),
'provider_id': None,
'drone_id': None,
'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'updated_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'completed_at': None,
'rating': None,
'comment': None
}
dist = calc_distance(order['from_lat'], order['from_lng'],
order['to_lat'], order['to_lng'])
price = calc_price(dist, order['weight'], order['urgency'])
order['distance_km'] = dist
order['price'] = price
orders.insert(0, order)
save_json('orders.json', orders)
# auto generate quotes from available providers
auto_generate_quotes(order)
return jsonify({'code': 0, 'data': order}), 201
def auto_generate_quotes(order):
drones = load_json('drones.json')
users = load_json('users.json')
quotes = load_json('quotes.json')
available = [d for d in drones if d['status'] == 'available']
for drone in available[:3]:
provider = next((u for u in users if u['id'] == drone['user_id']), None)
if not provider:
continue
base_price = order['price']
variance = 0.8 + (hash(drone['id']) % 40) / 100
quote = {
'id': gen_id('Q'),
'order_id': order['id'],
'provider_id': drone['user_id'],
'drone_id': drone['id'],
'price': int(base_price * variance),
'eta_minutes': max(int(order['distance_km'] / 1.2 * (0.8 + (hash(drone['name']) % 40) / 100)), 5),
'distance_km': order['distance_km'],
'provider_name': provider['name'],
'drone_name': drone['name'],
'provider_rating': provider.get('credit_score', 800) / 100 * 0.5 + 2.5,
'provider_flights': drone['total_flights'],
'status': 'active',
'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
quote['provider_rating'] = round(min(quote['provider_rating'], 5.0), 1)
quotes.append(quote)
save_json('quotes.json', quotes)
@app.route('/api/orders/<order_id>')
def get_order(order_id):
orders = load_json('orders.json')
order = next((o for o in orders if o['id'] == order_id), None)
if not order:
return jsonify({'code': 404, 'msg': '订单不存在'}), 404
return jsonify({'code': 0, 'data': order})
@app.route('/api/orders/<order_id>/accept', methods=['POST'])
def accept_order(order_id):
orders = load_json('orders.json')
idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None)
if idx is None:
return jsonify({'code': 404, 'msg': '订单不存在'}), 404
data = request.get_json()
orders[idx]['status'] = 'accepted'
orders[idx]['provider_id'] = data.get('provider_id')
orders[idx]['drone_id'] = data.get('drone_id')
orders[idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# update drone status
drone_id = data.get('drone_id')
if drone_id:
drones = load_json('drones.json')
for d in drones:
if d['id'] == drone_id:
d['status'] = 'busy'
break
save_json('drones.json', drones)
save_json('orders.json', orders)
return jsonify({'code': 0, 'data': orders[idx]})
@app.route('/api/orders/<order_id>/transit', methods=['POST'])
def start_transit(order_id):
orders = load_json('orders.json')
idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None)
if idx is None:
return jsonify({'code': 404, 'msg': '订单不存在'}), 404
orders[idx]['status'] = 'in_transit'
orders[idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
save_json('orders.json', orders)
return jsonify({'code': 0, 'data': orders[idx]})
@app.route('/api/orders/<order_id>/complete', methods=['POST'])
def complete_order(order_id):
orders = load_json('orders.json')
idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None)
if idx is None:
return jsonify({'code': 404, 'msg': '订单不存在'}), 404
orders[idx]['status'] = 'completed'
orders[idx]['completed_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
orders[idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# release drone
drone_id = orders[idx].get('drone_id')
if drone_id:
drones = load_json('drones.json')
for d in drones:
if d['id'] == drone_id:
d['status'] = 'available'
d['total_flights'] = d.get('total_flights', 0) + 1
break
save_json('drones.json', drones)
save_json('orders.json', orders)
return jsonify({'code': 0, 'data': orders[idx]})
@app.route('/api/orders/<order_id>/cancel', methods=['POST'])
def cancel_order(order_id):
orders = load_json('orders.json')
idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None)
if idx is None:
return jsonify({'code': 404, 'msg': '订单不存在'}), 404
orders[idx]['status'] = 'cancelled'
orders[idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
drone_id = orders[idx].get('drone_id')
if drone_id:
drones = load_json('drones.json')
for d in drones:
if d['id'] == drone_id:
d['status'] = 'available'
break
save_json('drones.json', drones)
save_json('orders.json', orders)
return jsonify({'code': 0, 'data': orders[idx]})
@app.route('/api/orders/<order_id>/rate', methods=['POST'])
def rate_order(order_id):
data = request.get_json()
orders = load_json('orders.json')
idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None)
if idx is None:
return jsonify({'code': 404, 'msg': '订单不存在'}), 404
orders[idx]['rating'] = data.get('rating', 5)
orders[idx]['comment'] = data.get('comment', '')
save_json('orders.json', orders)
return jsonify({'code': 0, 'data': orders[idx]})
# ── Quotes ──
@app.route('/api/quotes')
def list_quotes():
quotes = load_json('quotes.json')
order_id = request.args.get('order_id')
if order_id:
quotes = [q for q in quotes if q['order_id'] == order_id]
return jsonify({'code': 0, 'data': quotes})
@app.route('/api/quotes', methods=['POST'])
def create_quote():
data = request.get_json()
quotes = load_json('quotes.json')
quote = {
'id': gen_id('Q'),
'order_id': data['order_id'],
'provider_id': data['provider_id'],
'drone_id': data['drone_id'],
'price': data.get('price', 100),
'eta_minutes': data.get('eta_minutes', 15),
'distance_km': data.get('distance_km', 5),
'provider_name': data.get('provider_name', '未知'),
'drone_name': data.get('drone_name', '未知机型'),
'provider_rating': data.get('provider_rating', 4.5),
'provider_flights': data.get('provider_flights', 0),
'status': 'active',
'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
quotes.append(quote)
save_json('quotes.json', quotes)
return jsonify({'code': 0, 'data': quote}), 201
@app.route('/api/quotes/<quote_id>/accept', methods=['POST'])
def accept_quote(quote_id):
quotes = load_json('quotes.json')
q_idx = next((i for i, q in enumerate(quotes) if q['id'] == quote_id), None)
if q_idx is None:
return jsonify({'code': 404, 'msg': '报价不存在'}), 404
quote = quotes[q_idx]
quotes[q_idx]['status'] = 'accepted'
save_json('quotes.json', quotes)
orders = load_json('orders.json')
o_idx = next((i for i, o in enumerate(orders) if o['id'] == quote['order_id']), None)
if o_idx is not None:
orders[o_idx]['status'] = 'accepted'
orders[o_idx]['provider_id'] = quote['provider_id']
orders[o_idx]['drone_id'] = quote['drone_id']
orders[o_idx]['price'] = quote['price']
orders[o_idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
save_json('orders.json', orders)
return jsonify({'code': 0, 'data': quote})
# ── Dashboard Stats ──
@app.route('/api/dashboard/stats')
def dashboard_stats():
role = request.args.get('role', 'demander')
user_id = request.args.get('user_id')
orders = load_json('orders.json')
if user_id:
if role == 'demander':
orders = [o for o in orders if o['demander_id'] == user_id]
else:
orders = [o for o in orders if o.get('provider_id') == user_id]
total = len(orders)
pending = len([o for o in orders if o['status'] == 'pending'])
in_transit = len([o for o in orders if o['status'] in ('accepted', 'in_transit')])
completed = len([o for o in orders if o['status'] == 'completed'])
cancelled = len([o for o in orders if o['status'] == 'cancelled'])
total_revenue = sum(o['price'] for o in orders if o['status'] == 'completed')
avg_rating = 0
rated = [o['rating'] for o in orders if o.get('rating')]
if rated:
avg_rating = round(sum(rated) / len(rated), 1)
drones_online = 0
if role == 'provider':
user_drones = [d for d in load_json('drones.json') if d.get('user_id') == user_id]
drones_online = len([d for d in user_drones if d['status'] == 'available'])
else:
drones_online = len([d for d in load_json('drones.json') if d['status'] == 'available'])
stats = {
'total_orders': total,
'pending_orders': pending,
'in_transit_orders': in_transit,
'completed_orders': completed,
'cancelled_orders': cancelled,
'total_revenue': total_revenue,
'avg_rating': avg_rating,
'online_drones': drones_online,
'completion_rate': round(completed / total * 100, 1) if total > 0 else 0
}
return jsonify({'code': 0, 'data': stats})
# ── Agent analysis (AI simulation) ──
@app.route('/api/agent/analyze', methods=['POST'])
def agent_analyze():
data = request.get_json()
action = data.get('action', '')
if action == 'credit_check':
orders = load_json('orders.json')
flags = []
for o in orders:
if o['status'] == 'cancelled':
flags.append({
'order_id': o['id'],
'issue': '订单被取消',
'severity': '轻微',
'suggestion': '建议关注取消原因'
})
if o.get('rating') and o['rating'] <= 2:
flags.append({
'order_id': o['id'],
'issue': '低分评价',
'severity': '中等',
'suggestion': '建议回访用户了解问题'
})
return jsonify({'code': 0, 'data': {'issues': flags, 'summary': f'发现 {len(flags)} 个值得关注的问题'}})
elif action == 'pricing_suggestion':
orders = load_json('orders.json')
from datetime import datetime
now = datetime.now()
hour = now.hour
if 10 <= hour <= 12 or 17 <= hour <= 19:
multiplier = 1.2
reason = '当前为高峰时段建议加价20%'
elif 22 <= hour or hour <= 6:
multiplier = 1.3
reason = '当前为夜间时段建议加价30%'
else:
multiplier = 1.0
reason = '当前为平峰时段,维持基础费率'
return jsonify({
'code': 0,
'data': {
'multiplier': multiplier,
'reason': reason,
'suggestion': f'建议当前时段基础费率调整为 {multiplier}x'
}
})
elif action == 'anomaly_detect':
orders = load_json('orders.json')
anomalies = []
for o in orders:
if o['status'] == 'in_transit':
duration_hours = (datetime.now() - datetime.strptime(o['created_at'], '%Y-%m-%d %H:%M:%S')).total_seconds() / 3600
if duration_hours > 1:
anomalies.append({
'order_id': o['id'],
'type': '配送超时',
'detail': f'配送已超过{duration_hours:.1f}小时',
'suggestion': '建议联系承运方确认配送状态'
})
return jsonify({'code': 0, 'data': {'anomalies': anomalies, 'count': len(anomalies)}})
return jsonify({'code': 400, 'msg': '未知的分析类型'})
# ── Agent management (for future real AI integration) ──
@app.route('/api/agent/settings', methods=['GET'])
def agent_settings():
path = os.path.join(DB_DIR, 'agent_settings.json')
if not os.path.exists(path):
default = {
'providers': {
'health_check_enabled': True,
'auto_reconciliation': True,
'cert_reminder_days': 30
},
'pricing': {
'dynamic_pricing_enabled': True,
'max_surge_multiplier': 2.0,
'peak_hours': ['10:00-12:00', '17:00-20:00']
},
'anomaly': {
'auto_handle_l1': True,
'auto_handle_l2': False,
'notification_channels': ['sms', 'app']
},
'credit': {
'fraud_detection_enabled': True,
'min_rating_threshold': 3,
'auto_penalty': True
}
}
save_json('agent_settings.json', default)
return jsonify({'code': 0, 'data': default})
return jsonify({'code': 0, 'data': load_json('agent_settings.json')})
@app.route('/api/agent/settings', methods=['PUT'])
def update_agent_settings():
data = request.get_json()
save_json('agent_settings.json', data)
return jsonify({'code': 0, 'msg': 'Agent设置已更新'})
# ── Logging ──
@app.route('/api/logs')
def get_logs():
path = os.path.join(DB_DIR, 'logs.json')
if not os.path.exists(path):
return jsonify({'code': 0, 'data': []})
logs = load_json('logs.json')
logs.sort(key=lambda x: x.get('timestamp', ''), reverse=True)
return jsonify({'code': 0, 'data': logs[:100]})
@app.route('/api/logs', methods=['POST'])
def add_log():
data = request.get_json()
logs = load_json('logs.json') if os.path.exists(os.path.join(DB_DIR, 'logs.json')) else []
log = {
'id': gen_id('LOG'),
'type': data.get('type', 'info'),
'message': data.get('message', ''),
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
logs.append(log)
save_json('logs.json', logs)
return jsonify({'code': 0, 'data': log}), 201
# ── Health ──
@app.route('/api/health')
def health():
return jsonify({'status': 'ok', 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
if __name__ == '__main__':
print("""
╔══════════════════════════════════════════╗
║ 空运宝 - 后端服务 v1.0 ║
║ Listening at: http://localhost:5000 ║
║ API: http://localhost:5000/api/... ║
╚══════════════════════════════════════════╝
""")
app.run(host='0.0.0.0', port=5000, debug=True)